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