/**
  * test converting vcard from apple iCal to Calendar_Model_Event
  */
 public function testConvertToTine20Model()
 {
     $vcalendarStream = fopen(dirname(__FILE__) . '/../../../Import/files/apple_caldendar_mavericks_organizer_only.ics', 'r');
     $converter = Calendar_Convert_Event_VCalendar_Factory::factory(Calendar_Convert_Event_VCalendar_Factory::CLIENT_MACOSX, '10.9');
     $event = $converter->toTine20Model($vcalendarStream);
     // assert testuser is not attendee
     $this->assertEquals(1, $event->attendee->count(), 'there sould only be one attendee');
     $this->assertNotEquals($event->organizer, $event->attendee[0]->user_id, 'organizer should not attend');
     // assert alarm
     $this->assertEquals(1, $event->alarms->count(), 'there should be exactly one alarm');
     $this->assertFalse((bool) $event->alarms->getFirstRecord()->getOption('custom'), 'alarm should be duration alarm');
     $this->assertEquals(15, $event->alarms->getFirstRecord()->minutes_before, 'alarm should be 15 min. before');
     $this->assertEquals('2013-11-15 11:47:23', Calendar_Controller_Alarm::getAcknowledgeTime($event->alarms->getFirstRecord())->format(Tinebase_Record_Abstract::ISO8601LONG), 'ACKNOWLEDGED was not imported properly');
 }
 /**
  * converts an iTIP event to a tine20 event
  * 
  * @param Calendar_Model_Event $_event
  * @param Calendar_Model_Event $_currentEvent (not iTIP!)
  */
 protected function _fromiTIP($_event, $_currentEvent)
 {
     if (!$_event->rrule) {
         $_event->exdate = NULL;
     }
     if ($_event->exdate instanceof Tinebase_Record_RecordSet) {
         try {
             $currExdates = $this->_eventController->getRecurExceptions($_event, TRUE);
             $this->getAlarms($currExdates);
             $currClientExdates = $this->_eventController->getRecurExceptions($_event, TRUE, $this->getEventFilter());
             $this->getAlarms($currClientExdates);
         } catch (Tinebase_Exception_NotFound $e) {
             $currExdates = NULL;
             $currClientExdates = NULL;
         }
         foreach ($_event->exdate as $idx => $exdate) {
             try {
                 $this->_prepareException($_event, $exdate);
             } catch (Exception $e) {
             }
             $currExdate = $currExdates instanceof Tinebase_Record_RecordSet ? $currExdates->filter('recurid', $exdate->recurid)->getFirstRecord() : NULL;
             if ($exdate->is_deleted) {
                 // reset implicit filter fallouts and mark as don't touch (seq = -1)
                 $currClientExdate = $currClientExdates instanceof Tinebase_Record_RecordSet ? $currClientExdates->filter('recurid', $exdate->recurid)->getFirstRecord() : NULL;
                 if ($currClientExdate && $currClientExdate->is_deleted) {
                     $_event->exdate[$idx] = $currExdate;
                     $currExdate->seq = -1;
                     continue;
                 }
             }
             $this->_fromiTIP($exdate, $currExdate ? $currExdate : clone $_currentEvent);
         }
     }
     // assert organizer
     $_event->organizer = $_event->organizer ?: ($_currentEvent->organizer ?: $this->_calendarUser->user_id);
     $this->_addAttendeeWithoutEmail($_event, $_currentEvent);
     $CUAttendee = Calendar_Model_Attender::getAttendee($_event->attendee, $this->_calendarUser);
     $currentCUAttendee = Calendar_Model_Attender::getAttendee($_currentEvent->attendee, $this->_calendarUser);
     $isOrganizer = $_event->isOrganizer($this->_calendarUser);
     // remove perspective
     if ($CUAttendee && !$isOrganizer) {
         $CUAttendee->transp = $_event->transp;
         $_event->transp = $_currentEvent->transp ? $_currentEvent->transp : $_event->transp;
     }
     // apply changes to original alarms
     $_currentEvent->alarms = $_currentEvent->alarms instanceof Tinebase_Record_RecordSet ? $_currentEvent->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     $_event->alarms = $_event->alarms instanceof Tinebase_Record_RecordSet ? $_event->alarms : new Tinebase_Record_RecordSet('Tinebase_Model_Alarm');
     foreach ($_currentEvent->alarms as $currentAlarm) {
         if (Calendar_Model_Attender::isAlarmForAttendee($this->_calendarUser, $currentAlarm)) {
             $alarmUpdate = Calendar_Controller_Alarm::getMatchingAlarm($_event->alarms, $currentAlarm);
             if ($alarmUpdate) {
                 // we could map the alarm => save ack & snooze options
                 if ($dtAck = Calendar_Controller_Alarm::getAcknowledgeTime($alarmUpdate)) {
                     Calendar_Controller_Alarm::setAcknowledgeTime($currentAlarm, $dtAck, $this->getCalendarUser()->user_id);
                 }
                 if ($dtSnooze = Calendar_Controller_Alarm::getSnoozeTime($alarmUpdate)) {
                     Calendar_Controller_Alarm::setSnoozeTime($currentAlarm, $dtSnooze, $this->getCalendarUser()->user_id);
                 }
                 $_event->alarms->removeRecord($alarmUpdate);
             } else {
                 // alarm is to be skiped/deleted
                 if (!$currentAlarm->getOption('attendee')) {
                     Calendar_Controller_Alarm::skipAlarm($currentAlarm, $this->_calendarUser);
                 } else {
                     $_currentEvent->alarms->removeRecord($currentAlarm);
                 }
             }
         }
     }
     if (!$isOrganizer) {
         $_event->alarms->setOption('attendee', Calendar_Controller_Alarm::attendeeToOption($this->_calendarUser));
     }
     $_event->alarms->merge($_currentEvent->alarms);
     // assert organizer for personal calendars to be calendar owner
     if ($this->_currentEventFacadeContainer && $this->_currentEventFacadeContainer->getId() == $_event->container_id && $this->_currentEventFacadeContainer->type == Tinebase_Model_Container::TYPE_PERSONAL && !$_event->hasExternalOrganizer()) {
         $_event->organizer = $this->_calendarUser->user_id;
     }
     // in MS world only cal_user can do status updates
     if ($CUAttendee) {
         $CUAttendee->status_authkey = $currentCUAttendee ? $currentCUAttendee->status_authkey : NULL;
     }
 }
 /**
  * testAlarmAckInRecurException
  * 
  * @see 0009396: alarm_ack_time and alarm_snooze_time are not updated
  */
 public function testAlarmAckInRecurException()
 {
     $event = $this->testCreate();
     // save event as sclever to ack sclevers alarm
     Tinebase_Core::set(Tinebase_Core::USER, $this->_personas['sclever']);
     $exdateAlarms = $event->exdate->getFirstRecord()->alarms;
     $ackTime = Tinebase_DateTime::now();
     $scleverAlarm = new Tinebase_Model_Alarm(array('model' => 'Calendar_Model_Event', 'alarm_time' => $ackTime, 'minutes_before' => 90));
     $ackAlarm = Calendar_Controller_Alarm::getMatchingAlarm($exdateAlarms, $scleverAlarm);
     Calendar_Controller_Alarm::setAcknowledgeTime($ackAlarm, $ackTime);
     $updatedEvent = $this->_uit->update($event);
     $this->assertEquals(3, count($updatedEvent->alarms));
     $updatedAlarm = Calendar_Controller_Alarm::getMatchingAlarm($updatedEvent->exdate->getFirstRecord()->alarms, $scleverAlarm);
     $this->assertEquals($ackTime, Calendar_Controller_Alarm::getAcknowledgeTime($updatedAlarm));
     // check alarm ack client + ip
     $accessLog = Tinebase_Core::get(Tinebase_Core::USERACCESSLOG);
     $this->assertEquals($accessLog->ip, $updatedAlarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_IP), 'ip not found in options: ' . print_r($updatedAlarm->toArray(), true));
     $expectedClient = 'type: ' . $accessLog->clienttype . '|useragent: ' . $_SERVER['HTTP_USER_AGENT'];
     $this->assertEquals($expectedClient, $updatedAlarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_CLIENT), 'clienttype not found in options: ' . print_r($updatedAlarm->toArray(), true));
 }
 /**
  * print alarm acknowledgement report (when, ip, client, user, ...)
  * 
  * @param Zend_Console_Getopt $_opts
  */
 public function alarmAckReport(Zend_Console_Getopt $_opts)
 {
     $until = Tinebase_DateTime::now();
     $from = Tinebase_DateTime::now()->subDay(1);
     // get all events for today for current user
     $filter = new Calendar_Model_EventFilter(array(array('field' => 'attender', 'operator' => 'in', 'value' => array(array('user_type' => 'user', 'user_id' => Tinebase_Core::getUser()->contact_id))), array('field' => 'attender_status', 'operator' => 'not', 'value' => Calendar_Model_Attender::STATUS_DECLINED), array('field' => 'period', 'operator' => 'within', 'value' => array("from" => $from, "until" => $until))));
     $events = Calendar_Controller_Event::getInstance()->search($filter);
     Calendar_Model_Rrule::mergeAndRemoveNonMatchingRecurrences($events, $filter);
     Calendar_Controller_Event::getInstance()->getAlarms($events);
     echo "Reporting alarms for events of user " . Tinebase_Core::getUser()->accountLoginName . " (All times in UTC) from " . $from->toString() . ' until ' . $until->toString() . "...\n\n";
     // loop alarms and print alarm ack info
     foreach ($events as $event) {
         if (count($event->alarms) > 0) {
             $this->_printShortEvent($event);
             foreach ($event->alarms as $alarm) {
                 echo "  Alarm " . $alarm->alarm_time . "\n";
                 $ackTime = Calendar_Controller_Alarm::getAcknowledgeTime($alarm);
                 if (empty($ackTime)) {
                     echo "    not acknowledged!";
                 } else {
                     if (is_array($ackTime)) {
                         $ackTime = array_pop($ackTime);
                     }
                     echo "    acknowledged " . $ackTime->toString() . "\n    IP -> " . $alarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_IP) . "\n    Client ->" . $alarm->getOption(Tinebase_Model_Alarm::OPTION_ACK_CLIENT) . "\n";
                 }
                 echo "\n";
             }
             echo "\n";
         }
     }
 }
 /**
  * convert calendar event to Sabre\VObject\Component
  * 
  * @param  \Sabre\VObject\Component\VCalendar $vcalendar
  * @param  Calendar_Model_Event               $_event
  * @param  Calendar_Model_Event               $_mainEvent
  */
 protected function _convertCalendarModelEvent(\Sabre\VObject\Component\VCalendar $vcalendar, Calendar_Model_Event $_event, Calendar_Model_Event $_mainEvent = null)
 {
     // clone the event and change the timezone
     $event = clone $_event;
     $event->setTimezone($event->originator_tz);
     $lastModifiedDateTime = $_event->last_modified_time ? $_event->last_modified_time : $_event->creation_time;
     if (!$event->creation_time instanceof Tinebase_DateTime) {
         throw new Tinebase_Exception_Record_Validation('creation_time needed for conversion to Sabre\\VObject\\Component');
     }
     $vevent = $vcalendar->create('VEVENT', array('CREATED' => $_event->creation_time->getClone()->setTimezone('UTC'), 'LAST-MODIFIED' => $lastModifiedDateTime->getClone()->setTimezone('UTC'), 'DTSTAMP' => Tinebase_DateTime::now(), 'UID' => $event->uid));
     $vevent->add('SEQUENCE', $event->hasExternalOrganizer() ? $event->external_seq : $event->seq);
     if ($event->isRecurException()) {
         $originalDtStart = $_event->getOriginalDtStart()->setTimezone($_event->originator_tz);
         $recurrenceId = $vevent->add('RECURRENCE-ID', $originalDtStart);
         if ($_mainEvent && $_mainEvent->is_all_day_event == true) {
             $recurrenceId['VALUE'] = 'DATE';
         }
     }
     // dtstart and dtend
     $dtstart = $vevent->add('DTSTART', $_event->dtstart->getClone()->setTimezone($event->originator_tz));
     if ($event->is_all_day_event == true) {
         $dtstart['VALUE'] = 'DATE';
         // whole day events ends at 23:59:(00|59) in Tine 2.0 but 00:00 the next day in vcalendar
         $event->dtend->addSecond($event->dtend->get('s') == 59 ? 1 : 0);
         $event->dtend->addMinute($event->dtend->get('i') == 59 ? 1 : 0);
         $dtend = $vevent->add('DTEND', $event->dtend);
         $dtend['VALUE'] = 'DATE';
     } else {
         $dtend = $vevent->add('DTEND', $event->dtend);
     }
     // auto status for deleted events
     if ($event->is_deleted) {
         $event->status = Calendar_Model_Event::STATUS_CANCELED;
     }
     // event organizer
     if (!empty($event->organizer)) {
         $organizerContact = $event->resolveOrganizer();
         if ($organizerContact instanceof Addressbook_Model_Contact && !empty($organizerContact->email)) {
             $organizer = $vevent->add('ORGANIZER', 'mailto:' . $organizerContact->email, array('CN' => $organizerContact->n_fileas, 'EMAIL' => $organizerContact->email));
         }
     }
     $this->_addEventAttendee($vevent, $event);
     $optionalProperties = array('class', 'status', 'description', 'geo', 'location', 'priority', 'summary', 'transp', 'url');
     foreach ($optionalProperties as $property) {
         if (!empty($event->{$property})) {
             $vevent->add(strtoupper($property), $event->{$property});
         }
     }
     $class = $event->class == Calendar_Model_Event::CLASS_PUBLIC ? 'PUBLIC' : 'CONFIDENTIAL';
     $vcalendar->add('X-CALENDARSERVER-ACCESS', $class);
     $vevent->add('X-CALENDARSERVER-ACCESS', $class);
     // categories
     if (!isset($event->tags)) {
         $event->tags = Tinebase_Tags::getInstance()->getTagsOfRecord($event);
     }
     if (isset($event->tags) && count($event->tags) > 0) {
         $vevent->add('CATEGORIES', (array) $event->tags->name);
     }
     // repeating event properties
     if ($event->rrule) {
         if ($event->is_all_day_event == true) {
             $vevent->add('RRULE', preg_replace_callback('/UNTIL=([\\d :-]{19})(?=;?)/', function ($matches) {
                 $dtUntil = new Tinebase_DateTime($matches[1]);
                 $dtUntil->setTimezone((string) Tinebase_Core::getUserTimezone());
                 return 'UNTIL=' . $dtUntil->format('Ymd');
             }, $event->rrule));
         } else {
             $vevent->add('RRULE', preg_replace('/(UNTIL=)(\\d{4})-(\\d{2})-(\\d{2}) (\\d{2}):(\\d{2}):(\\d{2})/', '$1$2$3$4T$5$6$7Z', $event->rrule));
         }
         if ($event->exdate instanceof Tinebase_Record_RecordSet) {
             $event->exdate->addIndices(array('is_deleted'));
             $deletedEvents = $event->exdate->filter('is_deleted', true);
             foreach ($deletedEvents as $deletedEvent) {
                 $dateTime = $deletedEvent->getOriginalDtStart();
                 $exdate = $vevent->add('EXDATE');
                 if ($event->is_all_day_event == true) {
                     $dateTime->setTimezone($event->originator_tz);
                     $exdate['VALUE'] = 'DATE';
                 }
                 $exdate->setValue($dateTime);
             }
         }
     }
     $ownAttendee = Calendar_Model_Attender::getOwnAttender($event->attendee);
     if ($event->alarms instanceof Tinebase_Record_RecordSet) {
         $mozLastAck = NULL;
         $mozSnooze = NULL;
         foreach ($event->alarms as $alarm) {
             $valarm = $vcalendar->create('VALARM');
             $valarm->add('ACTION', 'DISPLAY');
             $valarm->add('DESCRIPTION', $event->summary);
             if ($dtack = Calendar_Controller_Alarm::getAcknowledgeTime($alarm)) {
                 $valarm->add('ACKNOWLEDGED', $dtack->getClone()->setTimezone('UTC')->format('Ymd\\THis\\Z'));
                 $mozLastAck = $dtack > $mozLastAck ? $dtack : $mozLastAck;
             }
             if ($dtsnooze = Calendar_Controller_Alarm::getSnoozeTime($alarm)) {
                 $mozSnooze = $dtsnooze > $mozSnooze ? $dtsnooze : $mozSnooze;
             }
             if (is_numeric($alarm->minutes_before)) {
                 if ($event->dtstart == $alarm->alarm_time) {
                     $periodString = 'PT0S';
                 } else {
                     $interval = $event->dtstart->diff($alarm->alarm_time);
                     $periodString = sprintf('%sP%s%s%s%s', $interval->format('%r'), $interval->format('%d') > 0 ? $interval->format('%dD') : null, $interval->format('%h') > 0 || $interval->format('%i') > 0 ? 'T' : null, $interval->format('%h') > 0 ? $interval->format('%hH') : null, $interval->format('%i') > 0 ? $interval->format('%iM') : null);
                 }
                 # TRIGGER;VALUE=DURATION:-PT1H15M
                 $trigger = $valarm->add('TRIGGER', $periodString);
                 $trigger['VALUE'] = "DURATION";
             } else {
                 # TRIGGER;VALUE=DATE-TIME:...
                 $trigger = $valarm->add('TRIGGER', $alarm->alarm_time->getClone()->setTimezone('UTC')->format('Ymd\\THis\\Z'));
                 $trigger['VALUE'] = "DATE-TIME";
             }
             $vevent->add($valarm);
         }
         if ($mozLastAck instanceof DateTime) {
             $vevent->add('X-MOZ-LASTACK', $mozLastAck->getClone()->setTimezone('UTC'), array('VALUE' => 'DATE-TIME'));
         }
         if ($mozSnooze instanceof DateTime) {
             $vevent->add('X-MOZ-SNOOZE-TIME', $mozSnooze->getClone()->setTimezone('UTC'), array('VALUE' => 'DATE-TIME'));
         }
     }
     $baseUrl = Tinebase_Core::getHostname() . "/webdav/Calendar/records/Calendar_Model_Event/{$event->getId()}/";
     if ($event->attachments instanceof Tinebase_Record_RecordSet) {
         foreach ($event->attachments as $attachment) {
             $filename = rawurlencode($attachment->name);
             $attach = $vcalendar->createProperty('ATTACH', "{$baseUrl}{$filename}", array('MANAGED-ID' => $attachment->hash, 'FMTTYPE' => $attachment->contenttype, 'SIZE' => $attachment->size, 'FILENAME' => $filename), 'TEXT');
             $vevent->add($attach);
         }
     }
     $vcalendar->add($vevent);
 }