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