Merge branch '2015.11-develop' into 2016.11
[tine20] / tests / tine20 / Calendar / Controller / EventNotificationsTests.php
1 <?php
2 /**
3  * Tine 2.0 - http://www.tine20.org
4  * 
5  * @package     Calendar
6  * @license     http://www.gnu.org/licenses/agpl.html AGPL Version 3
7  * @copyright   Copyright (c) 2009-2014 Metaways Infosystems GmbH (http://www.metaways.de)
8  * @author      Cornelius Weiss <c.weiss@metaways.de>
9  */
10
11 /**
12  * Test class for Calendar_Controller_EventNotifications
13  * 
14  * @package     Calendar
15  */
16 class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
17 {
18     /**
19      * @var Calendar_Controller_Event controller unter test
20      */
21     protected $_eventController;
22     
23     /**
24      * @var Calendar_Controller_EventNotifications controller unter test
25      */
26     protected $_notificationController;
27     
28    /**
29     * email test class
30     *
31     * @var Felamimail_Controller_MessageTest
32     */
33     protected $_emailTestClass;
34     
35     /**
36      * (non-PHPdoc)
37      * @see tests/tine20/Calendar/Calendar_TestCase::setUp()
38      */
39     public function setUp()
40     {
41         parent::setUp();
42         
43         Calendar_Controller_Event::getInstance()->sendNotifications(true);
44         
45         $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP, new Tinebase_Config_Struct())->toArray();
46         if (empty($smtpConfig)) {
47              $this->markTestSkipped('No SMTP config found: this is needed to send notifications.');
48         }
49         
50         $this->_eventController = Calendar_Controller_Event::getInstance();
51         $this->_notificationController = Calendar_Controller_EventNotifications::getInstance();
52         
53         $this->_setupPreferences();
54         
55         Calendar_Config::getInstance()->set(Calendar_Config::MAX_NOTIFICATION_PERIOD_FROM, /* last 10 years */ 52 * 10);
56     }
57     
58     /**
59      * Tears down the fixture
60      * This method is called after a test is executed.
61      *
62      * @access protected
63      */
64     public function tearDown()
65     {
66         parent::tearDown();
67         
68         if ($this->_emailTestClass instanceof Felamimail_Controller_MessageTest) {
69             $this->_emailTestClass->tearDown();
70         }
71         
72         Calendar_Config::getInstance()->set(Calendar_Config::MAX_NOTIFICATION_PERIOD_FROM, /* last week */ 1);
73     }
74     
75     /**
76      * testInvitation
77      */
78     public function testInvitation()
79     {
80         $event = $this->_getEvent(TRUE);
81         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
82         
83         self::flushMailer();
84         $persistentEvent = $this->_eventController->create($event);
85         $this->_assertMail('jsmith', NULL);
86         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'invit');
87         
88         self::flushMailer();
89        
90         $persistentEvent = $this->_eventController->delete($persistentEvent); 
91         $this->_assertMail('jsmith', NULL);
92         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'cancel');
93     }
94
95     /**
96      * Test event creation with muted invitation
97      */
98     public function testMuteToogleCreation()
99     {
100         $event = $this->_getEvent(TRUE, /* $mute = */ 1);
101         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
102
103         self::flushMailer();
104         $persistentEvent = $this->_eventController->create($event);
105         $this->_assertMail('jsmith, pwulf, sclever, jmcblack, rwright', NULL);
106
107         $this->assertEquals($event->mute, 1);
108     }
109     
110     /**
111      * Test event reschedul with muted invitation
112      */
113     public function testMuteToogleReschedul()
114     {
115         $event = $this->_getEvent(TRUE, /* $mute = */ 1);
116         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
117         $persistentEvent = $this->_eventController->create($event);
118         
119         $persistentEvent->summary = 'reschedule notification has precedence over normal update';
120         $persistentEvent->dtstart->addHour(1);
121         $persistentEvent->dtend->addHour(1);
122         $persistentEvent->mute = 1;
123         $this->assertEquals($persistentEvent->mute, 1);
124         
125         self::flushMailer();
126         $updatedEvent = $this->_eventController->update($persistentEvent);
127         $this->_assertMail('jsmith, pwulf, sclever, jmcblack, rwright', NULL);
128     }
129     
130     /**
131      * testMuteToogleUpdateAttendeeStatus
132      */
133     public function testMuteToogleUpdateAttendeeStatus()
134     {
135         $event = $this->_getEvent(TRUE, /* $mute = */ 1);
136         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
137         $persistentEvent = $this->_eventController->create($event);
138     
139         $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
140         $persistentEvent->mute = 1;
141         $this->assertEquals($persistentEvent->mute, 1);
142     
143         self::flushMailer();
144         $updatedEvent = $this->_eventController->update($persistentEvent);
145         $this->_assertMail('jsmith, pwulf, sclever, jmcblack, rwright', NULL);
146     }
147
148     /**
149      * testInvitationWithAttachment
150      * 
151      * @see 0008592: append event file attachments to invitation mail
152      * @see 0009246: Mail address of organizer is broken in invite mails
153      */
154     public function testInvitationWithAttachment()
155     {
156         $event = $this->_getEvent(TRUE);
157         $event->attendee = $this->_getPersonaAttendee('pwulf');
158         
159         $tempFileBackend = new Tinebase_TempFile();
160         $tempFile = $tempFileBackend->createTempFile(dirname(dirname(dirname(__FILE__))) . '/Filemanager/files/test.txt');
161         $event->attachments = array(array('tempFile' => array('id' => $tempFile->getId())));
162         
163         self::flushMailer();
164         $persistentEvent = $this->_eventController->create($event);
165         
166         $messages = self::getMessages();
167         
168         $this->assertEquals(1, count($messages));
169         $parts = $messages[0]->getParts();
170         $this->assertEquals(2, count($parts));
171         $fileAttachment = $parts[1];
172         $this->assertEquals('text/plain; name="=?utf-8?Q?tempfile.tmp?="', $fileAttachment->type);
173         
174         // check VEVENT ORGANIZER mailto
175         $vcalendarPart = $parts[0];
176         $vcalendar = quoted_printable_decode($vcalendarPart->getContent());
177         $this->assertContains('SENT-BY="mailto:' . Tinebase_Core::getUser()->accountEmailAddress . '":mailto:', str_replace("\r\n ", '', $vcalendar), 'sent-by mailto not quoted');
178         
179         // @todo assert attachment content (this seems to not work with array mailer, maybe we need a "real" email test here)
180 //         $content = $fileAttachment->getDecodedContent();
181 //         $this->assertEquals('test file content', $content);
182     }
183     
184     /**
185      * testUpdateEmpty
186      */
187     public function testUpdateEmpty()
188     {
189         $event = $this->_getEvent();
190         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
191         $persistentEvent = $this->_eventController->create($event);
192         
193         // no updates
194         self::flushMailer();
195         $updatedEvent = $this->_eventController->update($persistentEvent);
196         $this->_assertMail('jsmith, pwulf, sclever, jmcblack, rwright', NULL);
197     }
198     
199     /**
200      * testUpdateChangeAttendee
201      */
202     public function testUpdateChangeAttendee()
203     {
204         $event = $this->_getEvent(TRUE);
205         $event->attendee = $this->_getPersonaAttendee('pwulf, jmcblack, rwright');
206         $persistentEvent = $this->_eventController->create($event);
207         
208         $persistentEvent->attendee->merge($this->_getPersonaAttendee('jsmith, sclever'));
209         $persistentEvent->attendee->removeRecord(
210             $persistentEvent->attendee->find('user_id', $this->_getPersonasContacts('pwulf')->getId())
211         );
212         $persistentEvent->attendee->find('user_id', $this->_getPersonasContacts('rwright')->getId())->status =
213             Calendar_Model_Attender::STATUS_ACCEPTED;
214         $persistentEvent->attendee->find('user_id', $this->_getPersonasContacts('jmcblack')->getId())->status =
215             Calendar_Model_Attender::STATUS_DECLINED;
216             
217         self::flushMailer();
218         $updatedEvent = $this->_eventController->update($persistentEvent);
219         $this->_assertMail('jsmith, jmcblack', NULL);
220         $this->_assertMail('sclever', 'invit');
221         $this->_assertMail('pwulf', 'cancel');
222         $this->_assertMail('rwright', 'Attendee');
223     }
224     
225     /**
226      * testUpdateReschedule
227      */
228     public function testUpdateReschedule()
229     {
230         $event = $this->_getEvent(TRUE);
231         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
232         $persistentEvent = $this->_eventController->create($event);
233         
234         $persistentEvent->summary = 'reschedule notification has precedence over normal update';
235         $persistentEvent->dtstart->addHour(1);
236         $persistentEvent->dtend->addHour(1);
237         
238         self::flushMailer();
239         $updatedEvent = $this->_eventController->update($persistentEvent);
240         $this->_assertMail('jsmith, pwulf', NULL);
241         $this->_assertMail('sclever, jmcblack, rwright', 'reschedul');
242     }
243     
244     /**
245      * testUpdateDetails
246      */
247     public function testUpdateDetails()
248     {
249         $event = $this->_getEvent(TRUE);
250         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
251         $persistentEvent = $this->_eventController->create($event);
252         
253         $persistentEvent->summary = 'detail update notification has precedence over attendee update';
254         $persistentEvent->url = 'http://somedetail.com';
255         $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_ACCEPTED;
256         
257         self::flushMailer();
258         $updatedEvent = $this->_eventController->update($persistentEvent);
259         $this->_assertMail('jsmith, pwulf, sclever', NULL);
260         $this->_assertMail('jmcblack, rwright', 'update');
261     }
262         
263     /**
264      * testUpdateAttendeeStatus
265      */
266     public function testUpdateAttendeeStatus()
267     {
268         $event = $this->_getEvent(TRUE);
269         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
270         $persistentEvent = $this->_eventController->create($event);
271         
272         $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
273         
274         self::flushMailer();
275         $updatedEvent = $this->_eventController->update($persistentEvent);
276         $this->_assertMail('jsmith, pwulf, sclever, jmcblack', NULL);
277         $this->_assertMail('rwright', 'decline');
278     }
279     
280     /**
281      * testOrganizerNotificationSupress
282      */
283     public function testOrganizerNotificationSupress()
284     {
285         $event = $this->_getEvent();
286         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf');
287         $event->organizer = $this->_getPersonasContacts('jsmith')->getId();
288         $persistentEvent = $this->_eventController->create($event);
289         
290         $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
291         
292         self::flushMailer();
293         $updatedEvent = $this->_eventController->update($persistentEvent);
294         $this->_assertMail('jsmith, pwulf', NULL);
295     }
296     
297     /**
298      * testOrganizerNotificationSend
299      */
300     public function testOrganizerNotificationSend()
301     {
302         $event = $this->_getEvent(TRUE);
303         $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf');
304         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
305         $persistentEvent = $this->_eventController->create($event);
306         
307         $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
308         
309         self::flushMailer();
310         $updatedEvent = $this->_eventController->update($persistentEvent);
311         $this->_assertMail('jsmith', NULL);
312         $this->_assertMail('pwulf', 'decline');
313     }
314     
315     /**
316      * testNotificationToNonAccounts
317      */
318     public function testNotificationToNonAccounts()
319     {
320         $event = $this->_getEvent(TRUE);
321         $event->attendee = $this->_getPersonaAttendee('pwulf');
322         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
323         
324         // add nonaccount attender
325         $nonAccountEmail = 'externer@example.org';
326         $nonAccountAttender = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
327             'n_family'  => 'externer',
328             'email'     => $nonAccountEmail,
329         )));
330         $event->attendee->addRecord($this->_createAttender($nonAccountAttender->getId()));
331         
332         self::flushMailer();
333         $persistentEvent = $this->_eventController->create($event);
334         
335         // invitaion should be send to internal and external attendee
336         $this->_assertMail('pwulf,externer@example.org', 'invitation');
337         
338         // add alarm
339         $persistentEvent->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
340             new Tinebase_Model_Alarm(array(
341                 'minutes_before' => 30
342             ), TRUE)
343         ));
344         
345         self::flushMailer();
346         $updatedEvent = $this->_eventController->update($persistentEvent);
347         
348         // don't send alarm change to external attendee
349         $this->_assertMail('externer@example.org');
350         
351         self::flushMailer();
352         $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
353         $updatedEvent = $this->_eventController->update($persistentEvent);
354         
355         $this->_assertMail('externer@example.org');
356         $this->_assertMail('pwulf', 'declined');
357     }
358     
359     /**
360      * testRecuringExceptions
361      */
362     public function testRecuringExceptions()
363     {
364         $from = new Tinebase_DateTime('2012-03-01 00:00:00');
365         $until = new Tinebase_DateTime('2012-03-31 23:59:59');
366         
367         $event = new Calendar_Model_Event(array(
368                 'summary'       => 'Some Daily Event',
369                 'dtstart'       => '2012-03-14 09:00:00',
370                 'dtend'         => '2012-03-14 10:00:00',
371                 'rrule'         => 'FREQ=DAILY;INTERVAL=1',
372                 'container_id'  => $this->_getTestCalendar()->getId(),
373                 'attendee'      => $this->_getPersonaAttendee('jmcblack'),
374         ));
375         
376         self::flushMailer();
377         $persistentEvent = $this->_eventController->create($event);
378         $this->_assertMail('jmcblack', 'Recurrance rule:    Daily', 'body');
379         
380         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
381         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
382         
383         // cancel instance
384         self::flushMailer();
385         $this->_eventController->createRecurException($recurSet[4], TRUE, FALSE); //2012-03-19
386         $this->_assertMail('jmcblack', 'cancel');
387         
388         // update instance
389         self::flushMailer();
390         $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($recurSet[5]);
391         $recurSet[5]->last_modified_time = $updatedBaseEvent->last_modified_time;
392         $recurSet[5]->summary = 'exceptional summary';
393         $this->_eventController->createRecurException($recurSet[5], FALSE, FALSE); //2012-03-20
394         $this->_assertMail('jmcblack', 'This is an event series exception', 'body');
395         $this->_assertMail('jmcblack', 'update');
396         
397         // reschedule instance
398         self::flushMailer();
399         $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($recurSet[6]);
400         $recurSet[6]->last_modified_time = $updatedBaseEvent->last_modified_time;
401         $recurSet[6]->dtstart->addHour(2);
402         $recurSet[6]->dtend->addHour(2);
403         $this->_eventController->createRecurException($recurSet[6], FALSE, FALSE); //2012-03-21
404         $this->_assertMail('jmcblack', 'reschedule');
405         
406         // cancel thisandfuture
407         // @TODO check RANGE in ics
408         // @TODO add RANGE text to message
409         self::flushMailer();
410         $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($recurSet[16]);
411         $recurSet[16]->last_modified_time = $updatedBaseEvent->last_modified_time;
412         $this->_eventController->createRecurException($recurSet[16], TRUE, TRUE); //2012-03-31
413         $this->_assertMail('jmcblack', 'cancel');
414         
415         // first instance exception (update not reschedule)
416         self::flushMailer();
417         $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
418         $updatedBaseEvent->summary = 'update first occurence';
419         $this->_eventController->createRecurException($updatedBaseEvent, FALSE, FALSE); // 2012-03-14
420         $this->_assertMail('jmcblack', 'has been updated');
421     }
422     
423     public function testAttendeeAlarmSkip()
424     {
425         $event = $this->_getEvent();
426         $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
427         $event->organizer = $this->_getPersonasContacts('sclever')->getId();
428         
429         $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
430         $event->dtend = clone $event->dtstart;
431         $event->dtend->addMinute(30);
432         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
433             new Tinebase_Model_Alarm(array(
434                 'minutes_before' => 30
435             ), TRUE)
436         ));
437         
438         // pwulf skips alarm
439         $event->alarms->setOption('skip', array(
440             array(
441                 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
442                 'user_id'   => $this->_getPersonasContacts('pwulf')->getId(),
443             )
444         ));
445         
446         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
447         $persistentEvent = $this->_eventController->create($event);
448         self::flushMailer();
449         
450         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
451         $this->_assertMail('sclever', 'Alarm for event');
452         $this->_assertMail('pwulf');
453     }
454     
455     public function testAttendeeAlarmOnly()
456     {
457         $event = $this->_getEvent();
458         $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
459         $event->organizer = $this->_getPersonasContacts('sclever')->getId();
460         
461         $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
462         $event->dtend = clone $event->dtstart;
463         $event->dtend->addMinute(30);
464         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
465             new Tinebase_Model_Alarm(array(
466                 'minutes_before' => 30
467             ), TRUE)
468         ));
469         $event->alarms->setOption('attendee', array(
470             'user_type' => Calendar_Model_Attender::USERTYPE_USER,
471             'user_id'   => $this->_getPersonasContacts('pwulf')->getId()
472         ));
473         
474         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
475         $persistentEvent = $this->_eventController->create($event);
476         self::flushMailer();
477         
478         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
479         $this->_assertMail('pwulf', 'Alarm for event');
480         $this->_assertMail('sclever');
481         
482     }
483     
484     public function testAlarm()
485     {
486         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
487         
488         $event = $this->_getEvent();
489         $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
490         $event->dtend = clone $event->dtstart;
491         $event->dtend->addMinute(30);
492         $event->attendee = $this->_getAttendee();
493         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
494             new Tinebase_Model_Alarm(array(
495                     'minutes_before' => 30
496             ), TRUE)
497         ));
498         
499         $persistentEvent = $this->_eventController->create($event);
500         Calendar_Model_Attender::getOwnAttender($persistentEvent->attendee)->status = Calendar_Model_Attender::STATUS_DECLINED;
501         
502         // hack to get declined attendee
503         $this->_eventController->sendNotifications(FALSE);
504         $updatedEvent = $this->_eventController->update($persistentEvent);
505         $this->_eventController->sendNotifications(TRUE);
506         
507         self::flushMailer();
508         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
509         $this->_assertMail('sclever', 'Alarm');
510         $this->assertEquals(1, count(self::getMessages()));
511     }
512
513     public function testAlarmSkipPreference()
514     {
515         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
516         
517         $calPreferences = Tinebase_Core::getPreference('Calendar');
518         $calPreferences->setValueForUser(
519             Calendar_Preference::SEND_ALARM_NOTIFICATIONS,
520             0,
521             $this->_getPersona('sclever')->getId(), TRUE
522         );
523
524         $event = $this->_getEvent();
525         $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
526         $event->dtend = clone $event->dtstart;
527         $event->dtend->addMinute(30);
528         $event->attendee = $this->_getAttendee();
529         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
530             new Tinebase_Model_Alarm(array(
531                 'minutes_before' => 30
532             ), TRUE)
533         ));
534
535         $persistentEvent = $this->_eventController->create($event);
536
537         self::flushMailer();
538         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
539         $this->_assertMail('sclever', NULL);
540     }
541
542     /**
543      * CalDAV/Custom can have alarms with odd times
544      */
545     public function testAlarmRoundMinutes()
546     {
547         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
548         
549         $event = $this->_getEvent();
550         $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
551         $event->dtend = clone $event->dtstart;
552         $event->dtend->addMinute(30);
553         $event->attendee = $this->_getAttendee();
554         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
555             new Tinebase_Model_Alarm(array(
556                     'minutes_before' => 12.1
557             ), TRUE)
558         ));
559         
560         $persistentEvent = $this->_eventController->create($event);
561         
562         $this->assertEquals(12, $persistentEvent->alarms->getFirstRecord()->getOption('minutes_before'));
563     }
564     
565     public function testSkipPastAlarm()
566     {
567         $event = $this->_getEvent();
568         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
569             new Tinebase_Model_Alarm(array(
570                     'minutes_before' => 30
571             ), TRUE)
572         ));
573         
574         $persistentEvent = $this->_eventController->create($event);
575         self::flushMailer();
576         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
577         $this->_assertMail('sclever');
578     }
579
580     /**
581      * testParallelAlarmTrigger
582      * 
583      * @see 0004878: improve asyncJob fencing
584      */
585     public function testParallelAlarmTrigger()
586     {
587         $this->_testNeedsTransaction();
588         
589         try {
590             $this->_emailTestClass = new Felamimail_Controller_MessageTest();
591             $this->_emailTestClass->setup();
592         } catch (Exception $e) {
593             Tinebase_Exception::log($e);
594             $this->markTestIncomplete('email not available.');
595         }
596         
597         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
598         self::flushMailer();
599         $this->_getAlarmMails(TRUE);
600         
601         $event = $this->_getEvent();
602         $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
603         $event->dtend = clone $event->dtstart;
604         $event->dtend->addMinute(30);
605         $event->attendee = $this->_getAttendee();
606         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
607             new Tinebase_Model_Alarm(array(
608                     'minutes_before' => 30
609             ), TRUE)
610         ));
611         
612         $persistentEvent = $this->_eventController->create($event);
613         try {
614             Tinebase_AsyncJobTest::triggerAsyncEvents();
615         } catch (Exception $e) {
616             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
617                 . ' Something strange happened and the async jobs did not complete ... maybe the test system is not configured correctly for this: ' . $e);
618             $this->markTestIncomplete($e->getMessage());
619         }
620         
621         $result = $this->_getAlarmMails(TRUE);
622         $this->assertEquals(1, count($result), 'expected exactly 1 alarm mail, got: ' . print_r($result->toArray(), TRUE));
623     }
624     
625     /**
626      * testRecuringAlarm
627      */
628     public function testRecuringAlarm()
629     {
630         $event = $this->_getEvent();
631         $event->attendee = $this->_getPersonaAttendee('pwulf');
632         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
633         
634         // lets flush mailer so next flushing ist faster!
635         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
636         self::flushMailer();
637         
638         // make sure next occurence contains now
639         // next occurance now+29min 
640         $event->dtstart = Tinebase_DateTime::now()->subDay(1)->addMinute(28);
641         $event->dtend = clone $event->dtstart;
642         $event->dtend->addMinute(30);
643         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
644         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
645             new Tinebase_Model_Alarm(array(
646                 'minutes_before' => 30
647             ), TRUE)
648         ));
649         
650         $persistentEvent = $this->_eventController->create($event);
651         
652         // assert alarm
653         self::flushMailer();
654         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
655         $assertString = ' at ' . Tinebase_DateTime::now()->format('M j');
656         $this->_assertMail('pwulf', $assertString);
657
658         // check adjusted alarm time
659         $loadedEvent = $this->_eventController->get($persistentEvent->getId());
660         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
661         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
662         
663         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now()->addDay(1), 'alarmtime is not adjusted');
664         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $loadedEvent->alarms->getFirstRecord()->sent_status, 'alarmtime is set to pending');
665         
666         // update series @see #7430: Calendar sends too much alarms for recurring events
667         $this->_eventController->update($loadedEvent);
668         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
669         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
670         
671         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now()->addDay(1), 'alarmtime is wrong');
672     }
673     
674     /**
675      * if an event with an alarm gets an exception instance, also the alarm gets an exception instance
676      * @see #6328
677      */
678     public function testRecuringAlarmException()
679     {
680         $event = $this->_getEvent();
681         $event->attendee = $this->_getPersonaAttendee('pwulf');
682         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
683         
684         $event->dtstart = Tinebase_DateTime::now()->subDay(1)->addMinute(15);
685         $event->dtend = clone $event->dtstart;
686         $event->dtend->addMinute(30);
687         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
688         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
689                 new Tinebase_Model_Alarm(array(
690                         'minutes_before' => 30
691                 ), TRUE)
692         ));
693         
694         $persistentEvent = $this->_eventController->create($event);
695         
696         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
697         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $persistentEvent->dtstart, Tinebase_DateTime::now()->addDay(1));
698         $exceptionEvent = $this->_eventController->createRecurException($recurSet->getFirstRecord());
699         
700         // assert one alarm only
701         self::flushMailer();
702         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
703         $assertString = ' at ' . Tinebase_DateTime::now()->format('M j');
704         $this->_assertMail('pwulf', $assertString);
705         
706         // check series
707         $loadedEvent = $this->_eventController->get($persistentEvent->getId());
708         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
709         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
710         
711         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now(), 'alarmtime of series is not adjusted');
712         
713         // check exception
714         $recurid = $exceptionEvent->alarms->getFirstRecord()->getOption('recurid');
715         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
716         
717         $this->assertTrue($nextAlarmEventStart < Tinebase_DateTime::now()->addHour(1), 'alarmtime of exception is not adjusted');
718         
719         // update exception @see #7430: Calendar sends too much alarms for recurring events
720         $exceptionEvent = $this->_eventController->update($exceptionEvent);
721         $recurid = $exceptionEvent->alarms->getFirstRecord()->getOption('recurid');
722         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
723         
724         $this->assertTrue($nextAlarmEventStart < Tinebase_DateTime::now()->addHour(1), 'alarmtime of exception is wrong');
725     }
726     
727     /**
728      * testRecuringAlarmCustomDate
729      */
730     public function testRecuringAlarmCustomDate()
731     {
732         $event = $this->_getEvent();
733         $event->attendee = $this->_getPersonaAttendee('pwulf');
734         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
735         
736         $event->dtstart = Tinebase_DateTime::now()->addWeek(1)->addMinute(15);
737         $event->dtend = clone $event->dtstart;
738         $event->dtend->addMinute(30);
739         $event->rrule = 'FREQ=YEARLY;INTERVAL=1;BYDAY=2TH;BYMONTH=12';
740         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
741             new Tinebase_Model_Alarm(array(
742                 'minutes_before' => Tinebase_Model_Alarm::OPTION_CUSTOM,
743                 // NOTE: user means one week and 30 mins before
744                 'alarm_time'     => Tinebase_DateTime::now()->subMinute(15)
745             ), TRUE)
746         ));
747         
748         $persistentEvent = $this->_eventController->create($event);
749         
750         // assert one alarm only
751         self::flushMailer();
752         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
753         $assertString = ' at ' . Tinebase_DateTime::now()->addWeek(1)->format('M j');
754         $this->_assertMail('pwulf', $assertString);
755         
756         // check adjusted alarm time
757         $loadedEvent = $this->_eventController->get($persistentEvent->getId());
758         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
759         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
760         
761         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now(), 'alarmtime of series is not adjusted');
762     }
763     
764     /**
765      * test alarm inspection from 24.03.2012 -> 25.03.2012
766      */
767     public function testAdoptAlarmDSTBoundary()
768     {
769         $event = $this->_getEvent();
770         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
771         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
772             new Tinebase_Model_Alarm(array(
773                 'minutes_before' => 30
774             ), TRUE)
775         ));
776         $persistentEvent = $this->_eventController->create($event);
777         
778         // prepare alarm for last non DST instance
779         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
780         $from = new Tinebase_DateTime('2012-03-24 00:00:00');
781         $until = new Tinebase_DateTime('2012-03-24 23:59:59');
782         $recurSet =Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
783         
784         $alarm = $persistentEvent->alarms->getFirstRecord();
785         $alarm->setOption('recurid', $recurSet[0]->recurid);
786         Tinebase_Alarm::getInstance()->update($alarm);
787         
788         $loadedBaseEvent = $this->_eventController->get($persistentEvent->getId());
789         $alarm = $loadedBaseEvent->alarms->getFirstRecord();
790         $this->assertEquals('2012-03-24', substr($alarm->getOption('recurid'), -19, -9), 'precondition failed');
791         
792         // adopt alarm
793         $this->_eventController->adoptAlarmTime($loadedBaseEvent, $alarm, 'instance');
794         $this->assertEquals('2012-03-25', substr($alarm->getOption('recurid'), -19, -9), 'alarm adoption failed');
795     }
796     
797     /**
798      * test alarm inspection from 24.03.2012 -> 25.03.2012
799      */
800     public function testAdoptAlarmDSTBoundaryWithSkipping()
801     {
802         $event = new Calendar_Model_Event(array(
803             'summary'      => 'Cleanup',
804             'dtstart'      => '2012-01-31 07:30:00',
805             'dtend'        => '2012-01-31 10:30:00',
806             'container_id' => $this->_getTestCalendar()->getId(),
807             'uid'          => Calendar_Model_Event::generateUID(),
808             'rrule'        => 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=TU',
809             'originator_tz'=> 'Europe/Berlin',
810         ));
811         
812         $alarm = new Tinebase_Model_Alarm(array(
813             'model'        => 'Calendar_Model_Event',
814             'alarm_time'   => '2012-03-26 06:30:00',
815             'minutes_before' => 1440,
816             'options'      => '{"minutes_before":1440,"recurid":"a7c55ce09cea9aec4ac37d9d72789183b12cad7c-2012-03-27 06:30:00","custom":false}',
817         ));
818         
819         $this->_eventController->adoptAlarmTime($event, $alarm, 'instance');
820         
821         $this->assertEquals('2012-04-02 06:30:00', $alarm->alarm_time->toString());
822     }
823     
824     public function testAlarmSkipDeclined()
825     {
826         $event = $this->_getEvent();
827         $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
828         $event->organizer = $this->_getPersonasContacts('sclever')->getId();
829         
830         $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
831         $event->dtend = clone $event->dtstart;
832         $event->dtend->addMinute(30);
833         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
834             new Tinebase_Model_Alarm(array(
835                 'minutes_before' => 30
836             ), TRUE)
837         ));
838         
839         $persistentEvent = $this->_eventController->create($event);
840         $sclever = Calendar_Model_Attender::getAttendee($persistentEvent->attendee, $event->attendee[0]);
841         $sclever->status = Calendar_Model_Attender::STATUS_DECLINED;
842         $this->_eventController->attenderStatusUpdate($persistentEvent, $sclever, $sclever->status_authkey);
843         
844         self::flushMailer();
845         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
846         $this->_assertMail('pwulf', 'Alarm');
847         $this->assertEquals(1, count(self::getMessages()));
848     }
849     
850     /**
851      * Trying to reproduce a fatal error but won't work yet
852      */
853     public function testAlarmWithoutOrganizer()
854     {
855         $calInstance = Addressbook_Controller_Contact::getInstance();
856         $newContactData = array(
857             'n_given'           => 'foo',
858             'n_family'          => 'PHPUNIT',
859             'email'             => 'foo@tine20.org',
860             'tel_cell_private'  => '+49TELCELLPRIVATE',
861         );
862         $newContact = $calInstance->create(new Addressbook_Model_Contact($newContactData));
863         
864         $event = $this->_getEvent();
865         $event->attendee = $this->_createAttender($newContact->getId());
866         $event->organizer = $newContact->getId();
867         
868         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
869             new Tinebase_Model_Alarm(array(
870                 'minutes_before' => 30
871             ), TRUE)
872         ));
873         $persistentEvent = $this->_eventController->create($event);
874         
875         $calInstance->delete(array($newContact->getId()));
876         self::flushMailer();
877         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
878         $this->assertEquals(0, count(self::getMessages()));
879     }
880     
881     /**
882      * testRecuringAlarmAfterSeriesEnds
883      * 
884      * @see 0008386: alarm is sent for recur series that is already over
885      */
886     public function testRecuringAlarmAfterSeriesEnds()
887     {
888         $this->_recurAlarmTestHelper();
889     }
890     
891     /**
892      * helper for recurring alarm tests
893      * 
894      * @param boolean $allFollowing
895      * @param integer $alarmMinutesBefore
896      */
897     protected function _recurAlarmTestHelper($allFollowing = TRUE, $alarmMinutesBefore = 60)
898     {
899         $event = $this->_getEvent();
900         
901         // lets flush mailer so next flushing ist faster!
902         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
903         self::flushMailer();
904         
905         // make sure next occurence contains now
906         $event->dtstart = Tinebase_DateTime::now()->subDay(2)->addHour(1);
907         $event->dtend = clone $event->dtstart;
908         $event->dtend->addMinute(60);
909         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
910         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
911             new Tinebase_Model_Alarm(array(
912                 'minutes_before' => $alarmMinutesBefore
913             ), TRUE)
914         ));
915         
916         // check alarm
917         $persistentEvent = $this->_eventController->create($event);
918         $this->assertEquals(1, count($persistentEvent->alarms));
919         $alarm = $persistentEvent->alarms->getFirstRecord();
920         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $alarm->sent_status);
921         $persistentDtstart = clone $persistentEvent->dtstart;
922         $this->assertEquals($persistentDtstart->subMinute($alarmMinutesBefore), $alarm->alarm_time, print_r($alarm->toArray(), TRUE));
923         
924         // delete all following
925         $from = $event->dtstart;
926         $until = $event->dtend->addDay(3);
927         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
928         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
929         $recurEvent = $recurSet[1]; // today
930         $persistentEvent = $this->_eventController->createRecurException($recurEvent, TRUE, $allFollowing);
931         
932         $baseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
933         if ($allFollowing) {
934             $until = $recurSet[0]->dtstart->getClone()
935             ->setTimezone($baseEvent->originator_tz)
936             ->setTime(23,59,59)
937             ->setTimezone('UTC');
938             
939             $this->assertEquals('FREQ=DAILY;INTERVAL=1;UNTIL=' . $until->toString(), (string) $baseEvent->rrule, 'rrule mismatch');
940             $this->assertEquals(1, count($baseEvent->alarms));
941             $this->assertEquals('Nothing to send, series is over', $baseEvent->alarms->getFirstRecord()->sent_message,
942                 'alarm adoption failed: ' . print_r($baseEvent->alarms->getFirstRecord()->toArray(), TRUE));
943         } else {
944             $this->assertEquals('FREQ=DAILY;INTERVAL=1', (string) $baseEvent->rrule);
945             $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $baseEvent->alarms->getFirstRecord()->sent_status);
946             $this->assertEquals('', $baseEvent->alarms->getFirstRecord()->sent_message);
947         }
948         
949         // assert no alarm
950         self::flushMailer();
951         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
952         $messages = self::getMessages();
953         $this->assertEquals(0, count($messages), 'no alarm message should be sent: ' . print_r($messages, TRUE));
954     }
955     
956     /**
957      * testRecuringAlarmWithRecurException
958      * 
959      * @see 0008386: alarm is sent for recur series that is already over
960      */
961     public function testRecuringAlarmWithRecurException()
962     {
963         $this->_recurAlarmTestHelper(FALSE);
964     }
965
966     /**
967      * testRecuringAlarmWithRecurException120MinutesBefore
968      * 
969      * @see 0008386: alarm is sent for recur series that is already over
970      */
971     public function testRecuringAlarmWithRecurException120MinutesBefore()
972     {
973         $this->_recurAlarmTestHelper(FALSE, 120);
974     }
975
976     /**
977      * testRecuringAlarmWithRecurExceptionMoved
978      * 
979      * @see 0008386: alarm is sent for recur series that is already over
980      */
981     public function testRecuringAlarmWithRecurExceptionMoved()
982     {
983         $event = $this->_getEvent();
984         
985         // lets flush mailer so next flushing ist faster!
986         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
987         self::flushMailer();
988         
989         // make sure next occurence contains now
990         $event->dtstart = Tinebase_DateTime::now()->subWeek(2)->addDay(1);
991         $event->dtend = clone $event->dtstart;
992         $event->dtend->addMinute(60);
993         $event->rrule = 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=' . array_search($event->dtstart->format('w'), Calendar_Model_Rrule::$WEEKDAY_DIGIT_MAP);
994         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
995             new Tinebase_Model_Alarm(array(
996                 'minutes_before' => 1440
997             ), TRUE)
998         ));
999         
1000         $persistentEvent = $this->_eventController->create($event);
1001         
1002         // adopt alarm time (previous alarms have been sent already)
1003         $alarm = $persistentEvent->alarms->getFirstRecord();
1004         $alarm->alarm_time->addWeek(2);
1005         Tinebase_Alarm::getInstance()->update($alarm);
1006         
1007         // move next occurrence
1008         $from = $event->dtstart;
1009         $until = $event->dtend->addWeek(3);
1010         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
1011         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
1012         $recurEvent = $recurSet[1]; // tomorrow
1013         
1014         $recurEvent->dtstart->addDay(5);
1015         $recurEvent->dtend = clone $recurEvent->dtstart;
1016         $recurEvent->dtend->addMinute(60);
1017         $persistentEvent = $this->_eventController->createRecurException($recurEvent);
1018         
1019         $baseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
1020         $alarm = $baseEvent->alarms->getFirstRecord();
1021         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $alarm->sent_status);
1022         
1023         // assert no alarm
1024         sleep(1);
1025         self::flushMailer();
1026         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
1027         $messages = self::getMessages();
1028         $this->assertEquals(0, count($messages), 'no alarm message should be sent: ' . print_r($messages, TRUE));
1029     }
1030
1031     /**
1032      * testRecuringAlarmWithThisAndFutureSplit
1033      * 
1034      * @see 0008386: alarm is sent for recur series that is already over
1035      */
1036     public function testRecuringAlarmWithThisAndFutureSplit()
1037     {
1038         $this->markTestSkipped('@see 0009816: fix failing testRecuringAlarmWithThisAndFutureSplit test');
1039         
1040         $event = $this->_getEvent();
1041         
1042         // lets flush mailer so next flushing ist faster!
1043         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
1044         self::flushMailer();
1045         
1046         // make sure next occurence contains now
1047         $event->dtstart = Tinebase_DateTime::now()->subMonth(1)->addDay(1)->subHour(2);
1048         $event->dtend = clone $event->dtstart;
1049         $event->dtend->addMinute(60);
1050         $event->rrule = 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=' . $event->dtstart->format('d');
1051         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
1052             new Tinebase_Model_Alarm(array(
1053                 'minutes_before' => 2880
1054             ), TRUE)
1055         ));
1056         
1057         $persistentEvent = $this->_eventController->create($event);
1058         
1059         // make sure, next alarm is for next month's event
1060         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
1061         self::flushMailer();
1062         
1063         // split THISANDFUTURE, alarm of old series should be set to SUCCESS because it no longer should be sent
1064         $from = $event->dtstart;
1065         $until = $event->dtend->addMonth(2);
1066         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
1067         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
1068         $recurEvent = (count($recurSet) > 1) ? $recurSet[1] : $recurSet[0]; // next month
1069         $recurEvent->summary = 'split series';
1070         $newPersistentEvent = $this->_eventController->createRecurException($recurEvent, FALSE, TRUE);
1071         
1072         // check alarms
1073         $oldSeriesAlarm = Tinebase_Alarm::getInstance()
1074             ->getAlarmsOfRecord('Calendar_Model_Event', $persistentEvent->getId())
1075             ->getFirstRecord();
1076         $this->assertEquals(Tinebase_Model_Alarm::STATUS_SUCCESS, $oldSeriesAlarm->sent_status,
1077             'no pending alarm should exist for old series: ' . print_r($oldSeriesAlarm->toArray(), TRUE));
1078     }
1079     
1080     /**
1081      * put an exception event created by "remind" option of alarm in iCal
1082      */
1083     public function testPutEventExceptionAlarmReminder()
1084     {
1085         $event = $this->_createRecurringCalDavEvent();
1086         $messages = self::getMessages();
1087         $this->assertEquals(1, count($messages), 'one invitation should be send to sclever');
1088         $this->_assertMail('sclever', 'invitation');
1089     
1090         // create alarm reminder/snooze exception
1091         Calendar_Controller_EventNotificationsTests::flushMailer();
1092         $vcalendar = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/../Import/files/apple_ical_remind_part2.ics');
1093         $event->put($vcalendar);
1094     
1095         // assert no reschedule mail
1096         $messages = Calendar_Controller_EventNotificationsTests::getMessages();
1097         $this->assertEquals(0, count($messages), 'no reschedule mails should be send for implicit exception');
1098     }
1099     
1100     /**
1101      * createRecurringCalDavEvent
1102      * 
1103      * @return Calendar_Frontend_WebDAV_Event
1104      */
1105     protected function _createRecurringCalDavEvent()
1106     {
1107         $_SERVER['HTTP_USER_AGENT'] = 'Mac_OS_X/10.9 (13A603) CalendarAgent/174';
1108         
1109         self::flushMailer();
1110         $vcalendar = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/../Import/files/apple_ical_remind_part1.ics');
1111         $id = Tinebase_Record_Abstract::generateUID();
1112         $event = Calendar_Frontend_WebDAV_Event::create($this->_getTestCalendar(), "$id.ics", $vcalendar);
1113         
1114         return $event;
1115     }
1116     
1117     /**
1118      * testNotificationPeriodConfig
1119      * 
1120      * @see 0010048: config for notifications for past events
1121      */
1122     public function testNotificationPeriodConfig()
1123     {
1124         Calendar_Config::getInstance()->set(Calendar_Config::MAX_NOTIFICATION_PERIOD_FROM, /* last week */ 1);
1125         $event = $this->_createRecurringCalDavEvent();
1126         $messages = self::getMessages();
1127         $this->assertEquals(0, count($messages), 'no invitation should be send to sclever');
1128     }
1129     
1130     /**
1131      * get test alarm emails
1132      * 
1133      * @param boolean $deleteThem
1134      * @return Tinebase_Record_RecordSet
1135      */
1136     protected function _getAlarmMails($deleteThem = FALSE)
1137     {
1138         // search and assert alarm mail
1139         $folder = $this->_emailTestClass->getFolder('INBOX');
1140         $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10, 1);
1141         $i = 0;
1142         while ($folder->cache_status != Felamimail_Model_Folder::CACHE_STATUS_COMPLETE && $i < 10) {
1143             $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10);
1144             $i++;
1145         }
1146         $account = Felamimail_Controller_Account::getInstance()->search()->getFirstRecord();
1147         $filter = new Felamimail_Model_MessageFilter(array(
1148             array('field' => 'folder_id',  'operator' => 'equals',     'value' => $folder->getId()),
1149             array('field' => 'account_id', 'operator' => 'equals',     'value' => $account->getId()),
1150             array('field' => 'subject',    'operator' => 'startswith', 'value' => 'Alarm for event "Wakeup" at'),
1151         ));
1152         
1153         $result = Felamimail_Controller_Message::getInstance()->search($filter);
1154         
1155         if ($deleteThem) {
1156             Felamimail_Controller_Message_Move::getInstance()->moveMessages($filter, Felamimail_Model_Folder::FOLDER_TRASH);
1157         }
1158         
1159         return $result;
1160     }
1161     
1162     /**
1163      * testAdoptAlarmDSTBoundaryAllDayEvent
1164      * 
1165      * @see 0009820: Infinite loop in adoptAlarmTime / computeNextOccurrence (DST Boundary)
1166      */
1167     public function testAdoptAlarmDSTBoundaryAllDayEvent()
1168     {
1169         $event = $this->_getEvent();
1170         $event->is_all_day_event = 1;
1171         $event->dtstart = new Tinebase_DateTime('2014-03-03 23:00:00');
1172         $event->dtend = new Tinebase_DateTime('2014-03-04 22:59:59');
1173         $event->originator_tz = 'Europe/Berlin';
1174         $event->rrule = 'FREQ=DAILY';
1175         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
1176             new Tinebase_Model_Alarm(array(
1177                 'minutes_before' => 15,
1178             ), TRUE)
1179         ));
1180         
1181         $savedEvent = Calendar_Controller_Event::getInstance()->create($event);
1182         
1183         $alarm = $savedEvent->alarms->getFirstRecord();
1184         $alarm->sent_time = new Tinebase_DateTime('2014-03-29 22:46:01');
1185         $alarm->alarm_time = new Tinebase_DateTime('2014-03-29 22:45:00');
1186         $alarm->setOption('recurid', $savedEvent->uid . '-2014-03-29 23:00:00');
1187         Tinebase_Alarm::getInstance()->update($alarm);
1188         $alarm = $this->_eventController->get($savedEvent->getId())->alarms->getFirstRecord();
1189         
1190         Calendar_Controller_Event::getInstance()->adoptAlarmTime($savedEvent, $alarm, 'instance');
1191         
1192         $this->assertEquals('2014-03-30 21:45:00', $alarm->alarm_time->toString());
1193     }
1194     
1195     /**
1196      * checks if mail for persona got send
1197      * 
1198      * @param string $_personas
1199      * @param string $_assertString
1200      * @return void
1201      * 
1202      * @see #6800: add message-id to notification mails
1203      */
1204     protected function _assertMail($_personas, $_assertString = NULL, $_location = 'subject')
1205     {
1206         $messages = self::getMessages();
1207         
1208         foreach (explode(',', $_personas) as $personaName) {
1209             $mailsForPersona = array();
1210             $otherRecipients = array();
1211             $personaEmail = strstr($personaName, '@') ? 
1212                 $personaName : 
1213                 $this->_getPersona(trim($personaName))->accountEmailAddress;
1214             
1215             foreach ($messages as $message) {
1216                 if (Tinebase_Helper::array_value(0, $message->getRecipients()) == $personaEmail) {
1217                     array_push($mailsForPersona, $message);
1218                 } else {
1219                     array_push($otherRecipients, $message->getRecipients());
1220                 }
1221             }
1222             
1223             if (! $_assertString) {
1224                 $this->assertEquals(0, count($mailsForPersona), 'No mail should be send for '. $personaName);
1225             } else {
1226                 $this->assertEquals(1, count($mailsForPersona), 'One mail should be send for '. $personaName . ' other recipients: ' . print_r($otherRecipients, true));
1227                 $this->assertEquals('UTF-8', $mailsForPersona[0]->getCharset());
1228                 
1229                 switch ($_location) {
1230                     case 'subject':
1231                         $subject = $mailsForPersona[0]->getSubject();
1232                         $this->assertTrue(FALSE !== strpos($subject, $_assertString), 'Mail subject for ' . $personaName . ' should contain "' . $_assertString . '" but '. $subject . ' is given');
1233                         break;
1234                         
1235                     case 'body':
1236                         $bodyPart = $mailsForPersona[0]->getBodyText(FALSE);
1237                         
1238                         // so odd!
1239                         $s = fopen('php://temp','r+');
1240                         fputs($s, $bodyPart->getContent());
1241                         rewind($s);
1242                         $bodyPartStream = new Zend_Mime_Part($s);
1243                         $bodyPartStream->encoding = $bodyPart->encoding;
1244                         $bodyText = $bodyPartStream->getDecodedContent();
1245                         
1246                         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1247                             . ' body text: ' . $bodyText);
1248                         
1249                         $this->assertContains($_assertString, $bodyText);
1250                         break;
1251                         
1252                     default:
1253                         throw new Exception('no such location '. $_location);
1254                         break;
1255                 }
1256                 
1257                 $headers = $mailsForPersona[0]->getHeaders();
1258                 $this->assertTrue(isset($headers['Message-Id']), 'message-id header not found');
1259                 $this->assertContains('@' . php_uname('n'), $headers['Message-Id'][0], 'hostname not in message-id');
1260             }
1261         }
1262     }
1263     
1264     /**
1265      * get attendee
1266      * 
1267      * @param string $_personas
1268      * @return Tinebase_Record_RecordSet
1269      */
1270     protected function _getPersonaAttendee($_personas)
1271     {
1272         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender');
1273         foreach (explode(',', $_personas) as $personaName) {
1274             $attendee->addRecord($this->_createAttender($this->_getPersonasContacts(trim($personaName))->getId()));
1275         }
1276         
1277         return $attendee;
1278     }
1279     
1280     /**
1281      * setup preferences for personas
1282      * 
1283      * jsmith   -> no updates
1284      * pwulf    -> on invitaion/cancelation
1285      * sclever  -> on reschedules
1286      * jmblack  -> on updates except answers
1287      * rwright  -> even on ansers
1288      * 
1289      * @return void
1290      */
1291     protected function _setupPreferences()
1292     {
1293         // set notification levels
1294         $calPreferences = Tinebase_Core::getPreference('Calendar');
1295         $calPreferences->setValueForUser(
1296             Calendar_Preference::NOTIFICATION_LEVEL, 
1297             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_NONE,
1298             $this->_getPersona('jsmith')->getId(), TRUE
1299         );
1300         $calPreferences->setValueForUser(
1301             Calendar_Preference::NOTIFICATION_LEVEL, 
1302             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_INVITE_CANCEL,
1303             $this->_getPersona('pwulf')->getId(), TRUE
1304         );
1305         $calPreferences->setValueForUser(
1306             Calendar_Preference::NOTIFICATION_LEVEL, 
1307             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_EVENT_RESCHEDULE,
1308             $this->_getPersona('sclever')->getId(), TRUE
1309         );
1310         $calPreferences->setValueForUser(
1311             Calendar_Preference::NOTIFICATION_LEVEL, 
1312             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_EVENT_UPDATE,
1313             $this->_getPersona('jmcblack')->getId(), TRUE
1314         );
1315         $calPreferences->setValueForUser(
1316             Calendar_Preference::NOTIFICATION_LEVEL, 
1317             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE,
1318             $this->_getPersona('rwright')->getId(), TRUE
1319         );
1320         
1321         // set all languages to en
1322         $preferences = Tinebase_Core::getPreference('Tinebase');
1323         foreach ($this->_getPersonas() as $name => $account) {
1324             $preferences->setValueForUser(Tinebase_Preference::LOCALE, 'en', $account->getId(), TRUE);
1325         }
1326     }
1327     
1328     /**
1329      * testResourceNotification
1330      * 
1331      * checks if notification mail is sent to configured mail address of a resource
1332      * 
1333      * @see 0009954: resource manager and email handling
1334      *
1335      * @param boolean $suppress_notification
1336      */
1337     public function testResourceNotification($suppress_notification = false)
1338     {
1339         // create resource with email address of unittest user
1340         $resource = $this->_getResource();
1341         $resource->email = $this->_GetPersonasContacts('pwulf')->email;
1342         $resource->suppress_notification = $suppress_notification;
1343         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
1344         
1345         // create event with this resource as attender
1346         $event = $this->_getEvent(/* now = */ true);
1347         $event->attendee->addRecord($this->_createAttender($persistentResource->getId(), Calendar_Model_Attender::USERTYPE_RESOURCE));
1348
1349         self::flushMailer();
1350         $persistentEvent = $this->_eventController->create($event);
1351         
1352         $this->assertEquals(3, count($persistentEvent->attendee));
1353
1354         $messages = self::getMessages();
1355
1356         if ($suppress_notification) {
1357             $this->assertEquals(1, count($messages), 'one mail should be send to attender (invite sclever) ' . print_r($messages, true));
1358         } else {
1359             $this->assertEquals(2, count($messages), 'two mails should be send (resource=pwulf + attender invite=sclever) '
1360                 . print_r($messages, true));
1361         }
1362
1363         // assert user agent
1364         // @see 0011498: set user agent header for notification messages
1365         $headers = $messages[0]->getHeaders();
1366         $this->assertEquals(Tinebase_Core::getTineUserAgent('Notification Service'), $headers['User-Agent'][0]);
1367     }
1368
1369     /**
1370      * Enable by a preference which sends mails to every user who got permissions to edit the resource
1371      */
1372     public function testResourceNotificationForGrantedUsers($userIsAttendee = true, $suppress_notification = false)
1373     {
1374         // Enable feature, disabled by default!
1375         Calendar_Config::getInstance()->set(Calendar_Config::RESOURCE_MAIL_FOR_EDITORS, true);
1376
1377         $resource = $this->_getResource();
1378         $resource->email = Tinebase_Core::getUser()->accountEmailAddress;
1379         $resource->suppress_notification = $suppress_notification;
1380         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
1381
1382         $event = $this->_getEvent(/*now = */ true);
1383         $event->attendee->addRecord($this->_createAttender($persistentResource->getId(), Calendar_Model_Attender::USERTYPE_RESOURCE));
1384
1385         if (! $userIsAttendee) {
1386             // remove organizer attendee
1387             foreach ($event->attendee as $idx => $attender) {
1388                 if ($attender->user_id === $event->organizer) {
1389                     $event->attendee->removeRecord($attender);
1390                 }
1391             }
1392         }
1393
1394         $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($resource->container_id);
1395
1396         $newGrants = array(
1397                 'account_id' => $this->_personas['sclever']->getId(),
1398                 'account_type' => 'user',
1399                 Tinebase_Model_Grants::GRANT_READ => true,
1400                 Tinebase_Model_Grants::GRANT_EDIT => true
1401             );
1402
1403         Tinebase_Container::getInstance()->setGrants($resource->container_id, new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array_merge(array($newGrants), $grants->toArray())), TRUE);
1404
1405         self::flushMailer();
1406
1407         $persistentEvent = $this->_eventController->create($event);
1408
1409         $messages = self::getMessages();
1410
1411         Tinebase_Container::getInstance()->setGrants($resource->container_id, $grants);
1412
1413         if ($suppress_notification) {
1414             $this->assertEquals(1, count($messages), 'one mail should be send to current user (attender)');
1415         } else {
1416             $this->assertEquals(4, count($messages), 'four mails should be send to current user (resource + attender + everybody who is allowed to edit this resource)');
1417             $this->assertEquals(count($event->attendee), count($persistentEvent->attendee));
1418             $this->assertContains('Resource "' . $persistentResource->name . '" was booked', print_r($messages, true));
1419             $this->assertContains('Meeting Room (Required, No response)', print_r($messages, true));
1420         }
1421     }
1422
1423     /**
1424      * @see 0011272: ressource invitation: organizer receives no mail if he is no attendee
1425      */
1426     public function testResourceNotificationForNonAttendeeOrganizer()
1427     {
1428         $this->testResourceNotificationForGrantedUsers(/* $userIsAttendee = */ false);
1429     }
1430
1431     /**
1432      * testResourceNotificationMuteForEditors
1433      *
1434      * @see 0011312: Make resource notification handling and default status configurable
1435      */
1436     public function testResourceNotificationMuteForEditors()
1437     {
1438         Calendar_Config::getInstance()->set(Calendar_Config::RESOURCE_MAIL_FOR_EDITORS, true);
1439         $this->testResourceNotification(/* $suppress_notification = */ true);
1440         $this->testResourceNotificationForGrantedUsers(/* $userIsAttendee = */ false, /* $suppress_notification = */ true);
1441     }
1442     
1443     /**
1444      * testGroupInvitation
1445      */
1446     public function testGroupInvitation()
1447     {
1448         $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
1449         
1450         $event = $this->_getEvent(TRUE);
1451         
1452         $event->attendee = $this->_getAttendee();
1453         $event->attendee[1] = new Calendar_Model_Attender(array(
1454                 'user_id'   => $defaultUserGroup->getId(),
1455                 'user_type' => Calendar_Model_Attender::USERTYPE_GROUP,
1456                 'role'      => Calendar_Model_Attender::ROLE_REQUIRED
1457         ));
1458         
1459         self::flushMailer();
1460         $persistentEvent = $this->_eventController->create($event);
1461         $this->_assertMail('jsmith', NULL);
1462         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'invit');
1463         
1464         self::flushMailer();
1465          
1466         $persistentEvent = $this->_eventController->delete($persistentEvent);
1467         $this->_assertMail('jsmith', NULL);
1468         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'cancel');
1469     }
1470 }