0011460: group attendee notifications raise error
[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     /**
514      * CalDAV/Custom can have alarms with odd times
515      */
516     public function testAlarmRoundMinutes()
517     {
518         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
519         
520         $event = $this->_getEvent();
521         $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
522         $event->dtend = clone $event->dtstart;
523         $event->dtend->addMinute(30);
524         $event->attendee = $this->_getAttendee();
525         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
526             new Tinebase_Model_Alarm(array(
527                     'minutes_before' => 12.1
528             ), TRUE)
529         ));
530         
531         $persistentEvent = $this->_eventController->create($event);
532         
533         $this->assertEquals(12, $persistentEvent->alarms->getFirstRecord()->getOption('minutes_before'));
534     }
535     
536     public function testSkipPastAlarm()
537     {
538         $event = $this->_getEvent();
539         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
540             new Tinebase_Model_Alarm(array(
541                     'minutes_before' => 30
542             ), TRUE)
543         ));
544         
545         $persistentEvent = $this->_eventController->create($event);
546         self::flushMailer();
547         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
548         $this->_assertMail('sclever');
549     }
550     
551     /**
552      * testParallelAlarmTrigger
553      * 
554      * @see 0004878: improve asyncJob fencing
555      */
556     public function testParallelAlarmTrigger()
557     {
558         $this->_testNeedsTransaction();
559         
560         try {
561             $this->_emailTestClass = new Felamimail_Controller_MessageTest();
562             $this->_emailTestClass->setup();
563         } catch (Exception $e) {
564             Tinebase_Exception::log($e);
565             $this->markTestIncomplete('email not available.');
566         }
567         
568         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
569         self::flushMailer();
570         $this->_getAlarmMails(TRUE);
571         
572         $event = $this->_getEvent();
573         $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
574         $event->dtend = clone $event->dtstart;
575         $event->dtend->addMinute(30);
576         $event->attendee = $this->_getAttendee();
577         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
578             new Tinebase_Model_Alarm(array(
579                     'minutes_before' => 30
580             ), TRUE)
581         ));
582         
583         $persistentEvent = $this->_eventController->create($event);
584         try {
585             Tinebase_AsyncJobTest::triggerAsyncEvents();
586         } catch (Exception $e) {
587             Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
588                 . ' Something strange happened and the async jobs did not complete ... maybe the test system is not configured correctly for this: ' . $e);
589             $this->markTestIncomplete($e->getMessage());
590         }
591         
592         $result = $this->_getAlarmMails(TRUE);
593         $this->assertEquals(1, count($result), 'expected exactly 1 alarm mail, got: ' . print_r($result->toArray(), TRUE));
594     }
595     
596     /**
597      * testRecuringAlarm
598      */
599     public function testRecuringAlarm()
600     {
601         $event = $this->_getEvent();
602         $event->attendee = $this->_getPersonaAttendee('pwulf');
603         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
604         
605         // lets flush mailer so next flushing ist faster!
606         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
607         self::flushMailer();
608         
609         // make sure next occurence contains now
610         // next occurance now+29min 
611         $event->dtstart = Tinebase_DateTime::now()->subDay(1)->addMinute(28);
612         $event->dtend = clone $event->dtstart;
613         $event->dtend->addMinute(30);
614         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
615         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
616             new Tinebase_Model_Alarm(array(
617                 'minutes_before' => 30
618             ), TRUE)
619         ));
620         
621         $persistentEvent = $this->_eventController->create($event);
622         
623         // assert alarm
624         self::flushMailer();
625         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
626         $assertString = ' at ' . Tinebase_DateTime::now()->format('M j');
627         $this->_assertMail('pwulf', $assertString);
628
629         // check adjusted alarm time
630         $loadedEvent = $this->_eventController->get($persistentEvent->getId());
631         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
632         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
633         
634         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now()->addDay(1), 'alarmtime is not adjusted');
635         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $loadedEvent->alarms->getFirstRecord()->sent_status, 'alarmtime is set to pending');
636         
637         // update series @see #7430: Calendar sends too much alarms for recurring events
638         $this->_eventController->update($loadedEvent);
639         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
640         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
641         
642         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now()->addDay(1), 'alarmtime is wrong');
643     }
644     
645     /**
646      * if an event with an alarm gets an exception instance, also the alarm gets an exception instance
647      * @see #6328
648      */
649     public function testRecuringAlarmException()
650     {
651         $event = $this->_getEvent();
652         $event->attendee = $this->_getPersonaAttendee('pwulf');
653         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
654         
655         $event->dtstart = Tinebase_DateTime::now()->subDay(1)->addMinute(15);
656         $event->dtend = clone $event->dtstart;
657         $event->dtend->addMinute(30);
658         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
659         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
660                 new Tinebase_Model_Alarm(array(
661                         'minutes_before' => 30
662                 ), TRUE)
663         ));
664         
665         $persistentEvent = $this->_eventController->create($event);
666         
667         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
668         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $persistentEvent->dtstart, Tinebase_DateTime::now()->addDay(1));
669         $exceptionEvent = $this->_eventController->createRecurException($recurSet->getFirstRecord());
670         
671         // assert one alarm only
672         self::flushMailer();
673         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
674         $assertString = ' at ' . Tinebase_DateTime::now()->format('M j');
675         $this->_assertMail('pwulf', $assertString);
676         
677         // check series
678         $loadedEvent = $this->_eventController->get($persistentEvent->getId());
679         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
680         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
681         
682         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now(), 'alarmtime of series is not adjusted');
683         
684         // check exception
685         $recurid = $exceptionEvent->alarms->getFirstRecord()->getOption('recurid');
686         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
687         
688         $this->assertTrue($nextAlarmEventStart < Tinebase_DateTime::now()->addHour(1), 'alarmtime of exception is not adjusted');
689         
690         // update exception @see #7430: Calendar sends too much alarms for recurring events
691         $exceptionEvent = $this->_eventController->update($exceptionEvent);
692         $recurid = $exceptionEvent->alarms->getFirstRecord()->getOption('recurid');
693         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
694         
695         $this->assertTrue($nextAlarmEventStart < Tinebase_DateTime::now()->addHour(1), 'alarmtime of exception is wrong');
696     }
697     
698     /**
699      * testRecuringAlarmCustomDate
700      */
701     public function testRecuringAlarmCustomDate()
702     {
703         $event = $this->_getEvent();
704         $event->attendee = $this->_getPersonaAttendee('pwulf');
705         $event->organizer = $this->_getPersonasContacts('pwulf')->getId();
706         
707         $event->dtstart = Tinebase_DateTime::now()->addWeek(1)->addMinute(15);
708         $event->dtend = clone $event->dtstart;
709         $event->dtend->addMinute(30);
710         $event->rrule = 'FREQ=YEARLY;INTERVAL=1;BYDAY=2TH;BYMONTH=12';
711         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
712             new Tinebase_Model_Alarm(array(
713                 'minutes_before' => Tinebase_Model_Alarm::OPTION_CUSTOM,
714                 // NOTE: user means one week and 30 mins before
715                 'alarm_time'     => Tinebase_DateTime::now()->subMinute(15)
716             ), TRUE)
717         ));
718         
719         $persistentEvent = $this->_eventController->create($event);
720         
721         // assert one alarm only
722         self::flushMailer();
723         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
724         $assertString = ' at ' . Tinebase_DateTime::now()->addWeek(1)->format('M j');
725         $this->_assertMail('pwulf', $assertString);
726         
727         // check adjusted alarm time
728         $loadedEvent = $this->_eventController->get($persistentEvent->getId());
729         $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
730         $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
731         
732         $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now(), 'alarmtime of series is not adjusted');
733     }
734     
735     /**
736      * test alarm inspection from 24.03.2012 -> 25.03.2012
737      */
738     public function testAdoptAlarmDSTBoundary()
739     {
740         $event = $this->_getEvent();
741         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
742         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
743             new Tinebase_Model_Alarm(array(
744                 'minutes_before' => 30
745             ), TRUE)
746         ));
747         $persistentEvent = $this->_eventController->create($event);
748         
749         // prepare alarm for last non DST instance
750         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
751         $from = new Tinebase_DateTime('2012-03-24 00:00:00');
752         $until = new Tinebase_DateTime('2012-03-24 23:59:59');
753         $recurSet =Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
754         
755         $alarm = $persistentEvent->alarms->getFirstRecord();
756         $alarm->setOption('recurid', $recurSet[0]->recurid);
757         Tinebase_Alarm::getInstance()->update($alarm);
758         
759         $loadedBaseEvent = $this->_eventController->get($persistentEvent->getId());
760         $alarm = $loadedBaseEvent->alarms->getFirstRecord();
761         $this->assertEquals('2012-03-24', substr($alarm->getOption('recurid'), -19, -9), 'precondition failed');
762         
763         // adopt alarm
764         $this->_eventController->adoptAlarmTime($loadedBaseEvent, $alarm, 'instance');
765         $this->assertEquals('2012-03-25', substr($alarm->getOption('recurid'), -19, -9), 'alarm adoption failed');
766     }
767     
768     /**
769      * test alarm inspection from 24.03.2012 -> 25.03.2012
770      */
771     public function testAdoptAlarmDSTBoundaryWithSkipping()
772     {
773         $event = new Calendar_Model_Event(array(
774             'summary'      => 'Cleanup',
775             'dtstart'      => '2012-01-31 07:30:00',
776             'dtend'        => '2012-01-31 10:30:00',
777             'container_id' => $this->_getTestCalendar()->getId(),
778             'uid'          => Calendar_Model_Event::generateUID(),
779             'rrule'        => 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=TU',
780             'originator_tz'=> 'Europe/Berlin',
781         ));
782         
783         $alarm = new Tinebase_Model_Alarm(array(
784             'model'        => 'Calendar_Model_Event',
785             'alarm_time'   => '2012-03-26 06:30:00',
786             'minutes_before' => 1440,
787             'options'      => '{"minutes_before":1440,"recurid":"a7c55ce09cea9aec4ac37d9d72789183b12cad7c-2012-03-27 06:30:00","custom":false}',
788         ));
789         
790         $this->_eventController->adoptAlarmTime($event, $alarm, 'instance');
791         
792         $this->assertEquals('2012-04-02 06:30:00', $alarm->alarm_time->toString());
793     }
794     
795     public function testAlarmSkipDeclined()
796     {
797         $event = $this->_getEvent();
798         $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
799         $event->organizer = $this->_getPersonasContacts('sclever')->getId();
800         
801         $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
802         $event->dtend = clone $event->dtstart;
803         $event->dtend->addMinute(30);
804         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
805             new Tinebase_Model_Alarm(array(
806                 'minutes_before' => 30
807             ), TRUE)
808         ));
809         
810         $persistentEvent = $this->_eventController->create($event);
811         $sclever = Calendar_Model_Attender::getAttendee($persistentEvent->attendee, $event->attendee[0]);
812         $sclever->status = Calendar_Model_Attender::STATUS_DECLINED;
813         $this->_eventController->attenderStatusUpdate($persistentEvent, $sclever, $sclever->status_authkey);
814         
815         self::flushMailer();
816         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
817         $this->_assertMail('pwulf', 'Alarm');
818         $this->assertEquals(1, count(self::getMessages()));
819     }
820     
821     /**
822      * Trying to reproduce a fatal error but won't work yet
823      */
824     public function testAlarmWithoutOrganizer()
825     {
826         $calInstance = Addressbook_Controller_Contact::getInstance();
827         $newContactData = array(
828             'n_given'           => 'foo',
829             'n_family'          => 'PHPUNIT',
830             'email'             => 'foo@tine20.org',
831             'tel_cell_private'  => '+49TELCELLPRIVATE',
832         );
833         $newContact = $calInstance->create(new Addressbook_Model_Contact($newContactData));
834         
835         $event = $this->_getEvent();
836         $event->attendee = $this->_createAttender($newContact->getId());
837         $event->organizer = $newContact->getId();
838         
839         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
840             new Tinebase_Model_Alarm(array(
841                 'minutes_before' => 30
842             ), TRUE)
843         ));
844         $persistentEvent = $this->_eventController->create($event);
845         
846         $calInstance->delete(array($newContact->getId()));
847         self::flushMailer();
848         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
849         $this->assertEquals(0, count(self::getMessages()));
850     }
851     
852     /**
853      * testRecuringAlarmAfterSeriesEnds
854      * 
855      * @see 0008386: alarm is sent for recur series that is already over
856      */
857     public function testRecuringAlarmAfterSeriesEnds()
858     {
859         $this->_recurAlarmTestHelper();
860     }
861     
862     /**
863      * helper for recurring alarm tests
864      * 
865      * @param boolean $allFollowing
866      * @param integer $alarmMinutesBefore
867      */
868     protected function _recurAlarmTestHelper($allFollowing = TRUE, $alarmMinutesBefore = 60)
869     {
870         $event = $this->_getEvent();
871         
872         // lets flush mailer so next flushing ist faster!
873         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
874         self::flushMailer();
875         
876         // make sure next occurence contains now
877         $event->dtstart = Tinebase_DateTime::now()->subDay(2)->addHour(1);
878         $event->dtend = clone $event->dtstart;
879         $event->dtend->addMinute(60);
880         $event->rrule = 'FREQ=DAILY;INTERVAL=1';
881         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
882             new Tinebase_Model_Alarm(array(
883                 'minutes_before' => $alarmMinutesBefore
884             ), TRUE)
885         ));
886         
887         // check alarm
888         $persistentEvent = $this->_eventController->create($event);
889         $this->assertEquals(1, count($persistentEvent->alarms));
890         $alarm = $persistentEvent->alarms->getFirstRecord();
891         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $alarm->sent_status);
892         $persistentDtstart = clone $persistentEvent->dtstart;
893         $this->assertEquals($persistentDtstart->subMinute($alarmMinutesBefore), $alarm->alarm_time, print_r($alarm->toArray(), TRUE));
894         
895         // delete all following
896         $from = $event->dtstart;
897         $until = $event->dtend->addDay(3);
898         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
899         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
900         $recurEvent = $recurSet[1]; // today
901         $persistentEvent = $this->_eventController->createRecurException($recurEvent, TRUE, $allFollowing);
902         
903         $baseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
904         if ($allFollowing) {
905             $until = $recurSet[0]->dtstart->getClone()
906             ->setTimezone($baseEvent->originator_tz)
907             ->setTime(23,59,59)
908             ->setTimezone('UTC');
909             
910             $this->assertEquals('FREQ=DAILY;INTERVAL=1;UNTIL=' . $until->toString(), (string) $baseEvent->rrule, 'rrule mismatch');
911             $this->assertEquals(1, count($baseEvent->alarms));
912             $this->assertEquals('Nothing to send, series is over', $baseEvent->alarms->getFirstRecord()->sent_message,
913                 'alarm adoption failed: ' . print_r($baseEvent->alarms->getFirstRecord()->toArray(), TRUE));
914         } else {
915             $this->assertEquals('FREQ=DAILY;INTERVAL=1', (string) $baseEvent->rrule);
916             $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $baseEvent->alarms->getFirstRecord()->sent_status);
917             $this->assertEquals('', $baseEvent->alarms->getFirstRecord()->sent_message);
918         }
919         
920         // assert no alarm
921         self::flushMailer();
922         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
923         $messages = self::getMessages();
924         $this->assertEquals(0, count($messages), 'no alarm message should be sent: ' . print_r($messages, TRUE));
925     }
926     
927     /**
928      * testRecuringAlarmWithRecurException
929      * 
930      * @see 0008386: alarm is sent for recur series that is already over
931      */
932     public function testRecuringAlarmWithRecurException()
933     {
934         $this->_recurAlarmTestHelper(FALSE);
935     }
936
937     /**
938      * testRecuringAlarmWithRecurException120MinutesBefore
939      * 
940      * @see 0008386: alarm is sent for recur series that is already over
941      */
942     public function testRecuringAlarmWithRecurException120MinutesBefore()
943     {
944         $this->_recurAlarmTestHelper(FALSE, 120);
945     }
946
947     /**
948      * testRecuringAlarmWithRecurExceptionMoved
949      * 
950      * @see 0008386: alarm is sent for recur series that is already over
951      */
952     public function testRecuringAlarmWithRecurExceptionMoved()
953     {
954         $event = $this->_getEvent();
955         
956         // lets flush mailer so next flushing ist faster!
957         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
958         self::flushMailer();
959         
960         // make sure next occurence contains now
961         $event->dtstart = Tinebase_DateTime::now()->subWeek(2)->addDay(1);
962         $event->dtend = clone $event->dtstart;
963         $event->dtend->addMinute(60);
964         $event->rrule = 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=' . array_search($event->dtstart->format('w'), Calendar_Model_Rrule::$WEEKDAY_DIGIT_MAP);
965         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
966             new Tinebase_Model_Alarm(array(
967                 'minutes_before' => 1440
968             ), TRUE)
969         ));
970         
971         $persistentEvent = $this->_eventController->create($event);
972         
973         // adopt alarm time (previous alarms have been sent already)
974         $alarm = $persistentEvent->alarms->getFirstRecord();
975         $alarm->alarm_time->addWeek(2);
976         Tinebase_Alarm::getInstance()->update($alarm);
977         
978         // move next occurrence
979         $from = $event->dtstart;
980         $until = $event->dtend->addWeek(3);
981         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
982         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
983         $recurEvent = $recurSet[1]; // tomorrow
984         
985         $recurEvent->dtstart->addDay(5);
986         $recurEvent->dtend = clone $recurEvent->dtstart;
987         $recurEvent->dtend->addMinute(60);
988         $persistentEvent = $this->_eventController->createRecurException($recurEvent);
989         
990         $baseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
991         $alarm = $baseEvent->alarms->getFirstRecord();
992         $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $alarm->sent_status);
993         
994         // assert no alarm
995         sleep(1);
996         self::flushMailer();
997         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
998         $messages = self::getMessages();
999         $this->assertEquals(0, count($messages), 'no alarm message should be sent: ' . print_r($messages, TRUE));
1000     }
1001
1002     /**
1003      * testRecuringAlarmWithThisAndFutureSplit
1004      * 
1005      * @see 0008386: alarm is sent for recur series that is already over
1006      */
1007     public function testRecuringAlarmWithThisAndFutureSplit()
1008     {
1009         $this->markTestSkipped('@see 0009816: fix failing testRecuringAlarmWithThisAndFutureSplit test');
1010         
1011         $event = $this->_getEvent();
1012         
1013         // lets flush mailer so next flushing ist faster!
1014         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
1015         self::flushMailer();
1016         
1017         // make sure next occurence contains now
1018         $event->dtstart = Tinebase_DateTime::now()->subMonth(1)->addDay(1)->subHour(2);
1019         $event->dtend = clone $event->dtstart;
1020         $event->dtend->addMinute(60);
1021         $event->rrule = 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=' . $event->dtstart->format('d');
1022         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
1023             new Tinebase_Model_Alarm(array(
1024                 'minutes_before' => 2880
1025             ), TRUE)
1026         ));
1027         
1028         $persistentEvent = $this->_eventController->create($event);
1029         
1030         // make sure, next alarm is for next month's event
1031         Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
1032         self::flushMailer();
1033         
1034         // split THISANDFUTURE, alarm of old series should be set to SUCCESS because it no longer should be sent
1035         $from = $event->dtstart;
1036         $until = $event->dtend->addMonth(2);
1037         $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
1038         $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
1039         $recurEvent = (count($recurSet) > 1) ? $recurSet[1] : $recurSet[0]; // next month
1040         $recurEvent->summary = 'split series';
1041         $newPersistentEvent = $this->_eventController->createRecurException($recurEvent, FALSE, TRUE);
1042         
1043         // check alarms
1044         $oldSeriesAlarm = Tinebase_Alarm::getInstance()
1045             ->getAlarmsOfRecord('Calendar_Model_Event', $persistentEvent->getId())
1046             ->getFirstRecord();
1047         $this->assertEquals(Tinebase_Model_Alarm::STATUS_SUCCESS, $oldSeriesAlarm->sent_status,
1048             'no pending alarm should exist for old series: ' . print_r($oldSeriesAlarm->toArray(), TRUE));
1049     }
1050     
1051     /**
1052      * put an exception event created by "remind" option of alarm in iCal
1053      */
1054     public function testPutEventExceptionAlarmReminder()
1055     {
1056         $event = $this->_createRecurringCalDavEvent();
1057         $messages = self::getMessages();
1058         $this->assertEquals(1, count($messages), 'one invitation should be send to sclever');
1059         $this->_assertMail('sclever', 'invitation');
1060     
1061         // create alarm reminder/snooze exception
1062         Calendar_Controller_EventNotificationsTests::flushMailer();
1063         $vcalendar = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/../Import/files/apple_ical_remind_part2.ics');
1064         $event->put($vcalendar);
1065     
1066         // assert no reschedule mail
1067         $messages = Calendar_Controller_EventNotificationsTests::getMessages();
1068         $this->assertEquals(0, count($messages), 'no reschedule mails should be send for implicit exception');
1069     }
1070     
1071     /**
1072      * createRecurringCalDavEvent
1073      * 
1074      * @return Calendar_Frontend_WebDAV_Event
1075      */
1076     protected function _createRecurringCalDavEvent()
1077     {
1078         $_SERVER['HTTP_USER_AGENT'] = 'Mac_OS_X/10.9 (13A603) CalendarAgent/174';
1079         
1080         self::flushMailer();
1081         $vcalendar = Calendar_Frontend_WebDAV_EventTest::getVCalendar(dirname(__FILE__) . '/../Import/files/apple_ical_remind_part1.ics');
1082         $id = Tinebase_Record_Abstract::generateUID();
1083         $event = Calendar_Frontend_WebDAV_Event::create($this->_getTestCalendar(), "$id.ics", $vcalendar);
1084         
1085         return $event;
1086     }
1087     
1088     /**
1089      * testNotificationPeriodConfig
1090      * 
1091      * @see 0010048: config for notifications for past events
1092      */
1093     public function testNotificationPeriodConfig()
1094     {
1095         Calendar_Config::getInstance()->set(Calendar_Config::MAX_NOTIFICATION_PERIOD_FROM, /* last week */ 1);
1096         $event = $this->_createRecurringCalDavEvent();
1097         $messages = self::getMessages();
1098         $this->assertEquals(0, count($messages), 'no invitation should be send to sclever');
1099     }
1100     
1101     /**
1102      * get test alarm emails
1103      * 
1104      * @param boolean $deleteThem
1105      * @return Tinebase_Record_RecordSet
1106      */
1107     protected function _getAlarmMails($deleteThem = FALSE)
1108     {
1109         // search and assert alarm mail
1110         $folder = $this->_emailTestClass->getFolder('INBOX');
1111         $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10, 1);
1112         $i = 0;
1113         while ($folder->cache_status != Felamimail_Model_Folder::CACHE_STATUS_COMPLETE && $i < 10) {
1114             $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10);
1115             $i++;
1116         }
1117         $account = Felamimail_Controller_Account::getInstance()->search()->getFirstRecord();
1118         $filter = new Felamimail_Model_MessageFilter(array(
1119             array('field' => 'folder_id',  'operator' => 'equals',     'value' => $folder->getId()),
1120             array('field' => 'account_id', 'operator' => 'equals',     'value' => $account->getId()),
1121             array('field' => 'subject',    'operator' => 'startswith', 'value' => 'Alarm for event "Wakeup" at'),
1122         ));
1123         
1124         $result = Felamimail_Controller_Message::getInstance()->search($filter);
1125         
1126         if ($deleteThem) {
1127             Felamimail_Controller_Message_Move::getInstance()->moveMessages($filter, Felamimail_Model_Folder::FOLDER_TRASH);
1128         }
1129         
1130         return $result;
1131     }
1132     
1133     /**
1134      * testAdoptAlarmDSTBoundaryAllDayEvent
1135      * 
1136      * @see 0009820: Infinite loop in adoptAlarmTime / computeNextOccurrence (DST Boundary)
1137      */
1138     public function testAdoptAlarmDSTBoundaryAllDayEvent()
1139     {
1140         $event = $this->_getEvent();
1141         $event->is_all_day_event = 1;
1142         $event->dtstart = new Tinebase_DateTime('2014-03-03 23:00:00');
1143         $event->dtend = new Tinebase_DateTime('2014-03-04 22:59:59');
1144         $event->originator_tz = 'Europe/Berlin';
1145         $event->rrule = 'FREQ=DAILY';
1146         $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
1147             new Tinebase_Model_Alarm(array(
1148                 'minutes_before' => 15,
1149             ), TRUE)
1150         ));
1151         
1152         $savedEvent = Calendar_Controller_Event::getInstance()->create($event);
1153         
1154         $alarm = $savedEvent->alarms->getFirstRecord();
1155         $alarm->sent_time = new Tinebase_DateTime('2014-03-29 22:46:01');
1156         $alarm->alarm_time = new Tinebase_DateTime('2014-03-29 22:45:00');
1157         $alarm->setOption('recurid', $savedEvent->uid . '-2014-03-29 23:00:00');
1158         Tinebase_Alarm::getInstance()->update($alarm);
1159         $alarm = $this->_eventController->get($savedEvent->getId())->alarms->getFirstRecord();
1160         
1161         Calendar_Controller_Event::getInstance()->adoptAlarmTime($savedEvent, $alarm, 'instance');
1162         
1163         $this->assertEquals('2014-03-30 21:45:00', $alarm->alarm_time->toString());
1164     }
1165     
1166     /**
1167      * checks if mail for persona got send
1168      * 
1169      * @param string $_personas
1170      * @param string $_assertString
1171      * @return void
1172      * 
1173      * @see #6800: add message-id to notification mails
1174      */
1175     protected function _assertMail($_personas, $_assertString = NULL, $_location = 'subject')
1176     {
1177         $messages = self::getMessages();
1178         
1179         foreach (explode(',', $_personas) as $personaName) {
1180             $mailsForPersona = array();
1181             $otherRecipients = array();
1182             $personaEmail = strstr($personaName, '@') ? 
1183                 $personaName : 
1184                 $this->_getPersona(trim($personaName))->accountEmailAddress;
1185             
1186             foreach ($messages as $message) {
1187                 if (Tinebase_Helper::array_value(0, $message->getRecipients()) == $personaEmail) {
1188                     array_push($mailsForPersona, $message);
1189                 } else {
1190                     array_push($otherRecipients, $message->getRecipients());
1191                 }
1192             }
1193             
1194             if (! $_assertString) {
1195                 $this->assertEquals(0, count($mailsForPersona), 'No mail should be send for '. $personaName);
1196             } else {
1197                 $this->assertEquals(1, count($mailsForPersona), 'One mail should be send for '. $personaName . ' other recipients: ' . print_r($otherRecipients, true));
1198                 $this->assertEquals('UTF-8', $mailsForPersona[0]->getCharset());
1199                 
1200                 switch ($_location) {
1201                     case 'subject':
1202                         $subject = $mailsForPersona[0]->getSubject();
1203                         $this->assertTrue(FALSE !== strpos($subject, $_assertString), 'Mail subject for ' . $personaName . ' should contain "' . $_assertString . '" but '. $subject . ' is given');
1204                         break;
1205                         
1206                     case 'body':
1207                         $bodyPart = $mailsForPersona[0]->getBodyText(FALSE);
1208                         
1209                         // so odd!
1210                         $s = fopen('php://temp','r+');
1211                         fputs($s, $bodyPart->getContent());
1212                         rewind($s);
1213                         $bodyPartStream = new Zend_Mime_Part($s);
1214                         $bodyPartStream->encoding = $bodyPart->encoding;
1215                         $bodyText = $bodyPartStream->getDecodedContent();
1216                         
1217                         if (Tinebase_Core::isLogLevel(Zend_Log::DEBUG)) Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__ 
1218                             . ' body text: ' . $bodyText);
1219                         
1220                         $this->assertContains($_assertString, $bodyText);
1221                         break;
1222                         
1223                     default:
1224                         throw new Exception('no such location '. $_location);
1225                         break;
1226                 }
1227                 
1228                 $headers = $mailsForPersona[0]->getHeaders();
1229                 $this->assertTrue(isset($headers['Message-Id']), 'message-id header not found');
1230                 $this->assertContains('@' . php_uname('n'), $headers['Message-Id'][0], 'hostname not in message-id');
1231             }
1232         }
1233     }
1234     
1235     /**
1236      * get attendee
1237      * 
1238      * @param string $_personas
1239      * @return Tinebase_Record_RecordSet
1240      */
1241     protected function _getPersonaAttendee($_personas)
1242     {
1243         $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender');
1244         foreach (explode(',', $_personas) as $personaName) {
1245             $attendee->addRecord($this->_createAttender($this->_getPersonasContacts(trim($personaName))->getId()));
1246         }
1247         
1248         return $attendee;
1249     }
1250     
1251     /**
1252      * setup preferences for personas
1253      * 
1254      * jsmith   -> no updates
1255      * pwulf    -> on invitaion/cancelation
1256      * sclever  -> on reschedules
1257      * jmblack  -> on updates except answers
1258      * rwright  -> even on ansers
1259      * 
1260      * @return void
1261      */
1262     protected function _setupPreferences()
1263     {
1264         // set notification levels
1265         $calPreferences = Tinebase_Core::getPreference('Calendar');
1266         $calPreferences->setValueForUser(
1267             Calendar_Preference::NOTIFICATION_LEVEL, 
1268             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_NONE,
1269             $this->_getPersona('jsmith')->getId(), TRUE
1270         );
1271         $calPreferences->setValueForUser(
1272             Calendar_Preference::NOTIFICATION_LEVEL, 
1273             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_INVITE_CANCEL,
1274             $this->_getPersona('pwulf')->getId(), TRUE
1275         );
1276         $calPreferences->setValueForUser(
1277             Calendar_Preference::NOTIFICATION_LEVEL, 
1278             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_EVENT_RESCHEDULE,
1279             $this->_getPersona('sclever')->getId(), TRUE
1280         );
1281         $calPreferences->setValueForUser(
1282             Calendar_Preference::NOTIFICATION_LEVEL, 
1283             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_EVENT_UPDATE,
1284             $this->_getPersona('jmcblack')->getId(), TRUE
1285         );
1286         $calPreferences->setValueForUser(
1287             Calendar_Preference::NOTIFICATION_LEVEL, 
1288             Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE,
1289             $this->_getPersona('rwright')->getId(), TRUE
1290         );
1291         
1292         // set all languages to en
1293         $preferences = Tinebase_Core::getPreference('Tinebase');
1294         foreach ($this->_getPersonas() as $name => $account) {
1295             $preferences->setValueForUser(Tinebase_Preference::LOCALE, 'en', $account->getId(), TRUE);
1296         }
1297     }
1298     
1299     /**
1300      * testResourceNotification
1301      * 
1302      * checks if notification mail is sent to configured mail address of a resource
1303      * 
1304      * @see 0009954: resource manager and email handling
1305      */
1306     public function testResourceNotification()
1307     {
1308         // create resource with email address of unittest user
1309         $resource = $this->_getResource();
1310         $resource->email = Tinebase_Core::getUser()->accountEmailAddress;
1311         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
1312         
1313         // create event with this resource as attender
1314         $event = $this->_getEvent(/* now = */ true);
1315         $event->attendee->addRecord($this->_createAttender($persistentResource->getId(), Calendar_Model_Attender::USERTYPE_RESOURCE));
1316
1317         self::flushMailer();
1318         $persistentEvent = $this->_eventController->create($event);
1319         
1320         $this->assertEquals(3, count($persistentEvent->attendee));
1321
1322         $messages = self::getMessages();
1323         
1324         $this->assertEquals(2, count($messages), 'two mails should be send to current user (resource + attender)');
1325     }
1326
1327     /**
1328      * Enable by a preference which sends mails to every user who got permissions to edit the resource
1329      */
1330     public function testResourceNotificationForGrantedUsers()
1331     {
1332         // Enable feature, disabled by default!
1333         Calendar_Config::getInstance()->set(Calendar_Config::RESOURCE_MAIL_FOR_EDITORS, true);
1334
1335         $resource = $this->_getResource();
1336         $resource->email = Tinebase_Core::getUser()->accountEmailAddress;
1337         $persistentResource = Calendar_Controller_Resource::getInstance()->create($resource);
1338
1339         $event = $this->_getEvent(/*now = */ true);
1340         $event->attendee->addRecord($this->_createAttender($persistentResource->getId(), Calendar_Model_Attender::USERTYPE_RESOURCE));
1341         $grants = Tinebase_Container::getInstance()->getGrantsOfContainer($resource->container_id);
1342
1343         $newGrants = array(
1344                 'account_id' => $this->_personas['sclever']->getId(),
1345                 'account_type' => 'user',
1346                 Tinebase_Model_Grants::GRANT_READ => true,
1347                 Tinebase_Model_Grants::GRANT_EDIT => true
1348             );
1349
1350         Tinebase_Container::getInstance()->setGrants($resource->container_id, new Tinebase_Record_RecordSet('Tinebase_Model_Grants', array_merge(array($newGrants), $grants->toArray())), TRUE);
1351
1352         self::flushMailer();
1353
1354         $persistentEvent = $this->_eventController->create($event);
1355
1356         $messages = self::getMessages();
1357
1358         Tinebase_Container::getInstance()->setGrants($resource->container_id, $grants);
1359
1360         $this->assertContains('Meeting Room (Required, No response)', print_r($messages, true));
1361         $this->assertEquals(4, count($messages), 'four mails should be send to current user (resource + attender + everybody whos allowed to edit this resource)');
1362         $this->assertEquals(3, count($persistentEvent->attendee));
1363     }
1364     
1365     /**
1366      * testGroupInvitation
1367      */
1368     public function testGroupInvitation()
1369     {
1370         $defaultUserGroup = Tinebase_Group::getInstance()->getDefaultGroup();
1371         
1372         $event = $this->_getEvent(TRUE);
1373         
1374         $event->attendee = $this->_getAttendee();
1375         $event->attendee[1] = new Calendar_Model_Attender(array(
1376                 'user_id'   => $defaultUserGroup->getId(),
1377                 'user_type' => Calendar_Model_Attender::USERTYPE_GROUP,
1378                 'role'      => Calendar_Model_Attender::ROLE_REQUIRED
1379         ));
1380         
1381         self::flushMailer();
1382         $persistentEvent = $this->_eventController->create($event);
1383         $this->_assertMail('jsmith', NULL);
1384         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'invit');
1385         
1386         self::flushMailer();
1387          
1388         $persistentEvent = $this->_eventController->delete($persistentEvent);
1389         $this->_assertMail('jsmith', NULL);
1390         $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'cancel');
1391     }
1392 }