diff --git a/plugins/calendar/calendar.js b/plugins/calendar/calendar.js index 1ef5894e..674dc5d2 100644 --- a/plugins/calendar/calendar.js +++ b/plugins/calendar/calendar.js @@ -45,20 +45,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { // private vars var day_clicked = 0; var ignore_click = false; - - // data loader - var load_events = function(calendar, start, end, callback) { - $.ajax({ - url: "./?_task=calendar&_action=plugin.load_events", - dataType: 'json', - data: { - source: calendar, - start: Math.round(start.getTime() / 1000), - end: Math.round(end.getTime() / 1000) - }, - success: callback - }); - }; // event details dialog (show only) var event_show_dialog = function(event) { @@ -438,7 +424,7 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { for (var id in rcmail.env.calendars) { cal = rcmail.env.calendars[id]; this.calendars[id] = $.extend({ - events: function(start, end, callback) { load_events(id, start, end, callback); }, + url: "./?_task=calendar&_action=plugin.load_events&source="+escape(id), editable: !cal.readonly, className: 'fc-event-cal-'+id, id: id @@ -455,8 +441,9 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { } }).data('id', id); $(li).click(function(e){ + var id = $(this).data('id'); rcmail.select_folder(id, me.selected_calendar, 'rcmlical'); - me.selected_calendar = $(this).data('id'); + me.selected_calendar = id; }).data('id', id); } diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php index 61cf97ff..a4342a82 100644 --- a/plugins/calendar/calendar.php +++ b/plugins/calendar/calendar.php @@ -521,7 +521,9 @@ class calendar extends rcube_plugin */ public static function parse_alaram_value($val) { - if (preg_match('/([+-])(\d+)([HMD])/', $val, $m)) + if ($val[0] == '@') + return array(substr($val, 1)); + else if (preg_match('/([+-])(\d+)([HMD])/', $val, $m)) return array($m[2], $m[1].$m[3]); return false; diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php index c479adfb..db0592e1 100644 --- a/plugins/calendar/drivers/calendar_driver.php +++ b/plugins/calendar/drivers/calendar_driver.php @@ -131,10 +131,26 @@ abstract class calendar_driver * * @param integer Current time (unix timestamp) * @param mixed List of calendar IDs to show alarms for (either as array or comma-separated string) - * @return array A list of alarms + * @return array A list of alarms, each encoded as hash array: + * id: Event identifier + * uid: Unique identifier of this event + * calendar: Calendar identifier to add event to (optional) + * start: Event start date/time as unix timestamp + * end: Event end date/time as unix timestamp + * allday: Boolean flag if this is an all-day event + * title: Event title/summary + * location: Location string */ abstract function pending_alarms($time, $calendars = null); + /** + * (User) feedback after showing an alarm notification + * This should mark the alarm as 'shown' or snooze it for the given amount of time + * + * @param string Event identifier + * @param integer Suspend the alarm for this number of seconds + */ + abstract function confirm_alarm($event_id, $snooze = 0); /** * Save an attachment related to the given event diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php index 812070d8..08d83e4f 100644 --- a/plugins/calendar/drivers/database/database_driver.php +++ b/plugins/calendar/drivers/database/database_driver.php @@ -139,8 +139,8 @@ class database_driver extends calendar_driver $event = $this->_save_preprocess($event); $query = $this->rc->db->query(sprintf( "INSERT INTO " . $this->db_events . " - (calendar_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, alarms) - VALUES (?, %s, %s, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?)", + (calendar_id, created, changed, uid, start, end, all_day, recurrence, title, description, location, categories, free_busy, priority, alarms, notifyat) + VALUES (?, %s, %s, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)", $this->rc->db->now(), $this->rc->db->now(), $this->rc->db->fromunixtime($event['start']), @@ -156,7 +156,8 @@ class database_driver extends calendar_driver strval($event['categories']), intval($event['free_busy']), intval($event['priority']), - $event['alarms'] + $event['alarms'], + $event['notifyat'] ); return $this->rc->db->insert_id($this->sequence_events); } @@ -176,7 +177,7 @@ class database_driver extends calendar_driver $event = $this->_save_preprocess($event); $query = $this->rc->db->query(sprintf( "UPDATE " . $this->db_events . " - SET changed=%s, start=%s, end=%s, all_day=?, recurrence=?, title=?, description=?, location=?, categories=?, free_busy=?, priority=?, alarms=? + SET changed=%s, start=%s, end=%s, all_day=?, recurrence=?, title=?, description=?, location=?, categories=?, free_busy=?, priority=?, alarms=?, notifyat=? WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")", $this->rc->db->now(), @@ -192,6 +193,7 @@ class database_driver extends calendar_driver intval($event['free_busy']), intval($event['priority']), $event['alarms'], + $event['notifyat'], $event['id'] ); return $this->rc->db->affected_rows($query); @@ -220,11 +222,38 @@ class database_driver extends calendar_driver } else if (is_string($event['recurrence'])) $rrule = $event['recurrence']; - + $event['recurrence'] = rtrim($rrule, ';'); $event['free_busy'] = intval($this->free_busy_map[strtolower($event['free_busy'])]); $event['allday'] = $event['allday'] ? 1 : 0; + // compute absolute time to notify the user + if ($event['alarms']) { + list($action, $trigger) = explode(':', $event['alarms']); + $notify = calendar::parse_alaram_value($trigger); + if (!empty($notify[1])){ // offset + $mult = 1; + switch ($notify[1]) { + case '-M': $mult = -60; break; + case '+M': $mult = 60; break; + case '-H': $mult = -3600; break; + case '+H': $mult = 3600; break; + case '-D': $mult = -86400; break; + case '+D': $mult = 86400; break; + } + $offset = $notify[0] * $mult; + $refdate = $mult > 0 ? $event['end'] : $event['start']; + $notify_at = $refdate + $offset; + } + else { // absolute timestamp + $notify_at = $notify[0]; + } + + $event['notifyat'] = date('Y-m-d H:i:s', $notify_at); + } + else + $event['notifyat'] = null; + return $event; } @@ -237,9 +266,10 @@ class database_driver extends calendar_driver public function move_event($event) { if (!empty($this->calendars)) { + $event = $this->_save_preprocess($event + (array)$this->get_event($event['id'])); $query = $this->rc->db->query(sprintf( "UPDATE " . $this->db_events . " - SET changed=%s, start=%s, end=%s, all_day=? + SET changed=%s, start=%s, end=%s, all_day=?, notifyat=? WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")", $this->rc->db->now(), @@ -247,6 +277,7 @@ class database_driver extends calendar_driver $this->rc->db->fromunixtime($event['end']) ), $event['allday'] ? 1 : 0, + $event['notifyat'], $event['id'] ); return $this->rc->db->affected_rows($query); @@ -264,15 +295,17 @@ class database_driver extends calendar_driver public function resize_event($event) { if (!empty($this->calendars)) { + $event = $this->_save_preprocess($event + (array)$this->get_event($event['id'])); $query = $this->rc->db->query(sprintf( "UPDATE " . $this->db_events . " - SET changed=%s, start=%s, end=%s + SET changed=%s, start=%s, end=%s, notifyat=? WHERE event_id=? AND calendar_id IN (" . $this->calendar_ids . ")", $this->rc->db->now(), $this->rc->db->fromunixtime($event['start']), $this->rc->db->fromunixtime($event['end']) ), + $event['notifyat'], $event['id'] ); return $this->rc->db->affected_rows($query); @@ -302,6 +335,27 @@ class database_driver extends calendar_driver return false; } + /** + * Return data of a specific event + * @param string Event ID + * @return array Hash array with event properties + */ + public function get_event($id) + { + $result = $this->rc->db->query(sprintf( + "SELECT * FROM " . $this->db_events . " + WHERE calendar_id IN (%s) + AND event_id=?", + $this->calendar_ids + ), + $id); + + if ($result && ($event = $this->rc->db->fetch_assoc($result))) + return $this->_read_postprocess($event); + + return false; + } + /** * Get event data * @@ -315,48 +369,57 @@ class database_driver extends calendar_driver $calendars = explode(',', $calendars); // only allow to select from calendars of this use - $calendars = array_intersect($calendars, array_keys($this->calendars)); + $calendar_ids = array_intersect($calendars, array_keys($this->calendars)); + array_walk($calendar_ids, array($this->rc->db, 'quote')); $events = array(); - $free_busy_map = array_flip($this->free_busy_map); - - if (!empty($calendars)) { + if (!empty($calendar_ids)) { $result = $this->rc->db->query(sprintf( "SELECT * FROM " . $this->db_events . " WHERE calendar_id IN (%s) AND start <= %s AND end >= %s", - $this->calendar_ids, + join(',', $calendar_ids), $this->rc->db->fromunixtime($end), $this->rc->db->fromunixtime($start) )); while ($result && ($event = $this->rc->db->fetch_assoc($result))) { - $event['id'] = $event['event_id']; - $event['start'] = strtotime($event['start']); - $event['end'] = strtotime($event['end']); - $event['free_busy'] = $free_busy_map[$event['free_busy']]; - $event['calendar'] = $event['calendar_id']; - - // parse recurrence rule - if ($event['recurrence'] && preg_match_all('/([A-Z]+)=([^;]+);?/', $event['recurrence'], $m, PREG_SET_ORDER)) { - $event['recurrence'] = array(); - foreach ($m as $rr) { - if (is_numeric($rr[2])) - $rr[2] = intval($rr[2]); - else if ($rr[1] == 'UNTIL') - $rr[2] = strtotime($rr[2]); - $event['recurrence'][$rr[1]] = $rr[2]; - } - } - - unset($event['event_id'], $event['calendar_id']); - $events[] = $event; + $events[] = $this->_read_postprocess($event); } } return $events; } + /** + * Convert sql record into a rcube style event object + */ + private function _read_postprocess($event) + { + $free_busy_map = array_flip($this->free_busy_map); + + $event['id'] = $event['event_id']; + $event['start'] = strtotime($event['start']); + $event['end'] = strtotime($event['end']); + $event['free_busy'] = $free_busy_map[$event['free_busy']]; + $event['calendar'] = $event['calendar_id']; + + // parse recurrence rule + if ($event['recurrence'] && preg_match_all('/([A-Z]+)=([^;]+);?/', $event['recurrence'], $m, PREG_SET_ORDER)) { + $event['recurrence'] = array(); + foreach ($m as $rr) { + if (is_numeric($rr[2])) + $rr[2] = intval($rr[2]); + else if ($rr[1] == 'UNTIL') + $rr[2] = strtotime($rr[2]); + $event['recurrence'][$rr[1]] = $rr[2]; + } + } + + unset($event['event_id'], $event['calendar_id']); + return $event; + } + /** * Search events * @@ -374,14 +437,63 @@ class database_driver extends calendar_driver */ public function pending_alarms($time, $calendars = null) { - // TBD. - return array(); + if (empty($calendars)) + $calendars = array_keys($this->calendars); + else if (is_string($calendars)) + $calendars = explode(',', $calendars); + + // only allow to select from calendars of this use + $calendar_ids = array_intersect($calendars, array_keys($this->calendars)); + array_walk($calendar_ids, array($this->rc->db, 'quote')); + + $alarms = array(); + if (!empty($calendar_ids)) { + $result = $this->rc->db->query(sprintf( + "SELECT * FROM " . $this->db_events . " + WHERE calendar_id IN (%s) + AND notifyat <= %s", + join(',', $calendar_ids), + $this->rc->db->fromunixtime($time) + )); + + while ($result && ($event = $this->rc->db->fetch_assoc($result))) + $alarms[] = $this->_read_postprocess($event); + } + + return $alarms; + } + + /** + * Feedback after showing/sending an alarm notification + * + * @see Driver:confirm_alarm() + */ + public function confirm_alarm($event_id, $snooze = 0) + { + // set new notifyat time + if ($snooze > 0) { + $event = $this->get_event($event_id); + $notify_at = date('Y-m-d H:i:s', strtotime($event['notifyat']) + $snooze); + } + else // unset notifyat value + $notify_at = null; + + $query = $this->rc->db->query(sprintf( + "UPDATE " . $this->db_events . " + SET changed=%s, notifyat=? + WHERE event_id=? + AND calendar_id IN (" . $this->calendar_ids . ")", + $this->rc->db->now()), + $notify_at, + $event_id + ); + return $this->rc->db->affected_rows($query); } /** * Save an attachment related to the given event */ - function add_attachment($attachment, $event_id) + public function add_attachment($attachment, $event_id) { // TBD. return false; @@ -390,7 +502,7 @@ class database_driver extends calendar_driver /** * Remove a specific attachment from the given event */ - function remove_attachment($attachment, $event_id) + public function remove_attachment($attachment, $event_id) { // TBD. return false; diff --git a/plugins/calendar/drivers/database/sql/mysql.sql b/plugins/calendar/drivers/database/sql/mysql.sql index 31fb07d8..5956ffdb 100644 --- a/plugins/calendar/drivers/database/sql/mysql.sql +++ b/plugins/calendar/drivers/database/sql/mysql.sql @@ -41,6 +41,7 @@ CREATE TABLE `events` ( `priority` tinyint(1) NOT NULL DEFAULT '1', `alarms` varchar(255) DEFAULT NULL, `attendees` text DEFAULT NULL, + `notifyat` datetime NOT NULL DEFAULT '1000-01-01 00:00:00', PRIMARY KEY(`event_id`), CONSTRAINT `fk_events_calendar_id` FOREIGN KEY (`calendar_id`) REFERENCES `calendars`(`calendar_id`) ON DELETE CASCADE ON UPDATE CASCADE diff --git a/plugins/calendar/drivers/database/sql/postgresql.sql b/plugins/calendar/drivers/database/sql/postgresql.sql index 58681368..5b75e545 100644 --- a/plugins/calendar/drivers/database/sql/postgresql.sql +++ b/plugins/calendar/drivers/database/sql/postgresql.sql @@ -55,7 +55,8 @@ CREATE TABLE events ( free_busy smallint NOT NULL DEFAULT 0, priority smallint NOT NULL DEFAULT 1, alarms varchar(255) DEFAULT NULL, - attendees text DEFAULT NULL + attendees text DEFAULT NULL, + notifyat timestamp without time zone DEFAULT now() NOT NULL PRIMARY KEY (event_id) ); diff --git a/plugins/calendar/drivers/database/sql/sqlite.sql b/plugins/calendar/drivers/database/sql/sqlite.sql index 22ef23d2..a102f279 100644 --- a/plugins/calendar/drivers/database/sql/sqlite.sql +++ b/plugins/calendar/drivers/database/sql/sqlite.sql @@ -41,6 +41,7 @@ CREATE TABLE events ( priority tinyint(1) NOT NULL default '1', alarms varchar(255) default NULL, attendees text default NULL, + notifyat datetime NOT NULL default '1000-01-01 00:00:00', CONSTRAINT fk_events_calendar_id FOREIGN KEY (calendar_id) REFERENCES calendars(calendar_id) ); diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php index 3edcf6f9..1a23da31 100644 --- a/plugins/calendar/drivers/kolab/kolab_driver.php +++ b/plugins/calendar/drivers/kolab/kolab_driver.php @@ -233,6 +233,17 @@ class kolab_driver extends calendar_driver return array(); } + /** + * Feedback after showing/sending an alarm notification + * + * @see Driver:confirm_alarm() + */ + public function confirm_alarm($event_id, $snooze = 0) + { + // TBD. + return false; + } + /** * Save an attachment related to the given event */