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