From e4917cf0aad992356bfa9523aa8f1a8b8eba45eb Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 14 Oct 2011 11:58:42 +0200 Subject: [PATCH] Move recurrence computation to a dedicated class --- .../drivers/database/database_driver.php | 22 +++-- .../calendar/drivers/kolab/kolab_calendar.php | 22 +---- plugins/calendar/lib/calendar_recurrence.php | 86 +++++++++++++++++++ 3 files changed, 100 insertions(+), 30 deletions(-) create mode 100644 plugins/calendar/lib/calendar_recurrence.php diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index b7de719d..f233d5a2 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -224,7 +224,7 @@ class database_driver extends calendar_driver $event['calendar'], strval($event['uid']), intval($event['all_day']), - $event['recurrence'], + $event['_recurrence'], strval($event['title']), strval($event['description']), strval($event['location']), @@ -365,8 +365,7 @@ class database_driver extends calendar_driver { // compose vcalendar-style recurrencue rule from structured data $rrule = $event['recurrence'] ? calendar::to_rrule($event['recurrence']) : ''; - $event['_exdates'] = (array)$event['recurrence']['EXDATE']; - $event['recurrence'] = rtrim($rrule, ';'); + $event['_recurrence'] = rtrim($rrule, ';'); $event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]); if (isset($event['allday'])) { @@ -435,12 +434,15 @@ class database_driver extends calendar_driver { $event = $this->_save_preprocess($event); $sql_set = array(); - $set_cols = array('all_day', 'recurrence', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat'); + $set_cols = array('all_day', 'recurrence_id', 'title', 'description', 'location', 'categories', 'free_busy', 'priority', 'sensitivity', 'attendees', 'alarms', 'notifyat'); foreach ($set_cols as $col) { if (isset($event[$col])) $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($event[$col]); } + if ($event['_recurrence']) + $sql_set[] = $this->rc->db->quote_identifier('recurrence') . '=' . $this->rc->db->quote($event['_recurrence']); + if ($event['_fromcalendar'] && $event['_fromcalendar'] != $event['calendar']) $sql_set[] = 'calendar_id=' . $this->rc->db->quote($event['calendar']); @@ -501,17 +503,13 @@ class database_driver extends calendar_driver // create new fake entries if ($event['recurrence']) { - // TODO: replace Horde classes with something that has less than 6'000 lines of code - $recurrence = new Horde_Date_Recurrence($event['start']); - $recurrence->fromRRule20($event['recurrence']); + // include library class + require_once($this->cal->home . '/lib/calendar_recurrence.php'); - foreach ((array)$event['_exdates'] as $exdate) - $recurrence->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate)); + $recurrence = new calendar_recurrence($this->cal, $event); $duration = $event['end'] - $event['start']; - $next = new Horde_Date($event['start']); - while ($next = $recurrence->nextActiveRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec))) { - $next_ts = $next->timestamp(); + while ($next_ts = $recurrence->next_start()) { $notify_at = $this->_get_notification(array('alarms' => $event['alarms'], 'start' => $next_ts, 'end' => $next_ts + $duration)); $query = $this->rc->db->query(sprintf( "INSERT INTO " . $this->db_events . " diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php index 7517963b..31b7e819 100644 --- a/plugins/calendar/drivers/kolab/kolab_calendar.php +++ b/plugins/calendar/drivers/kolab/kolab_calendar.php @@ -434,29 +434,15 @@ class kolab_calendar */ public function _get_recurring_events($event, $start, $end, $event_id = null) { - // use Horde classes to compute recurring instances - require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php'); + // include library class + require_once($this->cal->home . '/lib/calendar_recurrence.php'); - $recurrence = new Horde_Date_Recurrence($event['start']); - $recurrence->fromRRule20(calendar::to_rrule($event['recurrence'])); - - foreach ((array)$event['recurrence']['EXDATE'] as $exdate) - $recurrence->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate)); + $recurrence = new calendar_recurrence($this->cal, $event); $events = array(); $duration = $event['end'] - $event['start']; - $tz_offset = $event['allday'] ? $this->cal->gmt_offset - date('Z') : 0; - $next = new Horde_Date($event['start'] + $tz_offset); # shift all-day times to server timezone because computation operates in local TZ - $dst_start = $next->format('I'); - $hour = $next->hour; $i = 0; - while ($next = $recurrence->nextActiveRecurrence(array('year' => $next->year, 'month' => $next->month, 'mday' => $next->mday + 1, 'hour' => $next->hour, 'min' => $next->min, 'sec' => $next->sec))) { - if ($event['allday']) { - $next->hour = $hour; # fix time for all-day events - $next->min = 0; - } - $dst_diff = ($dst_start - $next->format('I')) * 3600; # consider difference in daylight saving between base event and recurring instance - $rec_start = $next->timestamp() - $tz_offset - $dst_diff; + while ($rec_start = $recurrence->next_start()) { $rec_end = $rec_start + $duration; $rec_id = $event['id'] . '-' . ++$i; diff --git a/plugins/calendar/lib/calendar_recurrence.php b/plugins/calendar/lib/calendar_recurrence.php new file mode 100644 index 00000000..ae151b74 --- /dev/null +++ b/plugins/calendar/lib/calendar_recurrence.php @@ -0,0 +1,86 @@ + + * @package calendar + * + * Copyright (C) 2011, Kolab Systems AG + * + * This program is free software; you can redistribute it and/or modify + * it under the terms of the GNU General Public License version 2 + * as published by the Free Software Foundation. + * + * This program is distributed in the hope that it will be useful, + * but WITHOUT ANY WARRANTY; without even the implied warranty of + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the + * GNU General Public License for more details. + * + * You should have received a copy of the GNU General Public License along + * with this program; if not, write to the Free Software Foundation, Inc., + * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. + */ +class calendar_recurrence +{ + private $cal; + private $event; + private $engine; + private $tz_offset = 0; + private $dst_start = false; + private $hour = 0; + + /** + * Default constructor + * + * @param object calendar The calendar plugin instance + * @param array The event object to operate on + */ + function __construct($cal, $event) + { + $this->cal = $cal; + + // use Horde classes to compute recurring instances + // TODO: replace with something that has less than 6'000 lines of code + require_once($this->cal->home . '/lib/Horde_Date_Recurrence.php'); + + $this->event = $event; + $this->engine = new Horde_Date_Recurrence($event['start']); + $this->engine->fromRRule20(calendar::to_rrule($event['recurrence'])); + + if (is_array($event['recurrence']['EXDATE'])) { + foreach ($event['recurrence']['EXDATE'] as $exdate) + $this->engine->addException(date('Y', $exdate), date('n', $exdate), date('j', $exdate)); + } + + $this->tz_offset = $event['allday'] ? $this->cal->gmt_offset - date('Z') : 0; + $this->next = new Horde_Date($event['start'] + $this->tz_offset); # shift all-day times to server timezone because computation operates in local TZ + $this->dst_start = $this->next->format('I'); + $this->hour = $this->next->hour; + } + + /** + * Get timestamp of the next occurence of this event + * + * @return mixed Unix timestamp or False if recurrence ended + */ + public function next_start() + { + $time = false; + if ($this->next && ($next = $this->engine->nextActiveRecurrence(array('year' => $this->next->year, 'month' => $this->next->month, 'mday' => $this->next->mday + 1, 'hour' => $this->next->hour, 'min' => $this->next->min, 'sec' => $this->next->sec)))) { + if ($this->event['allday']) { + $next->hour = $this->hour; # fix time for all-day events + $next->min = 0; + } + # $dst_diff = ($this->dst_start - $next->format('I')) * 3600; # consider difference in daylight saving between base event and recurring instance + $time = $next->timestamp() - $this->tz_offset; + $this->next = $next; + } + + return $time; + } + +}