/** * Get the next recurring instance of this event * * @return mixed Array with event properties or False if recurrence ended */ public function next_instance() { if ($next_start = $this->next()) { $next = $this->event; $next['start'] = $next_start; if ($this->duration) { $next['end'] = clone $next_start; $next['end']->add($this->duration); } $next['recurrence_date'] = clone $next_start; $next['_instance'] = libcalendaring::recurrence_instance_identifier($next); unset($next['_formatobj']); return $next; } return false; }
/** * Add or update the given event as an exception to $master */ public static function add_exception(&$master, $event, $old = null) { if ($old) { $event['_instance'] = $old['_instance']; if (!$event['recurrence_date']) { $event['recurrence_date'] = $old['recurrence_date'] ?: $old['start']; } } else { if (!$event['recurrence_date']) { $event['recurrence_date'] = $event['start']; } } if (!$event['_instance'] && is_a($event['recurrence_date'], 'DateTime')) { $event['_instance'] = libcalendaring::recurrence_instance_identifier($event); } if (!is_array($master['exceptions']) && is_array($master['recurrence']['EXCEPTIONS'])) { $master['exceptions'] =& $master['recurrence']['EXCEPTIONS']; } $existing = false; foreach ((array) $master['exceptions'] as $i => $exception) { if ($exception['_instance'] == $event['_instance']) { $master['exceptions'][$i] = $event; $existing = true; } } if (!$existing) { $master['exceptions'][] = $event; } return true; }
/** * Get event data * * @see calendar_driver::load_events() */ public function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null) { if (empty($calendars)) { $calendars = array_keys($this->calendars); } else { if (!is_array($calendars)) { $calendars = explode(',', strval($calendars)); } } // only allow to select from calendars of this use $calendar_ids = array_map(array($this->rc->db, 'quote'), array_intersect($calendars, array_keys($this->calendars))); // compose (slow) SQL query for searching // FIXME: improve searching using a dedicated col and normalized values if ($query) { foreach (array('title', 'location', 'description', 'categories', 'attendees') as $col) { $sql_query[] = $this->rc->db->ilike($col, '%' . $query . '%'); } $sql_add = 'AND (' . join(' OR ', $sql_query) . ')'; } if (!$virtual) { $sql_add .= ' AND e.recurrence_id = 0'; } if ($modifiedsince) { $sql_add .= ' AND e.changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $modifiedsince)); } $events = array(); if (!empty($calendar_ids)) { $result = $this->rc->db->query(sprintf("SELECT e.*, (SELECT COUNT(attachment_id) FROM " . $this->db_attachments . "\n WHERE event_id = e.event_id OR event_id = e.recurrence_id) AS _attachments\n FROM " . $this->db_events . " e\n WHERE e.calendar_id IN (%s)\n AND e.start <= %s AND e.end >= %s\n %s", join(',', $calendar_ids), $this->rc->db->fromunixtime($end), $this->rc->db->fromunixtime($start), $sql_add)); while ($result && ($sql_arr = $this->rc->db->fetch_assoc($result))) { $event = $this->_read_postprocess($sql_arr); $add = true; if (!empty($event['recurrence']) && !$event['recurrence_id']) { // load recurrence exceptions (i.e. for export) if (!$virtual) { $event['recurrence']['EXCEPTIONS'] = $this->_load_exceptions($event); } else { $instance = libcalendaring::recurrence_instance_identifier($event); $exceptions = $this->_load_exceptions($event, $instance); if ($exceptions && is_array($exceptions[$instance])) { $event = $exceptions[$instance]; $add = false; } } } if ($add) { $events[] = $event; } } } // add events from the address books birthday calendar if (in_array(self::BIRTHDAY_CALENDAR_ID, $calendars) && empty($query)) { $events = array_merge($events, $this->load_birthday_events($start, $end, $search, $modifiedsince)); } return $events; }
/** * Create instances of a recurring event * * @param array Hash array with event properties * @param object DateTime Start date of the recurrence window * @param object DateTime End date of the recurrence window * @param string ID of a specific recurring event instance * @return array List of recurring event instances */ public function get_recurring_events($event, $start, $end = null, $event_id = null) { $object = $event['_formatobj']; if (!$object) { $rec = $this->storage->get_object($event['id']); $object = $rec['_formatobj']; } if (!is_object($object)) { return array(); } // determine a reasonable end date if none given if (!$end) { switch ($event['recurrence']['FREQ']) { case 'YEARLY': $intvl = 'P100Y'; break; case 'MONTHLY': $intvl = 'P20Y'; break; default: $intvl = 'P10Y'; break; } $end = clone $event['start']; $end->add(new DateInterval($intvl)); } // copy the recurrence rule from the master event (to be used in the UI) $recurrence_rule = $event['recurrence']; unset($recurrence_rule['EXCEPTIONS'], $recurrence_rule['EXDATE']); // read recurrence exceptions first $events = array(); $exdata = array(); $futuredata = array(); $recurrence_id_format = libcalendaring::recurrence_id_format($event); if (is_array($event['recurrence']['EXCEPTIONS'])) { foreach ($event['recurrence']['EXCEPTIONS'] as $exception) { if (!$exception['_instance']) { $exception['_instance'] = libcalendaring::recurrence_instance_identifier($exception); } $rec_event = $this->_to_driver_event($exception); $rec_event['id'] = $event['uid'] . '-' . $exception['_instance']; $rec_event['isexception'] = 1; // found the specifically requested instance: register exception (single occurrence wins) if ($rec_event['id'] == $event_id && (!$this->events[$event_id] || $this->events[$event_id]['thisandfuture'])) { $rec_event['recurrence'] = $recurrence_rule; $rec_event['recurrence_id'] = $event['uid']; $this->events[$rec_event['id']] = $rec_event; } // remember this exception's date $exdate = substr($exception['_instance'], 0, 8); if (!$exdata[$exdate] || $exdata[$exdate]['thisandfuture']) { $exdata[$exdate] = $rec_event; } if ($rec_event['thisandfuture']) { $futuredata[$exdate] = $rec_event; } } } // found the specifically requested instance, exiting... if ($event_id && !empty($this->events[$event_id])) { return array($this->events[$event_id]); } // use libkolab to compute recurring events if (class_exists('kolabcalendaring')) { $recurrence = new kolab_date_recurrence($object); } else { // fallback to local recurrence implementation require_once $this->cal->home . '/lib/calendar_recurrence.php'; $recurrence = new calendar_recurrence($this->cal, $event); } $i = 0; while ($next_event = $recurrence->next_instance()) { $datestr = $next_event['start']->format('Ymd'); $instance_id = $next_event['start']->format($recurrence_id_format); // use this event data for future recurring instances if ($futuredata[$datestr]) { $overlay_data = $futuredata[$datestr]; } // add to output if in range $rec_id = $event['uid'] . '-' . $instance_id; if ($next_event['start'] <= $end && $next_event['end'] >= $start || $event_id && $rec_id == $event_id) { $rec_event = $this->_to_driver_event($next_event); $rec_event['_instance'] = $instance_id; $rec_event['_count'] = $i + 1; if ($overlay_data || $exdata[$datestr]) { // copy data from exception kolab_driver::merge_exception_data($rec_event, $exdata[$datestr] ?: $overlay_data); } $rec_event['id'] = $rec_id; $rec_event['recurrence_id'] = $event['uid']; $rec_event['recurrence'] = $recurrence_rule; unset($rec_event['_attendees']); $events[] = $rec_event; if ($rec_id == $event_id) { $this->events[$rec_id] = $rec_event; break; } } else { if ($next_event['start'] > $end) { // stop loop if out of range break; } } // avoid endless recursion loops if (++$i > 1000) { break; } } return $events; }