3 * Tine 2.0 - http://www.tine20.org
6 * @license http://www.gnu.org/licenses/agpl.html AGPL Version 3
7 * @copyright Copyright (c) 2009-2013 Metaways Infosystems GmbH (http://www.metaways.de)
8 * @author Cornelius Weiss <c.weiss@metaways.de>
14 require_once dirname(dirname(dirname(__FILE__))) . DIRECTORY_SEPARATOR . 'TestHelper.php';
17 * Test class for Calendar_Controller_EventNotifications
21 class Calendar_Controller_EventNotificationsTests extends Calendar_TestCase
24 * @var Calendar_Controller_Event controller unter test
26 protected $_eventController;
29 * @var Calendar_Controller_EventNotifications controller unter test
31 protected $_notificationController;
34 * @var Zend_Mail_Transport_Array
36 protected static $_mailer = NULL;
39 * @var Tinebase_Model_Container
41 protected $_testCalendar;
46 * @var Felamimail_Controller_MessageTest
48 protected $_emailTestClass;
52 * @see tests/tine20/Calendar/Calendar_TestCase::setUp()
54 public function setUp()
58 $smtpConfig = Tinebase_Config::getInstance()->get(Tinebase_Config::SMTP, new Tinebase_Config_Struct())->toArray();
59 if (empty($smtpConfig)) {
60 $this->markTestSkipped('No SMTP config found: this is needed to send notifications.');
63 $this->_eventController = Calendar_Controller_Event::getInstance();
64 $this->_notificationController = Calendar_Controller_EventNotifications::getInstance();
66 $this->_setupPreferences();
70 * Tears down the fixture
71 * This method is called after a test is executed.
75 public function tearDown()
79 if ($this->_emailTestClass instanceof Felamimail_Controller_MessageTest) {
80 $this->_emailTestClass->tearDown();
87 public function testInvitation()
89 $event = $this->_getEvent(TRUE);
90 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
93 $persistentEvent = $this->_eventController->create($event);
94 $this->_assertMail('jsmith', NULL);
95 $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'invit');
98 $persistentEvent = $this->_eventController->delete($persistentEvent);
99 $this->_assertMail('jsmith', NULL);
100 $this->_assertMail('pwulf, sclever, jmcblack, rwright', 'cancel');
104 * testInvitationWithAttachment
106 * @see 0008592: append event file attachments to invitation mail
108 public function testInvitationWithAttachment()
110 $event = $this->_getEvent(TRUE);
111 $event->attendee = $this->_getPersonaAttendee('pwulf');
113 $tempFileBackend = new Tinebase_TempFile();
114 $tempFile = $tempFileBackend->createTempFile(dirname(dirname(dirname(__FILE__))) . '/Filemanager/files/test.txt');
115 $event->attachments = array(array('tempFile' => array('id' => $tempFile->getId())));
118 $persistentEvent = $this->_eventController->create($event);
120 $messages = self::getMessages();
122 $this->assertEquals(1, count($messages));
123 $parts = $messages[0]->getParts();
124 $this->assertEquals(2, count($parts));
125 $fileAttachment = $parts[1];
126 $this->assertEquals('text/plain; name="=?utf-8?Q?tempfile.tmp?="', $fileAttachment->type);
128 // @todo assert attachment content (this seems to not work with array mailer, maybe we need a "real" email test here)
129 // $content = $fileAttachment->getDecodedContent();
130 // $this->assertEquals('test file content', $content);
136 public function testUpdateEmpty()
138 $event = $this->_getEvent();
139 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
140 $persistentEvent = $this->_eventController->create($event);
144 $updatedEvent = $this->_eventController->update($persistentEvent);
145 $this->_assertMail('jsmith, pwulf, sclever, jmcblack, rwright', NULL);
149 * testUpdateChangeAttendee
151 public function testUpdateChangeAttendee()
153 $event = $this->_getEvent(TRUE);
154 $event->attendee = $this->_getPersonaAttendee('pwulf, jmcblack, rwright');
155 $persistentEvent = $this->_eventController->create($event);
157 $persistentEvent->attendee->merge($this->_getPersonaAttendee('jsmith, sclever'));
158 $persistentEvent->attendee->removeRecord(
159 $persistentEvent->attendee->find('user_id', $this->_personasContacts['pwulf']->getId())
161 $persistentEvent->attendee->find('user_id', $this->_personasContacts['rwright']->getId())->status =
162 Calendar_Model_Attender::STATUS_ACCEPTED;
163 $persistentEvent->attendee->find('user_id', $this->_personasContacts['jmcblack']->getId())->status =
164 Calendar_Model_Attender::STATUS_DECLINED;
167 $updatedEvent = $this->_eventController->update($persistentEvent);
168 $this->_assertMail('jsmith, jmcblack', NULL);
169 $this->_assertMail('sclever', 'invit');
170 $this->_assertMail('pwulf', 'cancel');
171 $this->_assertMail('rwright', 'Attendee');
175 * testUpdateReschedule
177 public function testUpdateReschedule()
179 $event = $this->_getEvent(TRUE);
180 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
181 $persistentEvent = $this->_eventController->create($event);
183 $persistentEvent->summary = 'reschedule notification has precedence over normal update';
184 $persistentEvent->dtstart->addHour(1);
185 $persistentEvent->dtend->addHour(1);
188 $updatedEvent = $this->_eventController->update($persistentEvent);
189 $this->_assertMail('jsmith, pwulf', NULL);
190 $this->_assertMail('sclever, jmcblack, rwright', 'reschedul');
196 public function testUpdateDetails()
198 $event = $this->_getEvent(TRUE);
199 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
200 $persistentEvent = $this->_eventController->create($event);
202 $persistentEvent->summary = 'detail update notification has precedence over attendee update';
203 $persistentEvent->url = 'http://somedetail.com';
204 $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_ACCEPTED;
207 $updatedEvent = $this->_eventController->update($persistentEvent);
208 $this->_assertMail('jsmith, pwulf, sclever', NULL);
209 $this->_assertMail('jmcblack, rwright', 'update');
213 * testUpdateAttendeeStatus
215 public function testUpdateAttendeeStatus()
217 $event = $this->_getEvent(TRUE);
218 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf, sclever, jmcblack, rwright');
219 $persistentEvent = $this->_eventController->create($event);
221 $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
224 $updatedEvent = $this->_eventController->update($persistentEvent);
225 $this->_assertMail('jsmith, pwulf, sclever, jmcblack', NULL);
226 $this->_assertMail('rwright', 'decline');
230 * testOrganizerNotificationSupress
232 public function testOrganizerNotificationSupress()
234 $event = $this->_getEvent();
235 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf');
236 $event->organizer = $this->_personasContacts['jsmith']->getId();
237 $persistentEvent = $this->_eventController->create($event);
239 $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
242 $updatedEvent = $this->_eventController->update($persistentEvent);
243 $this->_assertMail('jsmith, pwulf', NULL);
247 * testOrganizerNotificationSend
249 public function testOrganizerNotificationSend()
251 $event = $this->_getEvent(TRUE);
252 $event->attendee = $this->_getPersonaAttendee('jsmith, pwulf');
253 $event->organizer = $this->_personasContacts['pwulf']->getId();
254 $persistentEvent = $this->_eventController->create($event);
256 $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
259 $updatedEvent = $this->_eventController->update($persistentEvent);
260 $this->_assertMail('jsmith', NULL);
261 $this->_assertMail('pwulf', 'decline');
265 * testNotificationToNonAccounts
267 public function testNotificationToNonAccounts()
269 $event = $this->_getEvent(TRUE);
270 $event->attendee = $this->_getPersonaAttendee('pwulf');
271 $event->organizer = $this->_personasContacts['pwulf']->getId();
273 // add nonaccount attender
274 $nonAccountEmail = 'externer@example.org';
275 $nonAccountAttender = Addressbook_Controller_Contact::getInstance()->create(new Addressbook_Model_Contact(array(
276 'n_family' => 'externer',
277 'email' => $nonAccountEmail,
279 $event->attendee->addRecord($this->_createAttender($nonAccountAttender->getId()));
281 $persistentEvent = $this->_eventController->create($event);
284 $persistentEvent->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
285 new Tinebase_Model_Alarm(array(
286 'minutes_before' => 30
289 $updatedEvent = $this->_eventController->update($persistentEvent);
293 $persistentEvent->attendee[1]->status = Calendar_Model_Attender::STATUS_DECLINED;
294 $updatedEvent = $this->_eventController->update($persistentEvent);
296 // make sure messages are sent if queue is activated
297 if (isset(Tinebase_Core::getConfig()->actionqueue)) {
298 Tinebase_ActionQueue::getInstance()->processQueue();
301 // check mailer messages
302 $foundNonAccountMessage = FALSE;
303 $foundPWulfMessage = FALSE;
304 foreach(self::getMailer()->getMessages() as $message) {
305 if (in_array($nonAccountEmail, $message->getRecipients())) {
306 $foundNonAccountMessage = TRUE;
308 if (in_array($this->_personas['pwulf']->accountEmailAddress, $message->getRecipients())) {
309 $foundPWulfMessage = TRUE;
313 $this->assertTrue($foundNonAccountMessage, 'notification has not been sent to non-account');
314 $this->assertTrue($foundPWulfMessage, 'notfication for pwulf not found');
318 * testRecuringExceptions
320 public function testRecuringExceptions()
322 $from = new Tinebase_DateTime('2012-03-01 00:00:00');
323 $until = new Tinebase_DateTime('2012-03-31 23:59:59');
325 $event = new Calendar_Model_Event(array(
326 'summary' => 'Some Daily Event',
327 'dtstart' => '2012-03-14 09:00:00',
328 'dtend' => '2012-03-14 10:00:00',
329 'rrule' => 'FREQ=DAILY;INTERVAL=1',
330 'container_id' => $this->_testCalendar->getId(),
331 'attendee' => $this->_getPersonaAttendee('jmcblack'),
335 $persistentEvent = $this->_eventController->create($event);
336 $this->_assertMail('jmcblack', 'Recurrance rule: Daily', 'body');
338 $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
339 $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
343 $this->_eventController->createRecurException($recurSet[4], TRUE, FALSE); //2012-03-19
344 $this->_assertMail('jmcblack', 'cancel');
348 $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($recurSet[5]);
349 $recurSet[5]->last_modified_time = $updatedBaseEvent->last_modified_time;
350 $recurSet[5]->summary = 'exceptional summary';
351 $this->_eventController->createRecurException($recurSet[5], FALSE, FALSE); //2012-03-20
352 $this->_assertMail('jmcblack', 'This is an event series exception', 'body');
353 $this->_assertMail('jmcblack', 'update');
355 // reschedule instance
357 $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($recurSet[6]);
358 $recurSet[6]->last_modified_time = $updatedBaseEvent->last_modified_time;
359 $recurSet[6]->dtstart->addHour(2);
360 $recurSet[6]->dtend->addHour(2);
361 $this->_eventController->createRecurException($recurSet[6], FALSE, FALSE); //2012-03-21
362 $this->_assertMail('jmcblack', 'reschedule');
364 // cancle thisandfuture
365 // @TODO check RANGE in ics
366 // @TODO add RANGE text to message
368 $updatedBaseEvent = $this->_eventController->getRecurBaseEvent($recurSet[16]);
369 $recurSet[16]->last_modified_time = $updatedBaseEvent->last_modified_time;
370 $this->_eventController->createRecurException($recurSet[16], TRUE, TRUE); //2012-03-31
371 $this->_assertMail('jmcblack', 'cancel');
373 // update thisandfuture
375 // reschedule thisandfuture
379 public function testAttendeeAlarmSkip()
381 $event = $this->_getEvent();
382 $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
383 $event->organizer = $this->_personasContacts['sclever']->getId();
385 $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
386 $event->dtend = clone $event->dtstart;
387 $event->dtend->addMinute(30);
388 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
389 new Tinebase_Model_Alarm(array(
390 'minutes_before' => 30
395 $event->alarms->setOption('skip', array(
397 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
398 'user_id' => $this->_personasContacts['pwulf']->getId(),
402 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
403 $persistentEvent = $this->_eventController->create($event);
406 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
407 $this->_assertMail('sclever', 'Alarm for event');
408 $this->_assertMail('pwulf');
411 public function testAttendeeAlarmOnly()
413 $event = $this->_getEvent();
414 $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
415 $event->organizer = $this->_personasContacts['sclever']->getId();
417 $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
418 $event->dtend = clone $event->dtstart;
419 $event->dtend->addMinute(30);
420 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
421 new Tinebase_Model_Alarm(array(
422 'minutes_before' => 30
425 $event->alarms->setOption('attendee', array(
426 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
427 'user_id' => $this->_personasContacts['pwulf']->getId()
430 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
431 $persistentEvent = $this->_eventController->create($event);
434 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
435 $this->_assertMail('pwulf', 'Alarm for event');
436 $this->_assertMail('sclever');
440 public function testAlarm()
442 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
444 $event = $this->_getEvent();
445 $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
446 $event->dtend = clone $event->dtstart;
447 $event->dtend->addMinute(30);
448 $event->attendee = $this->_getAttendee();
449 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
450 new Tinebase_Model_Alarm(array(
451 'minutes_before' => 30
455 $persistentEvent = $this->_eventController->create($event);
456 Calendar_Model_Attender::getOwnAttender($persistentEvent->attendee)->status = Calendar_Model_Attender::STATUS_DECLINED;
458 // hack to get declined attendee
459 $this->_eventController->sendNotifications(FALSE);
460 $updatedEvent = $this->_eventController->update($persistentEvent);
461 $this->_eventController->sendNotifications(TRUE);
464 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
465 $this->_assertMail('sclever', 'Alarm');
466 $this->assertEquals(1, count(self::getMessages()));
470 * CalDAV/Custom can have alarms with odd times
472 public function testAlarmRoundMinutes()
474 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
476 $event = $this->_getEvent();
477 $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
478 $event->dtend = clone $event->dtstart;
479 $event->dtend->addMinute(30);
480 $event->attendee = $this->_getAttendee();
481 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
482 new Tinebase_Model_Alarm(array(
483 'minutes_before' => 12.1
487 $persistentEvent = $this->_eventController->create($event);
489 $this->assertEquals(12, $persistentEvent->alarms->getFirstRecord()->getOption('minutes_before'));
492 public function testSkipPastAlarm()
494 $event = $this->_getEvent();
495 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
496 new Tinebase_Model_Alarm(array(
497 'minutes_before' => 30
501 $persistentEvent = $this->_eventController->create($event);
503 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
504 $this->_assertMail('sclever');
508 * testParallelAlarmTrigger
510 * @see 0004878: improve asyncJob fencing
512 public function testParallelAlarmTrigger()
514 $this->markTestSkipped('TODO fixme: 0009116: fix Calendar_Controller_EventNotificationsTests::testParallelAlarmTrigger');
516 $this->_testNeedsTransaction();
519 $this->_emailTestClass = new Felamimail_Controller_MessageTest();
520 $this->_emailTestClass->setup();
521 } catch (Exception $e) {
522 $this->markTestIncomplete('email not available.');
525 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
527 $this->_getAlarmMails(TRUE);
529 $event = $this->_getEvent();
530 $event->dtstart = Tinebase_DateTime::now()->addMinute(15);
531 $event->dtend = clone $event->dtstart;
532 $event->dtend->addMinute(30);
533 $event->attendee = $this->_getAttendee();
534 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
535 new Tinebase_Model_Alarm(array(
536 'minutes_before' => 30
540 $persistentEvent = $this->_eventController->create($event);
542 Tinebase_AsyncJobTest::triggerAsyncEvents();
543 } catch (Exception $e) {
544 Tinebase_Core::getLogger()->debug(__METHOD__ . '::' . __LINE__
545 . ' Something strange happened and the async jobs did not complete ... maybe the test system is not configured correctly for this: ' . $e);
546 $this->markTestIncomplete($e->getMessage());
549 $result = $this->_getAlarmMails(TRUE);
550 $this->assertEquals(1, count($result), 'expected exactly 1 alarm mail, got: ' . print_r($result->toArray(), TRUE));
556 public function testRecuringAlarm()
558 $event = $this->_getEvent();
559 $event->attendee = $this->_getPersonaAttendee('pwulf');
560 $event->organizer = $this->_personasContacts['pwulf']->getId();
562 // lets flush mailer so next flushing ist faster!
563 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
566 // make sure next occurence contains now
567 // next occurance now+29min
568 $event->dtstart = Tinebase_DateTime::now()->subDay(1)->addMinute(28);
569 $event->dtend = clone $event->dtstart;
570 $event->dtend->addMinute(30);
571 $event->rrule = 'FREQ=DAILY;INTERVAL=1';
572 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
573 new Tinebase_Model_Alarm(array(
574 'minutes_before' => 30
578 $persistentEvent = $this->_eventController->create($event);
582 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
583 $assertString = ' at ' . Tinebase_DateTime::now()->format('M j');
584 $this->_assertMail('pwulf', $assertString);
586 // check adjusted alarm time
587 $loadedEvent = $this->_eventController->get($persistentEvent->getId());
588 $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
589 $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
591 $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now()->addDay(1), 'alarmtime is not adjusted');
592 $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $loadedEvent->alarms->getFirstRecord()->sent_status, 'alarmtime is set to pending');
594 // update series @see #7430: Calendar sends too much alarms for recurring events
595 $this->_eventController->update($loadedEvent);
596 $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
597 $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
599 $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now()->addDay(1), 'alarmtime is wrong');
603 * if an event with an alarm gets an exception instance, also the alarm gets an exception instance
606 public function testRecuringAlarmException()
608 $event = $this->_getEvent();
609 $event->attendee = $this->_getPersonaAttendee('pwulf');
610 $event->organizer = $this->_personasContacts['pwulf']->getId();
612 $event->dtstart = Tinebase_DateTime::now()->subDay(1)->addMinute(15);
613 $event->dtend = clone $event->dtstart;
614 $event->dtend->addMinute(30);
615 $event->rrule = 'FREQ=DAILY;INTERVAL=1';
616 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
617 new Tinebase_Model_Alarm(array(
618 'minutes_before' => 30
622 $persistentEvent = $this->_eventController->create($event);
624 $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
625 $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $persistentEvent->dtstart, Tinebase_DateTime::now()->addDay(1));
626 $exceptionEvent = $this->_eventController->createRecurException($recurSet->getFirstRecord());
628 // assert one alarm only
630 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
631 $assertString = ' at ' . Tinebase_DateTime::now()->format('M j');
632 $this->_assertMail('pwulf', $assertString);
635 $loadedEvent = $this->_eventController->get($persistentEvent->getId());
636 $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
637 $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
639 $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now(), 'alarmtime of series is not adjusted');
642 $recurid = $exceptionEvent->alarms->getFirstRecord()->getOption('recurid');
643 $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
645 $this->assertTrue($nextAlarmEventStart < Tinebase_DateTime::now()->addHour(1), 'alarmtime of exception is not adjusted');
647 // update exception @see #7430: Calendar sends too much alarms for recurring events
648 $exceptionEvent = $this->_eventController->update($exceptionEvent);
649 $recurid = $exceptionEvent->alarms->getFirstRecord()->getOption('recurid');
650 $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
652 $this->assertTrue($nextAlarmEventStart < Tinebase_DateTime::now()->addHour(1), 'alarmtime of exception is wrong');
655 public function testRecuringAlarmCustomDate()
657 $event = $this->_getEvent();
658 $event->attendee = $this->_getPersonaAttendee('pwulf');
659 $event->organizer = $this->_personasContacts['pwulf']->getId();
661 $event->dtstart = Tinebase_DateTime::now()->addWeek(1)->addMinute(15);
662 $event->dtend = clone $event->dtstart;
663 $event->dtend->addMinute(30);
664 $event->rrule = 'FREQ=YEARLY;INTERVAL=1;BYDAY=2TH;BYMONTH=12';
665 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
666 new Tinebase_Model_Alarm(array(
667 'minutes_before' => Tinebase_Model_Alarm::OPTION_CUSTOM,
668 // NOTE: user means one week and 30 mins before
669 'alarm_time' => Tinebase_DateTime::now()->subMinute(15)
673 $persistentEvent = $this->_eventController->create($event);
675 // assert one alarm only
677 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
678 $assertString = ' at ' . Tinebase_DateTime::now()->addWeek(1)->format('M j');
679 $this->_assertMail('pwulf', $assertString);
681 // check adjusted alarm time
682 $loadedEvent = $this->_eventController->get($persistentEvent->getId());
683 $recurid = $loadedEvent->alarms->getFirstRecord()->getOption('recurid');
684 $nextAlarmEventStart = new Tinebase_DateTime(substr($recurid, -19));
686 $this->assertTrue($nextAlarmEventStart > Tinebase_DateTime::now(), 'alarmtime of series is not adjusted');
690 * test alarm inspection from 24.03.2012 -> 25.03.2012
692 public function testAdoptAlarmDSTBoundary()
694 $event = $this->_getEvent();
695 $event->rrule = 'FREQ=DAILY;INTERVAL=1';
696 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
697 new Tinebase_Model_Alarm(array(
698 'minutes_before' => 30
701 $persistentEvent = $this->_eventController->create($event);
703 // prepare alarm for last non DST instance
704 $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
705 $from = new Tinebase_DateTime('2012-03-24 00:00:00');
706 $until = new Tinebase_DateTime('2012-03-24 23:59:59');
707 $recurSet =Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
709 $alarm = $persistentEvent->alarms->getFirstRecord();
710 $alarm->setOption('recurid', $recurSet[0]->recurid);
711 Tinebase_Alarm::getInstance()->update($alarm);
713 $loadedBaseEvent = $this->_eventController->get($persistentEvent->getId());
714 $alarm = $loadedBaseEvent->alarms->getFirstRecord();
715 $this->assertEquals('2012-03-24', substr($alarm->getOption('recurid'), -19, -9), 'precondition failed');
718 $this->_eventController->adoptAlarmTime($loadedBaseEvent, $alarm, 'instance');
719 $this->assertEquals('2012-03-25', substr($alarm->getOption('recurid'), -19, -9), 'alarm adoption failed');
723 * test alarm inspection from 24.03.2012 -> 25.03.2012
725 public function testAdoptAlarmDSTBoundaryWithSkipping()
727 $event = new Calendar_Model_Event(array(
728 'summary' => 'Cleanup',
729 'dtstart' => '2012-01-31 07:30:00',
730 'dtend' => '2012-01-31 10:30:00',
731 'container_id' => $this->_testCalendar->getId(),
732 'uid' => Calendar_Model_Event::generateUID(),
733 'rrule' => 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=TU',
734 'originator_tz'=> 'Europe/Berlin',
737 $alarm = new Tinebase_Model_Alarm(array(
738 'model' => 'Calendar_Model_Event',
739 'alarm_time' => '2012-03-26 06:30:00',
740 'minutes_before' => 1440,
741 'options' => '{"minutes_before":1440,"recurid":"a7c55ce09cea9aec4ac37d9d72789183b12cad7c-2012-03-27 06:30:00","custom":false}',
744 $this->_eventController->adoptAlarmTime($event, $alarm, 'instance');
746 $this->assertEquals('2012-04-02 06:30:00', $alarm->alarm_time->toString());
749 public function testAlarmSkipDeclined()
751 $event = $this->_getEvent();
752 $event->attendee = $this->_getPersonaAttendee('sclever, pwulf');
753 $event->organizer = $this->_personasContacts['sclever']->getId();
755 $event->dtstart = Tinebase_DateTime::now()->addMinute(25);
756 $event->dtend = clone $event->dtstart;
757 $event->dtend->addMinute(30);
758 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
759 new Tinebase_Model_Alarm(array(
760 'minutes_before' => 30
764 $persistentEvent = $this->_eventController->create($event);
765 $sclever = Calendar_Model_Attender::getAttendee($persistentEvent->attendee, $event->attendee[0]);
766 $sclever->status = Calendar_Model_Attender::STATUS_DECLINED;
767 $this->_eventController->attenderStatusUpdate($persistentEvent, $sclever, $sclever->status_authkey);
770 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
771 $this->_assertMail('pwulf', 'Alarm');
772 $this->assertEquals(1, count(self::getMessages()));
776 * testRecuringAlarmAfterSeriesEnds
778 * @see 0008386: alarm is sent for recur series that is already over
780 public function testRecuringAlarmAfterSeriesEnds()
782 $this->_recurAlarmTestHelper();
786 * helper for recurring alarm tests
788 * @param boolean $allFollowing
789 * @param integer $alarmMinutesBefore
791 protected function _recurAlarmTestHelper($allFollowing = TRUE, $alarmMinutesBefore = 60)
793 $event = $this->_getEvent();
795 // lets flush mailer so next flushing ist faster!
796 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
799 // make sure next occurence contains now
800 $event->dtstart = Tinebase_DateTime::now()->subDay(2)->addHour(1);
801 $event->dtend = clone $event->dtstart;
802 $event->dtend->addMinute(60);
803 $event->rrule = 'FREQ=DAILY;INTERVAL=1';
804 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
805 new Tinebase_Model_Alarm(array(
806 'minutes_before' => $alarmMinutesBefore
811 $persistentEvent = $this->_eventController->create($event);
812 $this->assertEquals(1, count($persistentEvent->alarms));
813 $alarm = $persistentEvent->alarms->getFirstRecord();
814 $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $alarm->sent_status);
815 $persistentDtstart = clone $persistentEvent->dtstart;
816 $this->assertEquals($persistentDtstart->subMinute($alarmMinutesBefore), $alarm->alarm_time, print_r($alarm->toArray(), TRUE));
818 // delete all following
819 $from = $event->dtstart;
820 $until = $event->dtend->addDay(3);
821 $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
822 $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
823 $recurEvent = $recurSet[1]; // today
824 $persistentEvent = $this->_eventController->createRecurException($recurEvent, TRUE, $allFollowing);
826 $baseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
828 $this->assertEquals('FREQ=DAILY;INTERVAL=1;UNTIL=' . $recurEvent->dtstart->subHour(1)->toString(), (string) $baseEvent->rrule, 'rrule mismatch');
829 $this->assertEquals(1, count($baseEvent->alarms));
830 $this->assertEquals('Nothing to send, series is over', $baseEvent->alarms->getFirstRecord()->sent_message,
831 'alarm adoption failed: ' . print_r($baseEvent->alarms->getFirstRecord()->toArray(), TRUE));
833 $this->assertEquals('FREQ=DAILY;INTERVAL=1', (string) $baseEvent->rrule);
834 $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $baseEvent->alarms->getFirstRecord()->sent_status);
835 $this->assertEquals('', $baseEvent->alarms->getFirstRecord()->sent_message);
840 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
841 $messages = self::getMessages();
842 $this->assertEquals(0, count($messages), 'no alarm message should be sent: ' . print_r($messages, TRUE));
846 * testRecuringAlarmWithRecurException
848 * @see 0008386: alarm is sent for recur series that is already over
850 public function testRecuringAlarmWithRecurException()
852 $this->_recurAlarmTestHelper(FALSE);
856 * testRecuringAlarmWithRecurException120MinutesBefore
858 * @see 0008386: alarm is sent for recur series that is already over
860 public function testRecuringAlarmWithRecurException120MinutesBefore()
862 $this->_recurAlarmTestHelper(FALSE, 120);
866 * testRecuringAlarmWithRecurExceptionMoved
868 * @see 0008386: alarm is sent for recur series that is already over
870 public function testRecuringAlarmWithRecurExceptionMoved()
872 $event = $this->_getEvent();
874 // lets flush mailer so next flushing ist faster!
875 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
878 // make sure next occurence contains now
879 $event->dtstart = Tinebase_DateTime::now()->subWeek(2)->addDay(1);
880 $event->dtend = clone $event->dtstart;
881 $event->dtend->addMinute(60);
882 $event->rrule = 'FREQ=WEEKLY;INTERVAL=1;WKST=MO;BYDAY=' . array_search($event->dtstart->format('w'), Calendar_Model_Rrule::$WEEKDAY_DIGIT_MAP);
883 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
884 new Tinebase_Model_Alarm(array(
885 'minutes_before' => 1440
889 $persistentEvent = $this->_eventController->create($event);
891 // adopt alarm time (previous alarms have been sent already)
892 $alarm = $persistentEvent->alarms->getFirstRecord();
893 $alarm->alarm_time->addWeek(2);
894 Tinebase_Alarm::getInstance()->update($alarm);
896 // move next occurrence
897 $from = $event->dtstart;
898 $until = $event->dtend->addWeek(3);
899 $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
900 $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
901 $recurEvent = $recurSet[1]; // tomorrow
903 $recurEvent->dtstart->addDay(5);
904 $recurEvent->dtend = clone $recurEvent->dtstart;
905 $recurEvent->dtend->addMinute(60);
906 $persistentEvent = $this->_eventController->createRecurException($recurEvent);
908 $baseEvent = $this->_eventController->getRecurBaseEvent($persistentEvent);
909 $alarm = $baseEvent->alarms->getFirstRecord();
910 $this->assertEquals(Tinebase_Model_Alarm::STATUS_PENDING, $alarm->sent_status);
915 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
916 $messages = self::getMessages();
917 $this->assertEquals(0, count($messages), 'no alarm message should be sent: ' . print_r($messages, TRUE));
921 * testRecuringAlarmWithThisAndFutureSplit
923 * @see 0008386: alarm is sent for recur series that is already over
925 public function testRecuringAlarmWithThisAndFutureSplit()
927 $event = $this->_getEvent();
929 // lets flush mailer so next flushing ist faster!
930 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
933 // make sure next occurence contains now
934 $event->dtstart = Tinebase_DateTime::now()->subMonth(1)->addDay(1)->subHour(2);
935 $event->dtend = clone $event->dtstart;
936 $event->dtend->addMinute(60);
937 $event->rrule = 'FREQ=MONTHLY;INTERVAL=1;BYMONTHDAY=' . $event->dtstart->format('d');
938 $event->alarms = new Tinebase_Record_RecordSet('Tinebase_Model_Alarm', array(
939 new Tinebase_Model_Alarm(array(
940 'minutes_before' => 2880
944 $persistentEvent = $this->_eventController->create($event);
946 // make sure, next alarm is for next month's event
947 Tinebase_Alarm::getInstance()->sendPendingAlarms("Tinebase_Event_Async_Minutely");
950 // split THISANDFUTURE, alarm of old series should be set to SUCCESS because it no longer should be sent
951 $from = $event->dtstart;
952 $until = $event->dtend->addMonth(2);
953 $exceptions = new Tinebase_Record_RecordSet('Calendar_Model_Event');
954 $recurSet = Calendar_Model_Rrule::computeRecurrenceSet($persistentEvent, $exceptions, $from, $until);
955 $recurEvent = (count($recurSet) > 1) ? $recurSet[1] : $recurSet[0]; // next month
956 $recurEvent->summary = 'split series';
957 $newPersistentEvent = $this->_eventController->createRecurException($recurEvent, FALSE, TRUE);
960 $oldSeriesAlarm = Tinebase_Alarm::getInstance()
961 ->getAlarmsOfRecord('Calendar_Model_Event', $persistentEvent->getId())
963 $this->assertEquals(Tinebase_Model_Alarm::STATUS_SUCCESS, $oldSeriesAlarm->sent_status,
964 'no pending alarm should exist for old series: ' . print_r($oldSeriesAlarm->toArray(), TRUE));
968 * get test alarm emails
970 * @param boolean $deleteThem
971 * @return Tinebase_Record_RecordSet
973 protected function _getAlarmMails($deleteThem = FALSE)
975 // search and assert alarm mail
976 $folder = $this->_emailTestClass->getFolder('INBOX');
977 $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10, 1);
979 while ($folder->cache_status != Felamimail_Model_Folder::CACHE_STATUS_COMPLETE && $i < 10) {
980 $folder = Felamimail_Controller_Cache_Message::getInstance()->updateCache($folder, 10);
983 $account = Felamimail_Controller_Account::getInstance()->search()->getFirstRecord();
984 $filter = new Felamimail_Model_MessageFilter(array(
985 array('field' => 'folder_id', 'operator' => 'equals', 'value' => $folder->getId()),
986 array('field' => 'account_id', 'operator' => 'equals', 'value' => $account->getId()),
987 array('field' => 'subject', 'operator' => 'startswith', 'value' => 'Alarm for event "Wakeup" at'),
990 $result = Felamimail_Controller_Message::getInstance()->search($filter);
993 Felamimail_Controller_Message_Move::getInstance()->moveMessages($filter, Felamimail_Model_Folder::FOLDER_TRASH);
1004 public static function getMessages()
1006 // make sure messages are sent if queue is activated
1007 if (isset(Tinebase_Core::getConfig()->actionqueue)) {
1008 Tinebase_ActionQueue::getInstance()->processQueue(100);
1011 return self::getMailer()->getMessages();
1017 * @return Zend_Mail_Transport_Abstract
1019 public static function getMailer()
1021 if (! self::$_mailer) {
1022 self::$_mailer = Tinebase_Smtp::getDefaultTransport();
1025 return self::$_mailer;
1029 * flush mailer (send all remaining mails first)
1031 public static function flushMailer()
1033 // make sure all messages are sent if queue is activated
1034 if (isset(Tinebase_Core::getConfig()->actionqueue)) {
1035 Tinebase_ActionQueue::getInstance()->processQueue(10000);
1038 self::getMailer()->flush();
1042 * checks if mail for persona got send
1044 * @param string $_personas
1045 * @param string $_assertString
1048 * @see #6800: add message-id to notification mails
1050 protected function _assertMail($_personas, $_assertString = NULL, $_location = 'subject')
1052 $messages = self::getMessages();
1054 foreach (explode(',', $_personas) as $personaName) {
1055 $mailsForPersona = array();
1056 $personaEmail = $this->_personas[trim($personaName)]->accountEmailAddress;
1058 foreach ($messages as $message) {
1059 if (array_value(0, $message->getRecipients()) == $personaEmail) {
1060 array_push($mailsForPersona, $message);
1064 if (! $_assertString) {
1065 $this->assertEquals(0, count($mailsForPersona), 'No mail should be send for '. $personaName);
1067 $this->assertEquals(1, count($mailsForPersona), 'One mail should be send for '. $personaName);
1068 $this->assertEquals('UTF-8', $mailsForPersona[0]->getCharset());
1070 switch ($_location) {
1072 $subject = $mailsForPersona[0]->getSubject();
1073 $this->assertTrue(FALSE !== strpos($subject, $_assertString), 'Mail subject for ' . $personaName . ' should contain "' . $_assertString . '" but '. $subject . ' is given');
1077 $bodyPart = $mailsForPersona[0]->getBodyText(FALSE);
1080 $s = fopen('php://temp','r+');
1081 fputs($s, $bodyPart->getContent());
1083 $bodyPartStream = new Zend_Mime_Part($s);
1084 $bodyPartStream->encoding = $bodyPart->encoding;
1085 $bodyText = $bodyPartStream->getDecodedContent();
1087 $this->assertContains($_assertString, $bodyText);
1091 throw new Exception('no such location '. $_location);
1095 $headers = $mailsForPersona[0]->getHeaders();
1096 $this->assertTrue(isset($headers['Message-Id']), 'message-id header not found');
1097 $this->assertContains('@' . php_uname('n'), $headers['Message-Id'][0], 'hostname not in message-id');
1105 * @param string $_personas
1106 * @return Tinebase_Record_RecordSet
1108 protected function _getPersonaAttendee($_personas)
1110 $attendee = new Tinebase_Record_RecordSet('Calendar_Model_Attender');
1111 foreach (explode(',', $_personas) as $personaName) {
1112 $attendee->addRecord($this->_createAttender($this->_personasContacts[trim($personaName)]->getId()));
1119 * create new attender
1121 * @param string $_userId
1122 * @return Calendar_Model_Attender
1124 protected function _createAttender($_userId)
1126 return new Calendar_Model_Attender(array(
1127 'user_id' => $_userId,
1128 'user_type' => Calendar_Model_Attender::USERTYPE_USER,
1129 'role' => Calendar_Model_Attender::ROLE_REQUIRED,
1130 'status_authkey' => Tinebase_Record_Abstract::generateUID(),
1135 * setup preferences for personas
1137 * jsmith -> no updates
1138 * pwulf -> on invitaion/cancelation
1139 * sclever -> on reschedules
1140 * jmblack -> on updates except answers
1141 * rwright -> even on ansers
1145 protected function _setupPreferences()
1147 // set notification levels
1148 $calPreferences = Tinebase_Core::getPreference('Calendar');
1149 $calPreferences->setValueForUser(
1150 Calendar_Preference::NOTIFICATION_LEVEL,
1151 Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_NONE,
1152 $this->_personas['jsmith']->getId(), TRUE
1154 $calPreferences->setValueForUser(
1155 Calendar_Preference::NOTIFICATION_LEVEL,
1156 Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_INVITE_CANCEL,
1157 $this->_personas['pwulf']->getId(), TRUE
1159 $calPreferences->setValueForUser(
1160 Calendar_Preference::NOTIFICATION_LEVEL,
1161 Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_EVENT_RESCHEDULE,
1162 $this->_personas['sclever']->getId(), TRUE
1164 $calPreferences->setValueForUser(
1165 Calendar_Preference::NOTIFICATION_LEVEL,
1166 Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_EVENT_UPDATE,
1167 $this->_personas['jmcblack']->getId(), TRUE
1169 $calPreferences->setValueForUser(
1170 Calendar_Preference::NOTIFICATION_LEVEL,
1171 Calendar_Controller_EventNotifications::NOTIFICATION_LEVEL_ATTENDEE_STATUS_UPDATE,
1172 $this->_personas['rwright']->getId(), TRUE
1175 // set all languages to en
1176 $preferences = Tinebase_Core::getPreference('Tinebase');
1177 foreach ($this->_personas as $name => $account) {
1178 $preferences->setValueForUser(Tinebase_Preference::LOCALE, 'en', $account->getId(), TRUE);