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