/** * Handle parsing recurrence related fields. * * @param Horde_Icalendar $vEvent * @throws Kronolith_Exception */ protected function _handlevEventRecurrence($vEvent) { // Recurrence. try { $rrule = $vEvent->getAttribute('RRULE'); if (!is_array($rrule)) { $this->recurrence = new Horde_Date_Recurrence($this->start); if (strpos($rrule, '=') !== false) { $this->recurrence->fromRRule20($rrule); } else { $this->recurrence->fromRRule10($rrule); } /* Delete all existing exceptions to this event if it * already exists */ if (!empty($this->uid)) { $kronolith_driver = Kronolith::getDriver(null, $this->calendar); $search = new StdClass(); $search->start = $this->recurrence->getRecurStart(); $search->end = $this->recurrence->getRecurEnd(); $search->baseid = $this->uid; $results = $kronolith_driver->search($search); foreach ($results as $days) { foreach ($days as $exception) { $kronolith_driver->deleteEvent($exception->id); } } } // Exceptions. EXDATE represents deleted events, just add the // exception, no new event is needed. $exdates = $vEvent->getAttributeValues('EXDATE'); if (is_array($exdates)) { foreach ($exdates as $exdate) { if (is_array($exdate)) { $this->recurrence->addException((int) $exdate['year'], (int) $exdate['month'], (int) $exdate['mday']); } } } } } catch (Horde_Icalendar_Exception $e) { } // RECURRENCE-ID indicates that this event represents an exception try { $recurrenceid = $vEvent->getAttribute('RECURRENCE-ID'); $kronolith_driver = Kronolith::getDriver(null, $this->calendar); $originaldt = new Horde_Date($recurrenceid); $this->exceptionoriginaldate = $originaldt; $this->baseid = $this->uid; $this->uid = null; try { $originalEvent = $kronolith_driver->getByUID($this->baseid); $originalEvent->recurrence->addException($originaldt->format('Y'), $originaldt->format('m'), $originaldt->format('d')); $originalEvent->save(); } catch (Horde_Exception_NotFound $e) { throw new Kronolith_Exception(_("Unable to locate original event series.")); } } catch (Horde_Icalendar_Exception $e) { } }
/** * Creates a task from a Horde_Icalendar_Vtodo object. * * @param Horde_Icalendar_Vtodo $vTodo The iCalendar data to update from. */ public function fromiCalendar(Horde_Icalendar_Vtodo $vTodo) { /* Owner is always current user. */ $this->owner = $GLOBALS['registry']->getAuth(); try { $name = $vTodo->getAttribute('SUMMARY'); if (!is_array($name)) { $this->name = $name; } } catch (Horde_Icalendar_Exception $e) { } // Not sure why we were mapping the ORGANIZER to the person the // task is assigned to? If anything, this needs to be mapped to // any ATTENDEE fields from the vTodo. // try { // $assignee = $vTodo->getAttribute('ORGANIZER'); // if (!is_array($assignee)) { $this->assignee = $assignee; } // } catch (Horde_Icalendar_Exception $e) {} try { $organizer = $vTodo->getAttribute('ORGANIZER'); if (!is_array($organizer)) { $this->organizer = $organizer; } } catch (Horde_Icalendar_Exception $e) { } // If an attendee matches our from_addr, add current user as assignee. try { $atnames = $vTodo->getAttribute('ATTENDEE'); if (!is_array($atnames)) { $atnames = array($atnames); } $identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create(); $all_addrs = $identity->getAll('from_addr'); foreach ($atnames as $index => $attendee) { if ($vTodo->getAttribute('VERSION') < 2) { $addr_ob = new Horde_Mail_Rfc822_Address($attendee); if (!$addr_ob->valid) { continue; } $attendee = $addr_ob->bare_address; $name = $addr_ob->personal; } else { $attendee = str_ireplace('mailto:', '', $attendee); $addr_ob = new Horde_Mail_Rfc822_Address($attendee); if (!$addr_ob->valid) { continue; } $attendee = $addr_ob->bare_address; $name = isset($atparms[$index]['CN']) ? $atparms[$index]['CN'] : null; } if (in_array($attendee, $all_addrs) !== false) { $this->assignee = $GLOBALS['conf']['assignees']['allow_external'] ? $attendee : $GLOBALS['registry']->getAuth(); $this->status = Nag::RESPONSE_ACCEPTED; break; } elseif ($GLOBALS['conf']['assignees']['allow_external']) { $this->assignee = $attendee; } } } catch (Horde_Icalendar_Exception $e) { } // Default to current user as organizer if (empty($this->organizer) && !empty($this->assignee)) { $this->organizer = $identity->getValue('from_addr'); } try { $uid = $vTodo->getAttribute('UID'); if (!is_array($uid)) { $this->uid = $uid; } } catch (Horde_Icalendar_Exception $e) { } try { $relations = $vTodo->getAttribute('RELATED-TO'); if (!is_array($relations)) { $relations = array($relations); } $params = $vTodo->getAttribute('RELATED-TO', true); foreach ($relations as $id => $relation) { if (empty($params[$id]['RELTYPE']) || Horde_String::upper($params[$id]['RELTYPE']) == 'PARENT') { try { $parent = $this->_storage->getByUID($relation, $this->tasklist); $this->parent_id = $parent->id; } catch (Horde_Exception_NotFound $e) { } break; } } } catch (Horde_Icalendar_Exception $e) { } try { $start = $vTodo->getAttribute('DTSTART'); if (!is_array($start)) { // Date-Time field $this->start = $start; } else { // Date field $this->start = mktime(0, 0, 0, (int) $start['month'], (int) $start['mday'], (int) $start['year']); } } catch (Horde_Icalendar_Exception $e) { } try { $due = $vTodo->getAttribute('DUE'); if (is_array($due)) { $this->due = mktime(0, 0, 0, (int) $due['month'], (int) $due['mday'], (int) $due['year']); } elseif (!empty($due)) { $this->due = $due; } } catch (Horde_Icalendar_Exception $e) { } // Recurrence. try { $rrule = $vTodo->getAttribute('RRULE'); if (!is_array($rrule)) { $this->recurrence = new Horde_Date_Recurrence($this->due); if (strpos($rrule, '=') !== false) { $this->recurrence->fromRRule20($rrule); } else { $this->recurrence->fromRRule10($rrule); } // Completions. EXDATE represents completed tasks, just add the // exception. $exdates = $vTodo->getAttributeValues('EXDATE'); if (is_array($exdates)) { foreach ($exdates as $exdate) { if (is_array($exdate)) { $this->recurrence->addCompletion((int) $exdate['year'], (int) $exdate['month'], (int) $exdate['mday']); } } } } } catch (Horde_Icalendar_Exception $e) { } // vCalendar 1.0 alarms try { $alarm = $vTodo->getAttribute('AALARM'); if (!is_array($alarm) && !empty($alarm) && !empty($this->due)) { $this->alarm = intval(($this->due - $alarm) / 60); if ($this->alarm === 0) { // We don't support alarms exactly at due date. $this->alarm = 1; } } } catch (Horde_Icalendar_Exception $e) { } // vCalendar 2.0 alarms foreach ($vTodo->getComponents() as $alarm) { if (!$alarm instanceof Horde_Icalendar_Valarm) { continue; } try { if ($alarm->getAttribute('ACTION') == 'NONE') { continue; } } catch (Horde_Icalendar_Exception $e) { } try { // @todo consider implementing different ACTION types. // $action = $alarm->getAttribute('ACTION'); $trigger = $alarm->getAttribute('TRIGGER'); $triggerParams = $alarm->getAttribute('TRIGGER', true); } catch (Horde_Icalendar_Exception $e) { continue; } if (!is_array($triggerParams)) { $triggerParams = array($triggerParams); } $haveTrigger = false; foreach ($triggerParams as $tp) { if (isset($tp['VALUE']) && $tp['VALUE'] == 'DATE-TIME') { if (isset($tp['RELATED']) && $tp['RELATED'] == 'END') { if ($this->due) { $this->alarm = intval(($this->due - $trigger) / 60); $haveTrigger = true; break; } } else { if ($this->start) { $this->alarm = intval(($this->start - $trigger) / 60); $haveTrigger = true; break; } } } elseif (isset($tp['RELATED']) && $tp['RELATED'] == 'END' && $this->due && $this->start) { $this->alarm = -intval($trigger / 60); $this->alarm -= $this->due - $this->start; $haveTrigger = true; break; } } if (!$haveTrigger) { $this->alarm = -intval($trigger / 60); } break; } // Alarm snoozing/dismissal if ($this->alarm) { try { // If X-MOZ-LASTACK is set, this task is either dismissed or // snoozed. $vTodo->getAttribute('X-MOZ-LASTACK'); try { // If X-MOZ-SNOOZE-TIME is set, this task is snoozed. $snooze = $vTodo->getAttribute('X-MOZ-SNOOZE-TIME'); $this->snooze = intval(($snooze - time()) / 60); } catch (Horde_Icalendar_Exception $e) { // If X-MOZ-SNOOZE-TIME is not set, this event is dismissed. $this->snooze = -1; } } catch (Horde_Icalendar_Exception $e) { } } try { $desc = $vTodo->getAttribute('DESCRIPTION'); if (!is_array($desc)) { $this->desc = $desc; } } catch (Horde_Icalendar_Exception $e) { } try { $priority = $vTodo->getAttribute('PRIORITY'); if (!is_array($priority)) { $this->priority = $priority; } } catch (Horde_Icalendar_Exception $e) { } try { $cat = $vTodo->getAttribute('CATEGORIES'); if (!is_array($cat)) { $this->tags = $cat; } } catch (Horde_Icalendar_Exception $e) { } try { $status = $vTodo->getAttribute('STATUS'); if (!is_array($status)) { $this->completed = !strcasecmp($status, 'COMPLETED'); } } catch (Horde_Icalendar_Exception $e) { } try { $class = $vTodo->getAttribute('CLASS'); if (!is_array($class)) { $class = Horde_String::upper($class); $this->private = $class == 'PRIVATE' || $class == 'CONFIDENTIAL'; } } catch (Horde_Icalendar_Exception $e) { } }
/** * Handle parsing recurrence related fields. * * @param Horde_Icalendar $vEvent * @throws Kronolith_Exception */ protected function _handlevEventRecurrence($vEvent) { // Recurrence. try { $rrule = $vEvent->getAttribute('RRULE'); if (!is_array($rrule)) { $this->recurrence = new Horde_Date_Recurrence($this->start); if (strpos($rrule, '=') !== false) { $this->recurrence->fromRRule20($rrule); } else { $this->recurrence->fromRRule10($rrule); } // Exceptions. EXDATE represents deleted events, just add the // exception, no new event is needed. $exdates = $vEvent->getAttributeValues('EXDATE'); if (is_array($exdates)) { foreach ($exdates as $exdate) { if (is_array($exdate)) { $this->recurrence->addException((int) $exdate['year'], (int) $exdate['month'], (int) $exdate['mday']); } } } } } catch (Horde_Icalendar_Exception $e) { } // RECURRENCE-ID indicates that this event represents an exception try { $this->recurrenceid = $vEvent->getAttribute('RECURRENCE-ID'); $originaldt = new Horde_Date($this->recurrenceid); $this->exceptionoriginaldate = $originaldt; $this->baseid = $this->uid; $this->uid = null; try { $originalEvent = $this->getDriver()->getByUID($this->baseid); $originalEvent->recurrence->addException($originaldt->format('Y'), $originaldt->format('m'), $originaldt->format('d')); $originalEvent->save(); } catch (Horde_Exception_NotFound $e) { throw new Kronolith_Exception(_("Unable to locate original event series.")); } } catch (Horde_Icalendar_Exception $e) { } }
/** * Generate the HTML for a vEvent. */ protected function _vEvent($vevent, $id, $method = 'PUBLISH', $components = array()) { global $injector, $prefs, $registry, $notification; $attendees = null; $desc = ''; $sender = $vevent->organizerName(); $options = array(); try { if (($attendees = $vevent->getAttribute('ATTENDEE')) && !is_array($attendees)) { $attendees = array($attendees); } } catch (Horde_Icalendar_Exception $e) { } switch ($method) { case 'PUBLISH': $desc = _("%s wishes to make you aware of \"%s\"."); if ($registry->hasMethod('calendar/import')) { $options['import'] = _("Add this to my calendar"); } break; case 'REQUEST': // Check if this is an update. try { $calendars = $registry->calendar->listCalendars(true); $registry->call('calendar/export', array($vevent->getAttributeSingle('UID'), 'text/calendar', array(), $calendars)); $desc = _("%s wants to notify you about changes in \"%s\"."); $is_update = true; } catch (Horde_Exception $e) { $desc = _("%s wishes to make you aware of \"%s\"."); $is_update = false; // Check that you are one of the attendees here. if (!empty($attendees)) { $identity = $injector->getInstance('IMP_Identity'); for ($i = 0, $c = count($attendees); $i < $c; ++$i) { $attendee = parse_url($attendees[$i]); if (!empty($attendee['path']) && $identity->hasAddress($attendee['path'])) { $desc = _("%s requests your presence at \"%s\"."); break; } } } } if ($is_update && $registry->hasMethod('calendar/replace')) { $options['accept-import'] = _("Accept and update in my calendar"); $options['import'] = _("Update in my calendar"); } elseif ($registry->hasMethod('calendar/import')) { $options['accept-import'] = _("Accept and add to my calendar"); $options['import'] = _("Add to my calendar"); } $options['accept'] = _("Accept request"); $options['tentative'] = _("Tentatively Accept request"); $options['deny'] = _("Deny request"); // $options['delegate'] = _("Delegate position"); break; case 'ADD': $desc = _("%s wishes to amend \"%s\"."); if ($registry->hasMethod('calendar/import')) { $options['import'] = _("Update this event on my calendar"); } break; case 'REFRESH': $desc = _("%s wishes to receive the latest information about \"%s\"."); $options['send'] = _("Send Latest Information"); break; case 'REPLY': $desc = _("%s has replied to the invitation to \"%s\"."); $from = $this->getConfigParam('imp_contents')->getHeader()->getHeader('from'); $sender = $from ? $from->getAddressList(true)->first()->bare_address : null; if ($registry->hasMethod('calendar/updateAttendee') && $this->_autoUpdateReply(self::AUTO_UPDATE_EVENT_REPLY, $sender)) { try { $registry->call('calendar/updateAttendee', array($vevent, $sender)); $notification->push(_("Respondent Status Updated."), 'horde.success'); } catch (Horde_Exception $e) { $notification->push(sprintf(_("There was an error updating the event: %s"), $e->getMessage()), 'horde.error'); } } else { $options['update'] = _("Update respondent status"); } break; case 'CANCEL': try { $vevent->getAttributeSingle('RECURRENCE-ID'); $params = $vevent->getAttribute('RECURRENCE-ID', true); foreach ($params as $param) { if (array_key_exists('RANGE', $param)) { $desc = _("%s has cancelled multiple instances of the recurring \"%s\"."); } break; } if (empty($desc)) { $desc = _("%s has cancelled an instance of the recurring \"%s\"."); } if ($registry->hasMethod('calendar/replace')) { $options['delete'] = _("Update in my calendar"); } } catch (Horde_Icalendar_Exception $e) { $desc = _("%s has cancelled \"%s\"."); if ($registry->hasMethod('calendar/delete')) { $options['delete'] = _("Delete from my calendar"); } } break; } $view = $this->_getViewOb(); try { $start = $vevent->getAttribute('DTSTART'); $view->start = is_array($start) ? strftime($prefs->getValue('date_format'), mktime(0, 0, 0, $start['month'], $start['mday'], $start['year'])) : strftime($prefs->getValue('date_format'), $start) . ' ' . date($prefs->getValue('twentyFour') ? ' G:i' : ' g:i a', $start); } catch (Horde_Icalendar_Exception $e) { $start = null; } try { $end = $vevent->getAttribute('DTEND'); $view->end = is_array($end) ? strftime($prefs->getValue('date_format'), mktime(0, 0, 0, $end['month'], $end['mday'], $end['year'])) : strftime($prefs->getValue('date_format'), $end) . ' ' . date($prefs->getValue('twentyFour') ? ' G:i' : ' g:i a', $end); } catch (Horde_Icalendar_Exception $e) { $end = null; } try { $summary = $vevent->getAttributeSingle('SUMMARY'); $view->summary = $summary; } catch (Horde_Icalendar_Exception $e) { $summary = _("Unknown Meeting"); $view->summary_error = _("None"); } $view->desc = sprintf($desc, $sender, $summary); try { $view->desc2 = $vevent->getAttributeSingle('DESCRIPTION'); } catch (Horde_Icalendar_Exception $e) { } try { $view->loc = $vevent->getAttributeSingle('LOCATION'); } catch (Horde_Icalendar_Exception $e) { } try { $rrule = $vevent->getAttribute('RRULE'); } catch (Horde_Icalendar_Exception $e) { $rrule = array(); } if (!is_array($rrule)) { $recurrence = new Horde_Date_Recurrence(new Horde_Date($view->start)); if (strpos($rrule, '=') !== false) { $recurrence->fromRRule20($rrule); } else { $recurrence->fromRRule10($rrule); } // Add exceptions try { $exdates = $vevent->getAttributeValues('EXDATE'); if (is_array($exdates)) { foreach ($exdates as $exdate) { if (is_array($exdate)) { $recurrence->addException((int) $exdate['year'], (int) $exdate['month'], (int) $exdate['mday']); } } } } catch (Horde_ICalendar_Exception $e) { } $view->recurrence = $recurrence->toString($prefs->getValue('date_format')); $view->exceptions = array(); foreach ($components as $key => $component) { try { if ($component->getAttribute('RECURRENCE-ID') && $component->getAttributeSingle('UID') == $vevent->getAttributeSingle('UID')) { if ($ex = $this->_vEventException($component, $key, $method)) { $view->exceptions[] = $ex; } } } catch (Horde_Icalendar_Exception $e) { } } } if (!empty($attendees)) { $view->attendees = $this->_parseAttendees($vevent, $attendees); } if (!is_null($start) && !is_null($end) && in_array($method, array('PUBLISH', 'REQUEST', 'ADD')) && $registry->hasMethod('calendar/getFbCalendars') && $registry->hasMethod('calendar/listEvents')) { try { $calendars = $registry->call('calendar/getFbCalendars'); $vevent_start = new Horde_Date($start); $vevent_end = new Horde_Date($end); // Check if it's an all-day event. if (is_array($start)) { $vevent_allDay = true; $vevent_end = $vevent_end->sub(1); } else { $vevent_allDay = false; $time_span_start = $vevent_start->sub($prefs->getValue('conflict_interval') * 60); $time_span_end = $vevent_end->add($prefs->getValue('conflict_interval') * 60); } $events = $registry->call('calendar/listEvents', array($start, $vevent_end, $calendars, false)); // TODO: Check if there are too many events to show. $conflicts = array(); foreach ($events as $calendar) { foreach ($calendar as $event) { // TODO: WTF? Why are we using Kronolith constants // here? if (in_array($event->status, array(Kronolith::STATUS_CANCELLED, Kronolith::STATUS_FREE))) { continue; } if ($vevent_allDay || $event->isAllDay()) { $type = 'collision'; } elseif ($event->end->compareDateTime($time_span_start) <= -1 || $event->start->compareDateTime($time_span_end) >= 1) { continue; } elseif ($event->end->compareDateTime($vevent_start) <= -1 || $event->start->compareDateTime($vevent_end) >= 1) { $type = 'nearcollision'; } else { $type = 'collision'; } $conflicts[] = array('collision' => $type == 'collision', 'range' => $event->getTimeRange(), 'title' => $event->getTitle()); } } if (!empty($conflicts)) { $view->conflicts = $conflicts; } } catch (Horde_Exception $e) { } } if (!empty($options)) { reset($options); $view->options = $options; $view->options_id = $id; } return $view->render('action'); }
public function testBug12869RecurrenceEndFromIcalendar() { date_default_timezone_set('Europe/Amsterdam'); $iCal = new Horde_Icalendar(); $iCal->parsevCalendar(file_get_contents(__DIR__ . '/fixtures/bug12869.ics')); $components = $iCal->getComponents(); foreach ($components as $content) { if ($content instanceof Horde_Icalendar_Vevent) { $start = new Horde_Date($content->getAttribute('DTSTART')); $end = new Horde_Date($content->getAttribute('DTEND')); $rrule = $content->getAttribute('RRULE'); $recurrence = new Horde_Date_Recurrence($start, $end); $recurrence->fromRRule20($rrule); break; } } $after = array('year' => 2013, 'month' => 12); $after['mday'] = 11; $this->assertEquals('2013-12-12 13:45:00', (string) $recurrence->nextRecurrence($after)); $after['mday'] = 18; $this->assertEquals('2013-12-19 13:45:00', (string) $recurrence->nextRecurrence($after)); $after['mday'] = 20; $this->assertEquals('', (string) $recurrence->nextRecurrence($after)); date_default_timezone_set('Europe/Berlin'); }