Пример #1
0
 /**
  * Wrapper to update an event object depending on the given savemode
  */
 private function update_event($event)
 {
     if (!($storage = $this->get_calendar($event['calendar']))) {
         return false;
     }
     // move event to another folder/calendar
     if ($event['_fromcalendar'] && $event['_fromcalendar'] != $event['calendar']) {
         if (!($fromcalendar = $this->get_calendar($event['_fromcalendar']))) {
             return false;
         }
         $old = $fromcalendar->get_event($event['id']);
         if ($event['_savemode'] != 'new') {
             if (!$fromcalendar->storage->move($old['uid'], $storage->storage)) {
                 return false;
             }
             $fromcalendar = $storage;
         }
     } else {
         $fromcalendar = $storage;
     }
     $success = false;
     $savemode = 'all';
     $attachments = array();
     $old = $master = $storage->get_event($event['id']);
     if (!$old || !$old['start']) {
         rcube::raise_error(array('code' => 600, 'type' => 'php', 'file' => __FILE__, 'line' => __LINE__, 'message' => "Failed to load event object to update: id=" . $event['id']), true, false);
         return false;
     }
     // modify a recurring event, check submitted savemode to do the right things
     if ($old['recurrence'] || $old['recurrence_id'] || $old['isexception']) {
         $master = $storage->get_event($old['uid']);
         $savemode = $event['_savemode'] ?: ($old['recurrence_id'] || $old['isexception'] ? 'current' : 'all');
         // this-and-future on the first instance equals to 'all'
         if ($savemode == 'future' && $master['start'] && $old['_instance'] == libcalendaring::recurrence_instance_identifier($master)) {
             $savemode = 'all';
         } else {
             if (!$old['recurrence'] && !$old['recurrence_id'] && $old['isexception']) {
                 $savemode = 'current';
             }
         }
     }
     // check if update affects scheduling and update attendee status accordingly
     $reschedule = $this->check_scheduling($event, $old, true);
     // keep saved exceptions (not submitted by the client)
     if ($old['recurrence']['EXDATE'] && !isset($event['recurrence']['EXDATE'])) {
         $event['recurrence']['EXDATE'] = $old['recurrence']['EXDATE'];
     }
     if (isset($event['recurrence']['EXCEPTIONS'])) {
         $with_exceptions = true;
     } else {
         if ($old['recurrence']['EXCEPTIONS']) {
             $event['recurrence']['EXCEPTIONS'] = $old['recurrence']['EXCEPTIONS'];
         } else {
             if ($old['exceptions']) {
                 $event['exceptions'] = $old['exceptions'];
             }
         }
     }
     // remove some internal properties which should not be saved
     unset($event['_savemode'], $event['_fromcalendar'], $event['_identity'], $event['_owner'], $event['_notify'], $event['_method'], $event['_sender'], $event['_sender_utf'], $event['_size']);
     switch ($savemode) {
         case 'new':
             // save submitted data as new (non-recurring) event
             $event['recurrence'] = array();
             $event['_copyfrom'] = $master['_msguid'];
             $event['_mailbox'] = $master['_mailbox'];
             $event['uid'] = $this->cal->generate_uid();
             unset($event['recurrence_id'], $event['recurrence_date'], $event['_instance'], $event['id']);
             // copy attachment metadata to new event
             $event = self::from_rcube_event($event, $master);
             self::clear_attandee_noreply($event);
             if ($success = $storage->insert_event($event)) {
                 $success = $event['uid'];
             }
             break;
         case 'future':
             // create a new recurring event
             $event['_copyfrom'] = $master['_msguid'];
             $event['_mailbox'] = $master['_mailbox'];
             $event['uid'] = $this->cal->generate_uid();
             unset($event['recurrence_id'], $event['recurrence_date'], $event['_instance'], $event['id']);
             // copy attachment metadata to new event
             $event = self::from_rcube_event($event, $master);
             // remove recurrence exceptions on re-scheduling
             if ($reschedule) {
                 unset($event['recurrence']['EXCEPTIONS'], $event['exceptions'], $master['recurrence']['EXDATE']);
             } else {
                 if (is_array($event['recurrence']['EXCEPTIONS'])) {
                     // only keep relevant exceptions
                     $event['recurrence']['EXCEPTIONS'] = array_filter($event['recurrence']['EXCEPTIONS'], function ($exception) use($event) {
                         return $exception['start'] > $event['start'];
                     });
                     if (is_array($event['recurrence']['EXDATE'])) {
                         $event['recurrence']['EXDATE'] = array_filter($event['recurrence']['EXDATE'], function ($exdate) use($event) {
                             return $exdate > $event['start'];
                         });
                     }
                     // set link to top-level exceptions
                     $event['exceptions'] =& $event['recurrence']['EXCEPTIONS'];
                 }
             }
             // compute remaining occurrences
             if ($event['recurrence']['COUNT']) {
                 if (!$old['_count']) {
                     $old['_count'] = $this->get_recurrence_count($master, $old['start']);
                 }
                 $event['recurrence']['COUNT'] -= intval($old['_count']);
             }
             // remove fixed weekday when date changed
             if ($old['start']->format('Y-m-d') != $event['start']->format('Y-m-d')) {
                 if (strlen($event['recurrence']['BYDAY']) == 2) {
                     unset($event['recurrence']['BYDAY']);
                 }
                 if ($old['recurrence']['BYMONTH'] == $old['start']->format('n')) {
                     unset($event['recurrence']['BYMONTH']);
                 }
             }
             // set until-date on master event
             $master['recurrence']['UNTIL'] = clone $old['start'];
             $master['recurrence']['UNTIL']->sub(new DateInterval('P1D'));
             unset($master['recurrence']['COUNT']);
             // remove all exceptions after $event['start']
             if (is_array($master['recurrence']['EXCEPTIONS'])) {
                 $master['recurrence']['EXCEPTIONS'] = array_filter($master['recurrence']['EXCEPTIONS'], function ($exception) use($event) {
                     return $exception['start'] < $event['start'];
                 });
                 // set link to top-level exceptions
                 $master['exceptions'] =& $master['recurrence']['EXCEPTIONS'];
             }
             if (is_array($master['recurrence']['EXDATE'])) {
                 $master['recurrence']['EXDATE'] = array_filter($master['recurrence']['EXDATE'], function ($exdate) use($event) {
                     return $exdate < $event['start'];
                 });
             }
             // save new event
             if ($success = $storage->insert_event($event)) {
                 $success = $event['uid'];
                 // update master event (no rescheduling!)
                 self::clear_attandee_noreply($master);
                 $storage->update_event($master);
             }
             break;
         case 'current':
             // recurring instances shall not store recurrence rules and attachments
             $event['recurrence'] = array();
             $event['thisandfuture'] = $savemode == 'future';
             unset($event['attachments'], $event['id']);
             // increment sequence of this instance if scheduling is affected
             if ($reschedule) {
                 $event['sequence'] = max($old['sequence'], $master['sequence']) + 1;
             } else {
                 if (!isset($event['sequence'])) {
                     $event['sequence'] = $old['sequence'] ?: $master['sequence'];
                 }
             }
             // save properties to a recurrence exception instance
             if ($old['_instance'] && is_array($master['recurrence']['EXCEPTIONS'])) {
                 if ($this->update_recurrence_exceptions($master, $event, $old, $savemode)) {
                     $success = $storage->update_event($master, $old['id']);
                     break;
                 }
             }
             $add_exception = true;
             // adjust matching RDATE entry if dates changed
             if (is_array($master['recurrence']['RDATE']) && ($old_date = $old['start']->format('Ymd')) != $event['start']->format('Ymd')) {
                 foreach ($master['recurrence']['RDATE'] as $j => $rdate) {
                     if ($rdate->format('Ymd') == $old_date) {
                         $master['recurrence']['RDATE'][$j] = $event['start'];
                         sort($master['recurrence']['RDATE']);
                         $add_exception = false;
                         break;
                     }
                 }
             }
             // save as new exception to master event
             if ($add_exception) {
                 self::add_exception($master, $event, $old);
             }
             $success = $storage->update_event($master);
             break;
         default:
             // 'all' is default
             $event['id'] = $master['uid'];
             $event['uid'] = $master['uid'];
             // use start date from master but try to be smart on time or duration changes
             $old_start_date = $old['start']->format('Y-m-d');
             $old_start_time = $old['allday'] ? '' : $old['start']->format('H:i');
             $old_duration = $old['end']->format('U') - $old['start']->format('U');
             $new_start_date = $event['start']->format('Y-m-d');
             $new_start_time = $event['allday'] ? '' : $event['start']->format('H:i');
             $new_duration = $event['end']->format('U') - $event['start']->format('U');
             $diff = $old_start_date != $new_start_date || $old_start_time != $new_start_time || $old_duration != $new_duration;
             $date_shift = $old['start']->diff($event['start']);
             // shifted or resized
             if ($diff && ($old_start_date == $new_start_date || $old_duration == $new_duration)) {
                 $event['start'] = $master['start']->add($date_shift);
                 $event['end'] = clone $event['start'];
                 $event['end']->add(new DateInterval('PT' . $new_duration . 'S'));
                 // remove fixed weekday, will be re-set to the new weekday in kolab_calendar::update_event()
                 if ($old_start_date != $new_start_date) {
                     if (strlen($event['recurrence']['BYDAY']) == 2) {
                         unset($event['recurrence']['BYDAY']);
                     }
                     if ($old['recurrence']['BYMONTH'] == $old['start']->format('n')) {
                         unset($event['recurrence']['BYMONTH']);
                     }
                 }
             } else {
                 if ($new_start_date . $new_start_time == $old_start_date . $old_start_time) {
                     $event['start'] = $master['start'];
                     $event['end'] = $master['end'];
                 }
             }
             // when saving an instance in 'all' mode, copy recurrence exceptions over
             if ($old['recurrence_id']) {
                 $event['recurrence']['EXCEPTIONS'] = $master['recurrence']['EXCEPTIONS'];
             } else {
                 if ($master['_instance']) {
                     $event['_instance'] = $master['_instance'];
                     $event['recurrence_date'] = $master['recurrence_date'];
                 }
             }
             // TODO: forward changes to exceptions (which do not yet have differing values stored)
             if (is_array($event['recurrence']) && is_array($event['recurrence']['EXCEPTIONS']) && !$with_exceptions) {
                 // determine added and removed attendees
                 $old_attendees = $current_attendees = $added_attendees = array();
                 foreach ((array) $old['attendees'] as $attendee) {
                     $old_attendees[] = $attendee['email'];
                 }
                 foreach ((array) $event['attendees'] as $attendee) {
                     $current_attendees[] = $attendee['email'];
                     if (!in_array($attendee['email'], $old_attendees)) {
                         $added_attendees[] = $attendee;
                     }
                 }
                 $removed_attendees = array_diff($old_attendees, $current_attendees);
                 foreach ($event['recurrence']['EXCEPTIONS'] as $i => $exception) {
                     calendar::merge_attendee_data($event['recurrence']['EXCEPTIONS'][$i], $added_attendees, $removed_attendees);
                 }
                 // adjust recurrence-id when start changed and therefore the entire recurrence chain changes
                 if ($old_start_date != $new_start_date || $old_start_time != $new_start_time) {
                     $recurrence_id_format = libcalendaring::recurrence_id_format($event);
                     foreach ($event['recurrence']['EXCEPTIONS'] as $i => $exception) {
                         $recurrence_id = is_a($exception['recurrence_date'], 'DateTime') ? $exception['recurrence_date'] : rcube_utils::anytodatetime($exception['_instance'], $old['start']->getTimezone());
                         if (is_a($recurrence_id, 'DateTime')) {
                             $recurrence_id->add($date_shift);
                             $event['recurrence']['EXCEPTIONS'][$i]['recurrence_date'] = $recurrence_id;
                             $event['recurrence']['EXCEPTIONS'][$i]['_instance'] = $recurrence_id->format($recurrence_id_format);
                         }
                     }
                 }
                 // set link to top-level exceptions
                 $event['exceptions'] =& $event['recurrence']['EXCEPTIONS'];
             }
             // unset _dateonly flags in (cached) date objects
             unset($event['start']->_dateonly, $event['end']->_dateonly);
             $success = $storage->update_event($event) ? $event['id'] : false;
             // return master UID
             break;
     }
     if ($success && $this->freebusy_trigger) {
         $this->rc->output->command('plugin.ping_url', array('action' => 'calendar/push-freebusy', 'source' => $storage->id));
     }
     return $success;
 }
 /**
  * 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
  * @return array List of recurring event instances
  */
 public function get_recurring_events($event, $start, $end = null)
 {
     $events = array();
     if ($event['recurrence']) {
         // include library class
         require_once dirname(__FILE__) . '/../lib/calendar_recurrence.php';
         $rcmail = rcmail::get_instance();
         $recurrence = new calendar_recurrence($rcmail->plugins->get_plugin('calendar'), $event);
         $recurrence_id_format = libcalendaring::recurrence_id_format($event);
         // 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));
         }
         $i = 0;
         while ($next_event = $recurrence->next_instance()) {
             // add to output if in range
             if ($next_event['start'] <= $end && $next_event['end'] >= $start) {
                 $next_event['_instance'] = $next_event['start']->format($recurrence_id_format);
                 $next_event['id'] = $next_event['uid'] . '-' . $exception['_instance'];
                 $next_event['recurrence_id'] = $event['uid'];
                 $events[] = $next_event;
             } else {
                 if ($next_event['start'] > $end) {
                     // stop loop if out of range
                     break;
                 }
             }
             // avoid endless recursion loops
             if (++$i > 1000) {
                 break;
             }
         }
     }
     return $events;
 }
 /**
  * Insert "fake" entries for recurring occurences of this event
  */
 private function _update_recurring($event)
 {
     if (empty($this->calendars)) {
         return;
     }
     if (!empty($event['recurrence'])) {
         $exdata = array();
         $exceptions = $this->_load_exceptions($event);
         foreach ($exceptions as $exception) {
             $exdate = substr($exception['_instance'], 0, 8);
             $exdata[$exdate] = $exception;
         }
     }
     // clear existing recurrence copies
     $this->rc->db->query("DELETE FROM " . $this->db_events . "\n       WHERE recurrence_id=?\n       AND isexception=0\n       AND calendar_id IN (" . $this->calendar_ids . ")", $event['id']);
     // create new fake entries
     if (!empty($event['recurrence'])) {
         // include library class
         require_once $this->cal->home . '/lib/calendar_recurrence.php';
         $recurrence = new calendar_recurrence($this->cal, $event);
         $count = 0;
         $event['allday'] = $event['all_day'];
         $duration = $event['start']->diff($event['end']);
         $recurrence_id_format = libcalendaring::recurrence_id_format($event);
         while ($next_start = $recurrence->next_start()) {
             $instance = $next_start->format($recurrence_id_format);
             $datestr = substr($instance, 0, 8);
             // skip exceptions
             // TODO: merge updated data from master event
             if ($exdata[$datestr]) {
                 continue;
             }
             $next_start->setTimezone($this->server_timezone);
             $next_end = clone $next_start;
             $next_end->add($duration);
             $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_start, 'end' => $next_end, 'status' => $event['status']));
             $query = $this->rc->db->query(sprintf("INSERT INTO " . $this->db_events . "\n           (calendar_id, recurrence_id, created, changed, uid, instance, %s, %s, all_day, sequence, recurrence, title, description, location, categories, url, free_busy, priority, sensitivity, status, alarms, attendees, notifyat)\n            SELECT calendar_id, ?, %s, %s, uid, ?, ?, ?, all_day, sequence, recurrence, title, description, location, categories, url, free_busy, priority, sensitivity, status, alarms, attendees, ?\n            FROM  " . $this->db_events . " WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")", $this->rc->db->quote_identifier('start'), $this->rc->db->quote_identifier('end'), $this->rc->db->now(), $this->rc->db->now()), $event['id'], $instance, $next_start->format(self::DB_DATE_FORMAT), $next_end->format(self::DB_DATE_FORMAT), $notify_at, $event['id']);
             if (!$this->rc->db->affected_rows($query)) {
                 break;
             }
             // stop adding events for inifinite recurrence after 20 years
             if (++$count > 999 || !$recurrence->recurEnd && !$recurrence->recurCount && $next_start->format('Y') > date('Y') + 20) {
                 break;
             }
         }
         // remove all exceptions after recurrence end
         if ($next_end && !empty($exceptions)) {
             $this->rc->db->query("DELETE FROM " . $this->db_events . "\n           WHERE `recurrence_id`=?\n           AND `isexception`=1\n           AND `start` > ?\n           AND `calendar_id` IN (" . $this->calendar_ids . ")", $event['id'], $next_end->format(self::DB_DATE_FORMAT));
         }
     }
 }
 /**
  * Convert from Kolab_Format to internal representation
  */
 private function _to_driver_event($record, $noinst = false)
 {
     $record['calendar'] = $this->id;
     $record['links'] = $this->get_links($record['uid']);
     if ($this->get_namespace() == 'other') {
         $record['className'] = 'fc-event-ns-other';
         $record = kolab_driver::add_partstat_class($record, array('NEEDS-ACTION', 'DECLINED'), $this->get_owner());
     }
     // add instance identifier to first occurrence (master event)
     $recurrence_id_format = libcalendaring::recurrence_id_format($record);
     if (!$noinst && $record['recurrence'] && !$record['recurrence_id'] && !$record['_instance']) {
         $record['_instance'] = $record['start']->format($recurrence_id_format);
     } else {
         if (is_a($record['recurrence_date'], 'DateTime')) {
             $record['_instance'] = $record['recurrence_date']->format($recurrence_id_format);
         }
     }
     // clean up exception data
     if ($record['recurrence'] && is_array($record['recurrence']['EXCEPTIONS'])) {
         array_walk($record['recurrence']['EXCEPTIONS'], function (&$exception) {
             unset($exception['_mailbox'], $exception['_msguid'], $exception['_formatobj'], $exception['_attachments']);
         });
     }
     return $record;
 }