Refactored alarms in calendar and tasks to support multiple alarms. Moved redundant functions to libcalendaring
This commit is contained in:
parent
e648bee7aa
commit
93d2b69bb9
26 changed files with 547 additions and 197 deletions
|
@ -763,7 +763,7 @@ class calendar extends rcube_plugin
|
|||
case "new":
|
||||
// create UID for new event
|
||||
$event['uid'] = $this->generate_uid();
|
||||
$this->prepare_event($event, $action);
|
||||
$this->write_preprocess($event, $action);
|
||||
if ($success = $this->driver->new_event($event)) {
|
||||
$event['id'] = $event['uid'];
|
||||
$this->cleanup_event($event);
|
||||
|
@ -772,20 +772,20 @@ class calendar extends rcube_plugin
|
|||
break;
|
||||
|
||||
case "edit":
|
||||
$this->prepare_event($event, $action);
|
||||
$this->write_preprocess($event, $action);
|
||||
if ($success = $this->driver->edit_event($event))
|
||||
$this->cleanup_event($event);
|
||||
$reload = $success && ($event['recurrence'] || $event['_savemode'] || $event['_fromcalendar']) ? 2 : 1;
|
||||
break;
|
||||
|
||||
case "resize":
|
||||
$this->prepare_event($event, $action);
|
||||
$this->write_preprocess($event, $action);
|
||||
$success = $this->driver->resize_event($event);
|
||||
$reload = $event['_savemode'] ? 2 : 1;
|
||||
break;
|
||||
|
||||
case "move":
|
||||
$this->prepare_event($event, $action);
|
||||
$this->write_preprocess($event, $action);
|
||||
$success = $this->driver->move_event($event);
|
||||
$reload = $success && $event['_savemode'] ? 2 : 1;
|
||||
break;
|
||||
|
@ -1327,8 +1327,10 @@ class calendar extends rcube_plugin
|
|||
private function _client_event($event, $addcss = false)
|
||||
{
|
||||
// compose a human readable strings for alarms_text and recurrence_text
|
||||
if ($event['alarms'])
|
||||
$event['alarms_text'] = libcalendaring::alarms_text($event['alarms']);
|
||||
if ($event['valarms']) {
|
||||
$event['alarms_text'] = libcalendaring::alarms_text($event['valarms']);
|
||||
$event['valarms'] = libcalendaring::to_client_alarms($event['valarms']);
|
||||
}
|
||||
if ($event['recurrence']) {
|
||||
$event['recurrence_text'] = $this->_recurrence_text($event['recurrence']);
|
||||
if ($event['recurrence']['UNTIL'])
|
||||
|
@ -1555,7 +1557,7 @@ class calendar extends rcube_plugin
|
|||
/**
|
||||
* Prepares new/edited event properties before save
|
||||
*/
|
||||
private function prepare_event(&$event, $action)
|
||||
private function write_preprocess(&$event, $action)
|
||||
{
|
||||
// convert dates into DateTime objects in user's current timezone
|
||||
$event['start'] = new DateTime($event['start'], $this->timezone);
|
||||
|
@ -1584,6 +1586,11 @@ class calendar extends rcube_plugin
|
|||
}, $event['recurrence']['RDATE']);
|
||||
}
|
||||
|
||||
// convert the submitted alarm values
|
||||
if ($event['valarms']) {
|
||||
$event['valarms'] = libcalendaring::from_client_alarms($event['valarms']);
|
||||
}
|
||||
|
||||
$attachments = array();
|
||||
$eventid = 'cal:'.$event['id'];
|
||||
if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $eventid) {
|
||||
|
|
|
@ -81,7 +81,6 @@ function rcube_calendar_ui(settings)
|
|||
var date2unixtime = this.date2unixtime;
|
||||
var fromunixtime = this.fromunixtime;
|
||||
var parseISO8601 = this.parseISO8601;
|
||||
var init_alarms_edit = this.init_alarms_edit;
|
||||
|
||||
|
||||
/*** private methods ***/
|
||||
|
@ -311,7 +310,7 @@ function rcube_calendar_ui(settings)
|
|||
if (event.recurrence && event.recurrence_text)
|
||||
$('#event-repeat').show().children('.event-text').html(Q(event.recurrence_text));
|
||||
|
||||
if (event.alarms && event.alarms_text)
|
||||
if (event.valarms && event.alarms_text)
|
||||
$('#event-alarm').show().children('.event-text').html(Q(event.alarms_text));
|
||||
|
||||
if (calendar.name)
|
||||
|
@ -519,34 +518,10 @@ function rcube_calendar_ui(settings)
|
|||
else {
|
||||
allday.checked = false;
|
||||
}
|
||||
|
||||
|
||||
// set alarm(s)
|
||||
// TODO: support multiple alarm entries
|
||||
if (event.alarms || action != 'new') {
|
||||
if (typeof event.alarms == 'string')
|
||||
event.alarms = event.alarms.split(';');
|
||||
|
||||
var valarms = event.alarms || [''];
|
||||
for (var alarm, i=0; i < valarms.length; i++) {
|
||||
alarm = String(valarms[i]).split(':');
|
||||
if (!alarm[1] && alarm[0]) alarm[1] = 'DISPLAY';
|
||||
$('#eventedit select.edit-alarm-type').val(alarm[1]);
|
||||
|
||||
if (alarm[0].match(/@(\d+)/)) {
|
||||
var ondate = fromunixtime(parseInt(RegExp.$1));
|
||||
$('#eventedit select.edit-alarm-offset').val('@');
|
||||
$('#eventedit input.edit-alarm-date').val($.fullCalendar.formatDate(ondate, settings['date_format']));
|
||||
$('#eventedit input.edit-alarm-time').val($.fullCalendar.formatDate(ondate, settings['time_format']));
|
||||
}
|
||||
else if (alarm[0].match(/([-+])(\d+)([MHD])/)) {
|
||||
$('#eventedit input.edit-alarm-value').val(RegExp.$2);
|
||||
$('#eventedit select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3);
|
||||
}
|
||||
}
|
||||
}
|
||||
// set correct visibility by triggering onchange handlers
|
||||
$('#eventedit select.edit-alarm-type, #eventedit select.edit-alarm-offset').change();
|
||||
|
||||
me.set_alarms_edit('#edit-alarms', action != 'new' && event.valarms && calendar.alarms ? event.valarms : []);
|
||||
|
||||
// enable/disable alarm property according to backend support
|
||||
$('#edit-alarms')[(calendar.alarms ? 'show' : 'hide')]();
|
||||
|
||||
|
@ -690,23 +665,12 @@ function rcube_calendar_ui(settings)
|
|||
sensitivity: sensitivity.val(),
|
||||
status: eventstatus.val(),
|
||||
recurrence: '',
|
||||
alarms: '',
|
||||
valarms: me.serialize_alarms('#edit-alarms'),
|
||||
attendees: event_attendees,
|
||||
deleted_attachments: rcmail.env.deleted_attachments,
|
||||
attachments: []
|
||||
};
|
||||
|
||||
// serialize alarm settings
|
||||
// TODO: support multiple alarm entries
|
||||
var alarm = $('#eventedit select.edit-alarm-type').val();
|
||||
if (alarm) {
|
||||
var val, offset = $('#eventedit select.edit-alarm-offset').val();
|
||||
if (offset == '@')
|
||||
data.alarms = '@' + date2unixtime(parse_datetime($('#eventedit input.edit-alarm-time').val(), $('#eventedit input.edit-alarm-date').val())) + ':' + alarm;
|
||||
else if ((val = parseInt($('#eventedit input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
|
||||
data.alarms = offset[0] + val + offset[1] + ':' + alarm;
|
||||
}
|
||||
|
||||
// uploaded attachments list
|
||||
for (var i in rcmail.env.attachments)
|
||||
if (i.match(/^rcmfile(.+)/))
|
||||
|
@ -3260,7 +3224,7 @@ function rcube_calendar_ui(settings)
|
|||
});
|
||||
|
||||
// register events on alarm fields
|
||||
init_alarms_edit('#eventedit');
|
||||
me.init_alarms_edit('#edit-alarms');
|
||||
|
||||
// toggle recurrence frequency forms
|
||||
$('#edit-recurrence-frequency').change(function(e){
|
||||
|
|
|
@ -3,12 +3,11 @@
|
|||
*
|
||||
* Plugin to add a calendar to Roundcube.
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Lazlo Westerhof
|
||||
* @author Thomas Bruederli
|
||||
* @url http://rc-calendar.lazlo.me
|
||||
* @licence GNU AGPL
|
||||
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||
* @copyright (c) 2014 Kolab Systems AG
|
||||
*
|
||||
**/
|
||||
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
*
|
||||
* Plugin to add a calendar to RoundCube.
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Lazlo Westerhof
|
||||
* @author Albert Lee
|
||||
* @author Aleksander Machniak <machniak@kolabsys.com>
|
||||
* @url http://rc-calendar.lazlo.me
|
||||
* @licence GNU AGPL
|
||||
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||
* @copyright (c) 2014 Kolab Systems AG
|
||||
*
|
||||
**/
|
||||
|
||||
|
|
|
@ -3,13 +3,12 @@
|
|||
*
|
||||
* Plugin to add a calendar to Roundcube.
|
||||
*
|
||||
* @version @package_version@
|
||||
* @author Lazlo Westerhof
|
||||
* @author Thomas Bruederli
|
||||
* @author Albert Lee
|
||||
* @url http://rc-calendar.lazlo.me
|
||||
* @licence GNU AGPL
|
||||
* @copyright (c) 2010 Lazlo Westerhof - Netherlands
|
||||
* @copyright (c) 2014 Kolab Systems AG
|
||||
*
|
||||
**/
|
||||
|
||||
|
|
|
@ -459,10 +459,14 @@ class database_driver extends calendar_driver
|
|||
if (isset($event['allday'])) {
|
||||
$event['all_day'] = $event['allday'] ? 1 : 0;
|
||||
}
|
||||
|
||||
|
||||
// compute absolute time to notify the user
|
||||
$event['notifyat'] = $this->_get_notification($event);
|
||||
|
||||
|
||||
if (is_array($event['valarms'])) {
|
||||
$event['alarms'] = $this->serialize_alarms($event['valarms']);
|
||||
}
|
||||
|
||||
// process event attendees
|
||||
$_attendees = '';
|
||||
foreach ((array)$event['attendees'] as $attendee) {
|
||||
|
@ -484,10 +488,10 @@ class database_driver extends calendar_driver
|
|||
*/
|
||||
private function _get_notification($event)
|
||||
{
|
||||
if ($event['alarms'] && $event['start'] > new DateTime()) {
|
||||
if ($event['valarms'] && $event['start'] > new DateTime()) {
|
||||
$alarm = libcalendaring::get_next_alarm($event);
|
||||
|
||||
if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
|
||||
if ($alarm['time'] && in_array($alarm['action'], $this->alarm_types))
|
||||
return date('Y-m-d H:i:s', $alarm['time']);
|
||||
}
|
||||
|
||||
|
@ -877,7 +881,12 @@ class database_driver extends calendar_driver
|
|||
else {
|
||||
$event['attendees'] = array();
|
||||
}
|
||||
|
||||
|
||||
// decode serialized alarms
|
||||
if ($event['alarms']) {
|
||||
$event['valarms'] = $this->unserialize_alarms($event['alarms']);
|
||||
}
|
||||
|
||||
unset($event['event_id'], $event['calendar_id'], $event['notifyat'], $event['all_day'], $event['_attachments']);
|
||||
return $event;
|
||||
}
|
||||
|
@ -1088,4 +1097,48 @@ class database_driver extends calendar_driver
|
|||
return $this->rc->db->affected_rows($query);
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to serialize the list of alarms into a string
|
||||
*/
|
||||
private function serialize_alarms($valarms)
|
||||
{
|
||||
foreach ((array)$valarms as $i => $alarm) {
|
||||
if ($alarm['trigger'] instanceof DateTime) {
|
||||
$valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c');
|
||||
}
|
||||
}
|
||||
|
||||
return $valarms ? json_encode($valarms) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to decode a serialized list of alarms
|
||||
*/
|
||||
private function unserialize_alarms($alarms)
|
||||
{
|
||||
// decode json serialized alarms
|
||||
if ($alarms && $alarms[0] == '[') {
|
||||
$valarms = json_decode($alarms, true);
|
||||
foreach ($valarms as $i => $alarm) {
|
||||
if ($alarm['trigger'][0] == '@') {
|
||||
try {
|
||||
$valarms[$i]['trigger'] = new DateTime(substr($alarm['trigger'], 1));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
unset($valarms[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// convert legacy alarms data
|
||||
else if (strlen($alarms)) {
|
||||
list($trigger, $action) = explode(':', $alarms, 2);
|
||||
if ($trigger = libcalendaring::parse_alaram_value($trigger)) {
|
||||
$valarms = array(array('action' => $action, 'trigger' => $trigger[3] ?: $trigger[0]));
|
||||
}
|
||||
}
|
||||
|
||||
return $valarms;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -7,11 +7,11 @@
|
|||
**/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS `kolab_alarms` (
|
||||
`event_id` VARCHAR(255) NOT NULL,
|
||||
`alarm_id` VARCHAR(255) NOT NULL,
|
||||
`user_id` int(10) UNSIGNED NOT NULL,
|
||||
`notifyat` DATETIME DEFAULT NULL,
|
||||
`dismissed` TINYINT(3) UNSIGNED NOT NULL DEFAULT '0',
|
||||
PRIMARY KEY(`event_id`),
|
||||
PRIMARY KEY(`alarm_id`),
|
||||
CONSTRAINT `fk_kolab_alarms_user_id` FOREIGN KEY (`user_id`)
|
||||
REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
|
||||
) /*!40000 ENGINE=INNODB */;
|
||||
|
|
1
plugins/calendar/drivers/kolab/SQL/mysql/2014041700.sql
Normal file
1
plugins/calendar/drivers/kolab/SQL/mysql/2014041700.sql
Normal file
|
@ -0,0 +1 @@
|
|||
ALTER TABLE `kolab_alarms` CHANGE `event_id` `alarm_id` VARCHAR(255) NOT NULL;
|
|
@ -6,7 +6,7 @@
|
|||
**/
|
||||
|
||||
CREATE TABLE IF NOT EXISTS kolab_alarms (
|
||||
event_id character varying(255) NOT NULL,
|
||||
alarm_id character varying(255) NOT NULL,
|
||||
user_id integer NOT NULL
|
||||
REFERENCES users (user_id) ON DELETE CASCADE ON UPDATE CASCADE,
|
||||
notifyat timestamp without time zone DEFAULT NULL,
|
||||
|
|
|
@ -33,7 +33,7 @@ class kolab_driver extends calendar_driver
|
|||
public $freebusy = true;
|
||||
public $attachments = true;
|
||||
public $undelete = true;
|
||||
public $alarm_types = array('DISPLAY');
|
||||
public $alarm_types = array('DISPLAY','AUDIO');
|
||||
public $categoriesimmutable = true;
|
||||
|
||||
private $rc;
|
||||
|
@ -834,7 +834,7 @@ class kolab_driver extends calendar_driver
|
|||
|
||||
$time = $slot + $interval;
|
||||
|
||||
$events = array();
|
||||
$candidates = array();
|
||||
$query = array(array('tags', '=', 'x-has-alarms'));
|
||||
foreach ($this->calendars as $cid => $calendar) {
|
||||
// skip calendars with alarms disabled
|
||||
|
@ -844,41 +844,48 @@ class kolab_driver extends calendar_driver
|
|||
foreach ($calendar->list_events($time, $time + 86400 * 365, null, 1, $query) as $e) {
|
||||
// add to list if alarm is set
|
||||
$alarm = libcalendaring::get_next_alarm($e);
|
||||
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
|
||||
$id = $e['id'];
|
||||
$events[$id] = $e;
|
||||
$events[$id]['notifyat'] = $alarm['time'];
|
||||
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && in_array($alarm['action'], $this->alarm_types)) {
|
||||
$id = $alarm['id']; // use alarm-id as primary identifier
|
||||
$candidates[$id] = array(
|
||||
'id' => $id,
|
||||
'title' => $e['title'],
|
||||
'location' => $e['location'],
|
||||
'start' => $e['start'],
|
||||
'end' => $e['end'],
|
||||
'notifyat' => $alarm['time'],
|
||||
'action' => $alarm['action'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get alarm information stored in local database
|
||||
if (!empty($events)) {
|
||||
$event_ids = array_map(array($this->rc->db, 'quote'), array_keys($events));
|
||||
if (!empty($candidates)) {
|
||||
$alarm_ids = array_map(array($this->rc->db, 'quote'), array_keys($candidates));
|
||||
$result = $this->rc->db->query(sprintf(
|
||||
"SELECT * FROM kolab_alarms
|
||||
WHERE event_id IN (%s) AND user_id=?",
|
||||
join(',', $event_ids),
|
||||
WHERE alarm_id IN (%s) AND user_id=?",
|
||||
join(',', $alarm_ids),
|
||||
$this->rc->db->now()
|
||||
),
|
||||
$this->rc->user->ID
|
||||
);
|
||||
|
||||
while ($result && ($e = $this->rc->db->fetch_assoc($result))) {
|
||||
$dbdata[$e['event_id']] = $e;
|
||||
$dbdata[$e['alarm_id']] = $e;
|
||||
}
|
||||
}
|
||||
|
||||
$alarms = array();
|
||||
foreach ($events as $id => $e) {
|
||||
// skip dismissed
|
||||
foreach ($candidates as $id => $alarm) {
|
||||
// skip dismissed alarms
|
||||
if ($dbdata[$id]['dismissed'])
|
||||
continue;
|
||||
|
||||
// snooze function may have shifted alarm time
|
||||
$notifyat = $dbdata[$id]['notifyat'] ? strtotime($dbdata[$id]['notifyat']) : $e['notifyat'];
|
||||
$notifyat = $dbdata[$id]['notifyat'] ? strtotime($dbdata[$id]['notifyat']) : $alarm['notifyat'];
|
||||
if ($notifyat <= $time)
|
||||
$alarms[] = $e;
|
||||
$alarms[] = $alarm;
|
||||
}
|
||||
|
||||
return $alarms;
|
||||
|
@ -889,13 +896,13 @@ class kolab_driver extends calendar_driver
|
|||
*
|
||||
* @see calendar_driver::dismiss_alarm()
|
||||
*/
|
||||
public function dismiss_alarm($event_id, $snooze = 0)
|
||||
public function dismiss_alarm($alarm_id, $snooze = 0)
|
||||
{
|
||||
// delete old alarm entry
|
||||
$this->rc->db->query(
|
||||
"DELETE FROM kolab_alarms
|
||||
WHERE event_id=? AND user_id=?",
|
||||
$event_id,
|
||||
WHERE alarm_id=? AND user_id=?",
|
||||
$alarm_id,
|
||||
$this->rc->user->ID
|
||||
);
|
||||
|
||||
|
@ -904,9 +911,9 @@ class kolab_driver extends calendar_driver
|
|||
|
||||
$query = $this->rc->db->query(
|
||||
"INSERT INTO kolab_alarms
|
||||
(event_id, user_id, dismissed, notifyat)
|
||||
(alarm_id, user_id, dismissed, notifyat)
|
||||
VALUES(?, ?, ?, ?)",
|
||||
$event_id,
|
||||
$alarm_id,
|
||||
$this->rc->user->ID,
|
||||
$snooze > 0 ? 0 : 1,
|
||||
$notifyat
|
||||
|
|
|
@ -535,6 +535,44 @@ td.topalign {
|
|||
vertical-align: top;
|
||||
}
|
||||
|
||||
#eventedit .edit-alarm-item {
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
margin-bottom: 2px;
|
||||
}
|
||||
|
||||
#eventedit .edit-alarm-buttons {
|
||||
position: absolute;
|
||||
top: 2px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
#eventedit .edit-alarm-buttons a.iconlink {
|
||||
display: none;
|
||||
width: 18px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
text-indent: -5000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
#eventedit .edit-alarm-buttons a.add-alarm {
|
||||
background: url(images/plus.png) 1px 1px no-repeat;
|
||||
}
|
||||
|
||||
#eventedit .edit-alarm-buttons a.delete-alarm {
|
||||
background: url(images/delete.png) 1px 1px no-repeat;
|
||||
}
|
||||
|
||||
#eventedit .edit-alarm-buttons a.delete-alarm,
|
||||
#eventedit .first .edit-alarm-buttons a.add-alarm {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
#eventedit .first .edit-alarm-buttons a.delete-alarm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
#eventedit label.weekday,
|
||||
#eventedit label.monthday {
|
||||
min-width: 3em;
|
||||
|
|
BIN
plugins/calendar/skins/classic/images/delete.png
Normal file
BIN
plugins/calendar/skins/classic/images/delete.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 758 B |
BIN
plugins/calendar/skins/classic/images/plus.png
Normal file
BIN
plugins/calendar/skins/classic/images/plus.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.5 KiB |
|
@ -41,8 +41,14 @@
|
|||
<input type="text" name="endtime" size="6" id="edit-endtime" />
|
||||
</div>
|
||||
<div class="event-section" id="edit-alarms">
|
||||
<label for="edit-alarm"><roundcube:label name="calendar.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<div class="edit-alarm-item first">
|
||||
<label for="edit-alarm"><roundcube:label name="calendar.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<span class="edit-alarm-buttons">
|
||||
<a href="#add" class="iconlink add add-alarm">+</a>
|
||||
<a href="#delete" class="iconlink delete delete-alarm">-</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-section" id="calendar-select">
|
||||
<label for="edit-calendar"><roundcube:label name="calendar.calendar" /></label>
|
||||
|
|
|
@ -37,8 +37,14 @@
|
|||
<input type="text" name="endtime" size="6" id="edit-endtime" />
|
||||
</div>
|
||||
<div class="event-section" id="edit-alarms">
|
||||
<label for="edit-alarm"><roundcube:label name="calendar.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<div class="edit-alarm-item first">
|
||||
<label><roundcube:label name="calendar.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<span class="edit-alarm-buttons">
|
||||
<a href="#add" class="iconlink add add-alarm">+</a>
|
||||
<a href="#delete" class="iconlink delete delete-alarm">-</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-section" id="calendar-select">
|
||||
<label for="edit-calendar"><roundcube:label name="calendar.calendar" /></label>
|
||||
|
|
|
@ -311,9 +311,87 @@ function rcube_libcalendaring(settings)
|
|||
$(this).parent().find('.edit-alarm-value').prop('disabled', mode == 'show');
|
||||
});
|
||||
|
||||
$(prefix+' .edit-alarm-date').datepicker(datepicker_settings);
|
||||
$(prefix+' .edit-alarm-date').removeClass('hasDatepicker').removeAttr('id').datepicker(datepicker_settings);
|
||||
|
||||
$(prefix).on('click', 'a.delete-alarm', function(e){
|
||||
if ($(this).closest('.edit-alarm-item').siblings().length > 0) {
|
||||
$(this).closest('.edit-alarm-item').remove();
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
$(prefix).on('click', 'a.add-alarm', function(e){
|
||||
var i = $(this).closest('.edit-alarm-item').siblings().length + 1;
|
||||
var item = $(this).closest('.edit-alarm-item').clone(false)
|
||||
.removeClass('first')
|
||||
.appendTo(prefix);
|
||||
|
||||
me.init_alarms_edit(prefix + ' .edit-alarm-item:eq(' + i + ')');
|
||||
$('select.edit-alarm-type, select.edit-alarm-offset', item).change();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
this.set_alarms_edit = function(prefix, valarms)
|
||||
{
|
||||
$(prefix + ' .edit-alarm-item:gt(0)').remove();
|
||||
|
||||
var i, alarm, domnode, val, offset;
|
||||
for (i=0; i < valarms.length; i++) {
|
||||
alarm = valarms[i];
|
||||
if (!alarm.action)
|
||||
alarm.action = 'DISPLAY';
|
||||
|
||||
if (i == 0) {
|
||||
domnode = $(prefix + ' .edit-alarm-item').eq(0);
|
||||
}
|
||||
else {
|
||||
domnode = $(prefix + ' .edit-alarm-item').eq(0).clone(false).removeClass('first').appendTo(prefix);
|
||||
this.init_alarms_edit(prefix + ' .edit-alarm-item:eq(' + i + ')');
|
||||
}
|
||||
|
||||
$('select.edit-alarm-type', domnode).val(alarm.action);
|
||||
|
||||
if (String(alarm.trigger).match(/@(\d+)/)) {
|
||||
var ondate = this.fromunixtime(parseInt(RegExp.$1));
|
||||
$('select.edit-alarm-offset', domnode).val('@');
|
||||
$('input.edit-alarm-value', domnode).val('');
|
||||
$('input.edit-alarm-date', domnode).val(this.format_datetime(ondate, 1));
|
||||
$('input.edit-alarm-time', domnode).val(this.format_datetime(ondate, 2));
|
||||
}
|
||||
else if (String(alarm.trigger).match(/([-+])(\d+)([MHDS])/)) {
|
||||
val = RegExp.$2; offset = ''+RegExp.$1+RegExp.$3;
|
||||
$('input.edit-alarm-value', domnode).val(val);
|
||||
$('select.edit-alarm-offset', domnode).val(offset);
|
||||
}
|
||||
}
|
||||
|
||||
// set correct visibility by triggering onchange handlers
|
||||
$(prefix + ' select.edit-alarm-type, ' + prefix + ' select.edit-alarm-offset').change();
|
||||
};
|
||||
|
||||
this.serialize_alarms = function(prefix)
|
||||
{
|
||||
var valarms = [];
|
||||
|
||||
$(prefix + ':visible .edit-alarm-item').each(function(i, elem){
|
||||
var val, offset, alarm = { action: $('select.edit-alarm-type', elem).val() };
|
||||
if (alarm.action) {
|
||||
offset = $('select.edit-alarm-offset', elem).val();
|
||||
if (offset == '@') {
|
||||
alarm.trigger = '@' + me.date2unixtime(me.parse_datetime($('input.edit-alarm-time', elem).val(), $('input.edit-alarm-date', elem).val()));
|
||||
}
|
||||
else if (!isNaN((val = parseInt($('input.edit-alarm-value', elem).val()))) && val >= 0) {
|
||||
alarm.trigger = offset[0] + val + offset[1];
|
||||
}
|
||||
|
||||
valarms.push(alarm);
|
||||
}
|
||||
});
|
||||
|
||||
return valarms;
|
||||
};
|
||||
|
||||
|
||||
/***** Alarms handling *****/
|
||||
|
||||
|
|
|
@ -345,25 +345,89 @@ class libcalendaring extends rcube_plugin
|
|||
public static function parse_alaram_value($val)
|
||||
{
|
||||
if ($val[0] == '@') {
|
||||
return array(substr($val, 1));
|
||||
return array(new DateTime($val));
|
||||
}
|
||||
else if (preg_match('/([+-])P?(T?\d+[HMSDW])+/', $val, $m) && preg_match_all('/T?(\d+)([HMSDW])/', $val, $m2, PREG_SET_ORDER)) {
|
||||
else if (preg_match('/([+-]?)P?(T?\d+[HMSDW])+/', $val, $m) && preg_match_all('/T?(\d+)([HMSDW])/', $val, $m2, PREG_SET_ORDER)) {
|
||||
if ($m[1] == '')
|
||||
$m[1] = '+';
|
||||
foreach ($m2 as $seg) {
|
||||
$prefix = $seg[2] == 'D' || $seg[2] == 'W' ? 'P' : 'PT';
|
||||
if ($seg[1] > 0) { // ignore zero values
|
||||
return array($seg[1], $m[1].$seg[2], $m[1].$seg[1].$seg[2]);
|
||||
return array($seg[1], $m[1].$seg[2], $m[1].$seg[1].$seg[2], $m[1].$prefix.$seg[1].$seg[2]);
|
||||
}
|
||||
}
|
||||
|
||||
// return zero value nevertheless
|
||||
return array($seg[1], $m[1].$seg[2], $m[1].$seg[1].$seg[2], $m[1].$prefix.$seg[1].$seg[2]);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Convert the alarms list items to be processed on the client
|
||||
*/
|
||||
public static function to_client_alarms($valarms)
|
||||
{
|
||||
return array_map(function($alarm){
|
||||
if ($alarm['trigger'] instanceof DateTime) {
|
||||
$alarm['trigger'] = '@' . $alarm['trigger']->format('U');
|
||||
}
|
||||
else if ($trigger = self::parse_alaram_value($alarm['trigger'])) {
|
||||
$alarm['trigger'] = $trigger[2];
|
||||
}
|
||||
return $alarm;
|
||||
}, (array)$valarms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Process the alarms values submitted by the client
|
||||
*/
|
||||
public static function from_client_alarms($valarms)
|
||||
{
|
||||
return array_map(function($alarm){
|
||||
if ($alarm['trigger'][0] == '@') {
|
||||
try { $alarm['trigger'] = new DateTime($alarm['trigger']); }
|
||||
catch (Exception $e) { /* handle this ? */ }
|
||||
}
|
||||
else if ($trigger = libcalendaring::parse_alaram_value($alarm['trigger'])) {
|
||||
$alarm['trigger'] = $trigger[3];
|
||||
}
|
||||
return $alarm;
|
||||
}, (array)$valarms);
|
||||
}
|
||||
|
||||
/**
|
||||
* Render localized text for alarm settings
|
||||
*/
|
||||
public static function alarms_text($alarm)
|
||||
public static function alarms_text($alarms)
|
||||
{
|
||||
list($trigger, $action) = explode(':', $alarm);
|
||||
if (is_array($alarms) && is_array($alarms[0])) {
|
||||
$texts = array();
|
||||
foreach ($alarms as $alarm) {
|
||||
if ($text = self::alarm_text($alarm))
|
||||
$texts[] = $text;
|
||||
}
|
||||
|
||||
return join(', ', $texts);
|
||||
}
|
||||
else {
|
||||
return self::alarm_text($alarms);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Render localized text for a single alarm property
|
||||
*/
|
||||
public static function alarm_text($alarm)
|
||||
{
|
||||
if (is_string($alarm)) {
|
||||
list($trigger, $action) = explode(':', $alarm);
|
||||
}
|
||||
else {
|
||||
$trigger = $alarm['trigger'];
|
||||
$action = $alarm['action'];
|
||||
}
|
||||
|
||||
$text = '';
|
||||
$rcube = rcube::get_instance();
|
||||
|
@ -375,19 +439,33 @@ class libcalendaring extends rcube_plugin
|
|||
case 'DISPLAY':
|
||||
$text = $rcube->gettext('libcalendaring.alarmdisplay');
|
||||
break;
|
||||
case 'AUDIO':
|
||||
$text = $rcube->gettext('libcalendaring.alarmaudio');
|
||||
break;
|
||||
}
|
||||
|
||||
if (preg_match('/@(\d+)/', $trigger, $m)) {
|
||||
if ($trigger instanceof DateTime) {
|
||||
$text .= ' ' . $rcube->gettext(array(
|
||||
'name' => 'libcalendaring.alarmat',
|
||||
'vars' => array('datetime' => $rcube->format_date($trigger))
|
||||
));
|
||||
}
|
||||
else if (preg_match('/@(\d+)/', $trigger, $m)) {
|
||||
$text .= ' ' . $rcube->gettext(array(
|
||||
'name' => 'libcalendaring.alarmat',
|
||||
'vars' => array('datetime' => $rcube->format_date($m[1]))
|
||||
));
|
||||
}
|
||||
else if ($val = self::parse_alaram_value($trigger)) {
|
||||
$text .= ' ' . intval($val[0]) . ' ' . $rcube->gettext('libcalendaring.trigger' . $val[1]);
|
||||
// TODO: for all-day events say 'on date of event at XX' ?
|
||||
if ($val[0] == 0)
|
||||
$text .= ' ' . $rcube->gettext('libcalendaring.triggerattime');
|
||||
else
|
||||
$text .= ' ' . intval($val[0]) . ' ' . $rcube->gettext('libcalendaring.trigger' . $val[1]);
|
||||
}
|
||||
else
|
||||
else {
|
||||
return false;
|
||||
}
|
||||
|
||||
return $text;
|
||||
}
|
||||
|
@ -400,53 +478,78 @@ class libcalendaring extends rcube_plugin
|
|||
*/
|
||||
public static function get_next_alarm($rec, $type = 'event')
|
||||
{
|
||||
if (!$rec['alarms'] || $rec['cancelled'] || $rec['status'] == 'CANCELLED')
|
||||
if (!($rec['valarms'] || $rec['alarms']) || $rec['cancelled'] || $rec['status'] == 'CANCELLED')
|
||||
return null;
|
||||
|
||||
if ($type == 'task') {
|
||||
$timezone = self::get_instance()->timezone;
|
||||
if ($rec['date'])
|
||||
$rec['start'] = new DateTime($rec['date'] . ' ' . ($rec['time'] ?: '12:00'), $timezone);
|
||||
if ($rec['startdate'])
|
||||
$rec['end'] = new DateTime($rec['startdate'] . ' ' . ($rec['starttime'] ?: '12:00'), $timezone);
|
||||
$rec['start'] = new DateTime($rec['startdate'] . ' ' . ($rec['starttime'] ?: '12:00'), $timezone);
|
||||
if ($rec['date'])
|
||||
$rec[($rec['start'] ? 'end' : 'start')] = new DateTime($rec['date'] . ' ' . ($rec['time'] ?: '12:00'), $timezone);
|
||||
}
|
||||
|
||||
if (!$rec['end'])
|
||||
$rec['end'] = $rec['start'];
|
||||
|
||||
|
||||
// TODO: handle multiple alarms (currently not supported)
|
||||
list($trigger, $action) = explode(':', $rec['alarms'], 2);
|
||||
|
||||
$notify = self::parse_alaram_value($trigger);
|
||||
if (!empty($notify[1])){ // offset
|
||||
$mult = 1;
|
||||
switch ($notify[1]) {
|
||||
case '-S': $mult = -1; break;
|
||||
case '+S': $mult = 1; break;
|
||||
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;
|
||||
case '-W': $mult = -604800; break;
|
||||
case '+W': $mult = 604800; break;
|
||||
// support legacy format
|
||||
if (!$rec['valarms']) {
|
||||
list($trigger, $action) = explode(':', $rec['alarms'], 2);
|
||||
if ($alarm = self::parse_alaram_value($trigger)) {
|
||||
$rec['valarms'] = array(array('action' => $action, 'trigger' => $alarm[3] ?: $alarm[0]));
|
||||
}
|
||||
$offset = $notify[0] * $mult;
|
||||
$refdate = $mult > 0 ? $rec['end'] : $rec['start'];
|
||||
|
||||
// abort of no reference date is available to compute notification time
|
||||
if (!is_a($refdate, 'DateTime'))
|
||||
return null;
|
||||
|
||||
$notify_at = $refdate->format('U') + $offset;
|
||||
}
|
||||
else { // absolute timestamp
|
||||
$notify_at = $notify[0];
|
||||
}
|
||||
|
||||
return array('time' => $notify_at, 'action' => $action ? strtoupper($action) : 'DISPLAY');
|
||||
$expires = new DateTime('now - 12 hours');
|
||||
$alarm_id = $rec['id']; // alarm ID eq. record ID by default to keep backwards compatibility
|
||||
|
||||
// handle multiple alarms
|
||||
$notify_at = null;
|
||||
foreach ($rec['valarms'] as $alarm) {
|
||||
$notify_time = null;
|
||||
|
||||
if ($alarm['trigger'] instanceof DateTime) {
|
||||
$notify_time = $alarm['trigger'];
|
||||
}
|
||||
else if (is_string($alarm['trigger'])) {
|
||||
$refdate = $alarm['trigger'][0] == '+' ? $rec['end'] : $rec['start'];
|
||||
|
||||
// abort if no reference date is available to compute notification time
|
||||
if (!is_a($refdate, 'DateTime'))
|
||||
continue;
|
||||
|
||||
// TODO: for all-day events, take start @ 00:00 as reference date ?
|
||||
|
||||
try {
|
||||
$interval = new DateInterval(trim($alarm['trigger'], '+-'));
|
||||
$interval->invert = $alarm['trigger'][0] != '+';
|
||||
$notify_time = clone $refdate;
|
||||
$notify_time->add($interval);
|
||||
}
|
||||
catch (Exception $e) {
|
||||
rcube::raise_error($e, true);
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
if ($notify_time && (!$notify_at || ($notify_time < $notify_at && $notify_time > $expires))) {
|
||||
$notify_at = $notify_time;
|
||||
$action = $alarm['action'];
|
||||
$alarm_prop = $alarm;
|
||||
|
||||
// generate a unique alarm ID if multiple alarms are set
|
||||
if (count($rec['valarms']) > 1) {
|
||||
$alarm_id = substr(md5($rec['id']), 0, 16) . '-' . $notify_at->format('Ymd\THis');
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return !$notify_at ? null : array(
|
||||
'time' => $notify_at->format('U'),
|
||||
'action' => $action ? strtoupper($action) : 'DISPLAY',
|
||||
'id' => $alarm_id,
|
||||
'prop' => $alarm_prop,
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
|
|
|
@ -1025,9 +1025,10 @@ class libvcalendar implements Iterator
|
|||
$va = VObject\Component::create('VALARM');
|
||||
list($trigger, $va->action) = explode(':', $event['alarms']);
|
||||
$val = libcalendaring::parse_alaram_value($trigger);
|
||||
$period = $val[1] && preg_match('/[HMS]$/', $val[1]) ? 'PT' : 'P';
|
||||
if ($val[1]) $va->add('TRIGGER', preg_replace('/^([-+])P?T?(.+)/', "\\1$period\\2", $trigger));
|
||||
else $va->add('TRIGGER', gmdate('Ymd\THis\Z', $val[0]), array('VALUE' => 'DATE-TIME'));
|
||||
if ($val[3])
|
||||
$va->add('TRIGGER', $val[3]);
|
||||
else if ($val[0] instanceof DateTime)
|
||||
$va->add(self::datetime_prop('TRIGGER', $val[0]));
|
||||
$ve->add($va);
|
||||
}
|
||||
|
||||
|
|
|
@ -4,8 +4,10 @@ $labels = array();
|
|||
|
||||
$labels['alarmemail'] = 'Send Email';
|
||||
$labels['alarmdisplay'] = 'Show message';
|
||||
$labels['alarmaudio'] = 'Play sound';
|
||||
$labels['alarmdisplayoption'] = 'Message';
|
||||
$labels['alarmemailoption'] = 'Email';
|
||||
$labels['alarmaudiooption'] = 'Sound';
|
||||
$labels['alarmat'] = 'at $datetime';
|
||||
$labels['trigger@'] = 'on date';
|
||||
$labels['trigger-M'] = 'minutes before';
|
||||
|
@ -14,6 +16,7 @@ $labels['trigger-D'] = 'days before';
|
|||
$labels['trigger+M'] = 'minutes after';
|
||||
$labels['trigger+H'] = 'hours after';
|
||||
$labels['trigger+D'] = 'days after';
|
||||
$labels['triggerattime'] = 'at time';
|
||||
$labels['addalarm'] = 'add alarm';
|
||||
|
||||
$labels['alarmtitle'] = 'Upcoming events';
|
||||
|
|
|
@ -1,7 +1,7 @@
|
|||
/**
|
||||
* Roundcube libcalendaring plugin styles for skin "Larry"
|
||||
*
|
||||
* Copyright (c) 2012, Kolab Systems AG <contact@kolabsys.com>
|
||||
* Copyright (c) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
|
||||
*
|
||||
* The contents are subject to the Creative Commons Attribution-ShareAlike
|
||||
* License. It is allowed to copy, distribute, transmit and to adapt the work
|
||||
|
@ -75,4 +75,39 @@ a.reply-comment-toggle {
|
|||
|
||||
.popup textarea.itip-comment {
|
||||
width: 98%;
|
||||
}
|
||||
}
|
||||
|
||||
.edit-alarm-item {
|
||||
position: relative;
|
||||
padding-right: 30px;
|
||||
margin-bottom: 0.2em;
|
||||
}
|
||||
|
||||
.edit-alarm-buttons {
|
||||
position: absolute;
|
||||
top: 1px;
|
||||
right: 0;
|
||||
}
|
||||
|
||||
.edit-alarm-buttons a.iconlink {
|
||||
display: none;
|
||||
width: 18px;
|
||||
height: 17px;
|
||||
padding: 1px;
|
||||
text-indent: -5000px;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
.edit-alarm-buttons a.delete-alarm {
|
||||
background-position: -7px -377px;
|
||||
}
|
||||
|
||||
.edit-alarm-buttons a.delete-alarm,
|
||||
.edit-alarm-item.first .edit-alarm-buttons a.add-alarm {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
.edit-alarm-item.first .edit-alarm-buttons a.delete-alarm {
|
||||
display: none;
|
||||
}
|
||||
|
||||
|
|
|
@ -182,7 +182,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
|
|||
$this->assertEquals('-H', $alarm[1], "Alarm unit");
|
||||
|
||||
$this->assertEquals('DISPLAY', $event['valarms'][0]['action'], "Full alarm item (action)");
|
||||
$this->assertEquals('-PT12H', $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
|
||||
$this->assertEquals('-PT12H', $event['valarms'][0]['trigger'], "Full alarm item (trigger)");
|
||||
|
||||
// alarm trigger with 0 values
|
||||
$events = $ical->import_from_file(__DIR__ . '/resources/alarms.ics', 'UTF-8');
|
||||
|
@ -193,6 +193,7 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase
|
|||
$this->assertEquals('30', $alarm[0], "Alarm value");
|
||||
$this->assertEquals('-M', $alarm[1], "Alarm unit");
|
||||
$this->assertEquals('-30M', $alarm[2], "Alarm string");
|
||||
$this->assertEquals('-PT30M', $alarm[3], "Unified alarm string (stripped zero-values)");
|
||||
|
||||
$this->assertEquals('DISPLAY', $event['valarms'][0]['action'], "First alarm action");
|
||||
$this->assertEquals('This is the first event reminder', $event['valarms'][0]['description'], "First alarm text");
|
||||
|
|
|
@ -482,6 +482,12 @@ class tasklist_database_driver extends tasklist_driver
|
|||
if (!$rec['parent_id'])
|
||||
unset($rec['parent_id']);
|
||||
|
||||
// decode serialized alarms
|
||||
if ($rec['alarms']) {
|
||||
$rec['valarms'] = $this->unserialize_alarms($rec['alarms']);
|
||||
unset($rec['alarms']);
|
||||
}
|
||||
|
||||
unset($rec['task_id'], $rec['tasklist_id'], $rec['created']);
|
||||
return $rec;
|
||||
}
|
||||
|
@ -500,6 +506,10 @@ class tasklist_database_driver extends tasklist_driver
|
|||
if (!$this->lists[$list_id] || $this->lists[$list_id]['readonly'])
|
||||
return false;
|
||||
|
||||
if (is_array($prop['valarms'])) {
|
||||
$prop['alarms'] = $this->serialize_alarms($prop['valarms']);
|
||||
}
|
||||
|
||||
foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms') as $col) {
|
||||
if (empty($prop[$col]))
|
||||
$prop[$col] = null;
|
||||
|
@ -542,6 +552,10 @@ class tasklist_database_driver extends tasklist_driver
|
|||
*/
|
||||
public function edit_task($prop)
|
||||
{
|
||||
if (is_array($prop['valarms'])) {
|
||||
$prop['alarms'] = $this->serialize_alarms($prop['valarms']);
|
||||
}
|
||||
|
||||
$sql_set = array();
|
||||
foreach (array('title', 'description', 'flagged', 'complete') as $col) {
|
||||
if (isset($prop[$col]))
|
||||
|
@ -655,14 +669,58 @@ class tasklist_database_driver extends tasklist_driver
|
|||
*/
|
||||
private function _get_notification($task)
|
||||
{
|
||||
if ($task['alarms'] && $task['complete'] < 1 || strpos($task['alarms'], '@') !== false) {
|
||||
if ($task['valarms'] && $task['complete'] < 1) {
|
||||
$alarm = libcalendaring::get_next_alarm($task, 'task');
|
||||
|
||||
if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
|
||||
if ($alarm['time'] && in_array($alarm['action'], $this->alarm_types))
|
||||
return date('Y-m-d H:i:s', $alarm['time']);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to serialize the list of alarms into a string
|
||||
*/
|
||||
private function serialize_alarms($valarms)
|
||||
{
|
||||
foreach ((array)$valarms as $i => $alarm) {
|
||||
if ($alarm['trigger'] instanceof DateTime) {
|
||||
$valarms[$i]['trigger'] = '@' . $alarm['trigger']->format('c');
|
||||
}
|
||||
}
|
||||
|
||||
return $valarms ? json_encode($valarms) : null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper method to decode a serialized list of alarms
|
||||
*/
|
||||
private function unserialize_alarms($alarms)
|
||||
{
|
||||
// decode json serialized alarms
|
||||
if ($alarms && $alarms[0] == '[') {
|
||||
$valarms = json_decode($alarms, true);
|
||||
foreach ($valarms as $i => $alarm) {
|
||||
if ($alarm['trigger'][0] == '@') {
|
||||
try {
|
||||
$valarms[$i]['trigger'] = new DateTime(substr($alarm['trigger'], 1));
|
||||
}
|
||||
catch (Exception $e) {
|
||||
unset($valarms[$i]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
// convert legacy alarms data
|
||||
else if (strlen($alarms)) {
|
||||
list($trigger, $action) = explode(':', $alarms, 2);
|
||||
if ($trigger = libcalendaring::parse_alaram_value($trigger)) {
|
||||
$valarms = array(array('action' => $action, 'trigger' => $trigger[3] ?: $trigger[0]));
|
||||
}
|
||||
}
|
||||
|
||||
return $valarms;
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -28,7 +28,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
public $alarms = false;
|
||||
public $attachments = true;
|
||||
public $undelete = false; // task undelete action
|
||||
public $alarm_types = array('DISPLAY');
|
||||
public $alarm_types = array('DISPLAY','AUDIO');
|
||||
|
||||
private $rc;
|
||||
private $plugin;
|
||||
|
@ -477,7 +477,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
|
||||
$time = $slot + $interval;
|
||||
|
||||
$tasks = array();
|
||||
$candidates = array();
|
||||
$query = array(array('tags', '=', 'x-has-alarms'), array('tags', '!=', 'x-complete'));
|
||||
foreach ($this->lists as $lid => $list) {
|
||||
// skip lists with alarms disabled
|
||||
|
@ -486,40 +486,46 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
|
||||
$folder = $this->folders[$lid];
|
||||
foreach ($folder->select($query) as $record) {
|
||||
if (!$record['alarms'] || $record['status'] == 'COMPLETED' || $record['complete'] == 100) // don't trust query :-)
|
||||
if (!($record['valarms'] || $record['alarms']) || $record['status'] == 'COMPLETED' || $record['complete'] == 100) // don't trust query :-)
|
||||
continue;
|
||||
|
||||
$task = $this->_to_rcube_task($record);
|
||||
|
||||
// add to list if alarm is set
|
||||
$alarm = libcalendaring::get_next_alarm($task, 'task');
|
||||
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
|
||||
$id = $task['id'];
|
||||
$tasks[$id] = $task;
|
||||
$tasks[$id]['notifyat'] = $alarm['time'];
|
||||
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && in_array($alarm['action'], $this->alarm_types)) {
|
||||
$id = $alarm['id']; // use alarm-id as primary identifier
|
||||
$candidates[$id] = array(
|
||||
'id' => $id,
|
||||
'title' => $task['title'],
|
||||
'date' => $task['date'],
|
||||
'time' => $task['time'],
|
||||
'notifyat' => $alarm['time'],
|
||||
'action' => $alarm['action'],
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// get alarm information stored in local database
|
||||
if (!empty($tasks)) {
|
||||
$task_ids = array_map(array($this->rc->db, 'quote'), array_keys($tasks));
|
||||
if (!empty($candidates)) {
|
||||
$alarm_ids = array_map(array($this->rc->db, 'quote'), array_keys($candidates));
|
||||
$result = $this->rc->db->query(sprintf(
|
||||
"SELECT * FROM kolab_alarms
|
||||
WHERE event_id IN (%s) AND user_id=?",
|
||||
join(',', $task_ids),
|
||||
WHERE alarm_id IN (%s) AND user_id=?",
|
||||
join(',', $alarm_ids),
|
||||
$this->rc->db->now()
|
||||
),
|
||||
$this->rc->user->ID
|
||||
);
|
||||
|
||||
while ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
|
||||
$dbdata[$rec['event_id']] = $rec;
|
||||
$dbdata[$rec['alarm_id']] = $rec;
|
||||
}
|
||||
}
|
||||
|
||||
$alarms = array();
|
||||
foreach ($tasks as $id => $task) {
|
||||
foreach ($candidates as $id => $task) {
|
||||
// skip dismissed
|
||||
if ($dbdata[$id]['dismissed'])
|
||||
continue;
|
||||
|
@ -545,7 +551,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
// delete old alarm entry
|
||||
$this->rc->db->query(
|
||||
"DELETE FROM kolab_alarms
|
||||
WHERE event_id=? AND user_id=?",
|
||||
WHERE alarm_id=? AND user_id=?",
|
||||
$id,
|
||||
$this->rc->user->ID
|
||||
);
|
||||
|
@ -555,7 +561,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
|
||||
$query = $this->rc->db->query(
|
||||
"INSERT INTO kolab_alarms
|
||||
(event_id, user_id, dismissed, notifyat)
|
||||
(alarm_id, user_id, dismissed, notifyat)
|
||||
VALUES(?, ?, ?, ?)",
|
||||
$id,
|
||||
$this->rc->user->ID,
|
||||
|
@ -599,7 +605,10 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
$task['changed'] = $record['dtstamp'];
|
||||
}
|
||||
|
||||
if ($record['alarms']) {
|
||||
if ($record['valarms']) {
|
||||
$task['valarms'] = $record['valarms'];
|
||||
}
|
||||
else if ($record['alarms']) {
|
||||
$task['alarms'] = $record['alarms'];
|
||||
}
|
||||
|
||||
|
|
|
@ -32,8 +32,14 @@
|
|||
<a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#taskedit-date,#taskedit-time"><roundcube:label name="tasklist.nodate" /></a>
|
||||
</div>
|
||||
<div class="form-section" id="taskedit-alarms">
|
||||
<label for="taskedit-alarm"><roundcube:label name="tasklist.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<div class="edit-alarm-item first">
|
||||
<label><roundcube:label name="tasklist.alarms" /></label>
|
||||
<roundcube:object name="plugin.alarm_select" />
|
||||
<span class="edit-alarm-buttons">
|
||||
<a href="#add" class="iconlink add add-alarm">+</a>
|
||||
<a href="#delete" class="iconlink delete delete-alarm">-</a>
|
||||
</span>
|
||||
</div>
|
||||
</div>
|
||||
<div class="form-section">
|
||||
<label for="taskedit-completeness"><roundcube:label name="tasklist.complete" /></label>
|
||||
|
|
|
@ -107,7 +107,6 @@ function rcube_tasklist_ui(settings)
|
|||
var parse_datetime = this.parse_datetime;
|
||||
var date2unixtime = this.date2unixtime;
|
||||
var fromunixtime = this.fromunixtime;
|
||||
var init_alarms_edit = this.init_alarms_edit;
|
||||
|
||||
/**
|
||||
* initialize the tasks UI
|
||||
|
@ -380,7 +379,7 @@ function rcube_tasklist_ui(settings)
|
|||
});
|
||||
|
||||
// register events on alarm fields
|
||||
init_alarms_edit('#taskedit');
|
||||
me.init_alarms_edit('#taskedit-alarms');
|
||||
|
||||
$('#taskedit-date, #taskedit-startdate').datepicker(datepicker_settings);
|
||||
|
||||
|
@ -1169,7 +1168,7 @@ function rcube_tasklist_ui(settings)
|
|||
if (rcmail.busy || !list.editable || (action == 'edit' && (!rec || rec.readonly)))
|
||||
return false;
|
||||
|
||||
me.selected_task = $.extend({ alarms:'' }, rec); // clone task object
|
||||
me.selected_task = $.extend({ valarms:[] }, rec); // clone task object
|
||||
rec = me.selected_task;
|
||||
|
||||
// assign temporary id
|
||||
|
@ -1210,29 +1209,7 @@ function rcube_tasklist_ui(settings)
|
|||
});
|
||||
|
||||
// set alarm(s)
|
||||
if (rec.alarms || action != 'new') {
|
||||
var valarms = (typeof rec.alarms == 'string' ? rec.alarms.split(';') : rec.alarms) || [''];
|
||||
for (var alarm, i=0; i < valarms.length; i++) {
|
||||
alarm = String(valarms[i]).split(':');
|
||||
if (!alarm[1] && alarm[0]) alarm[1] = 'DISPLAY';
|
||||
$('#taskedit select.edit-alarm-type').val(alarm[1]);
|
||||
|
||||
if (alarm[0].match(/@(\d+)/)) {
|
||||
var ondate = fromunixtime(parseInt(RegExp.$1));
|
||||
$('#taskedit select.edit-alarm-offset').val('@');
|
||||
$('#taskedit input.edit-alarm-date').val(me.format_datetime(ondate, 1));
|
||||
$('#taskedit input.edit-alarm-time').val(me.format_datetime(ondate, 2));
|
||||
}
|
||||
else if (alarm[0].match(/([-+])(\d+)([MHD])/)) {
|
||||
$('#taskedit input.edit-alarm-value').val(RegExp.$2);
|
||||
$('#taskedit select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3);
|
||||
}
|
||||
|
||||
break; // only one alarm is currently supported
|
||||
}
|
||||
}
|
||||
// set correct visibility by triggering onchange handlers
|
||||
$('#taskedit select.edit-alarm-type, #taskedit select.edit-alarm-offset').change();
|
||||
me.set_alarms_edit('#taskedit-alarms', action != 'new' && rec.valarms ? rec.valarms : []);
|
||||
|
||||
// attachments
|
||||
rcmail.enable_command('remove-attachment', list.editable);
|
||||
|
@ -1263,6 +1240,7 @@ function rcube_tasklist_ui(settings)
|
|||
});
|
||||
me.selected_task.tags = [];
|
||||
me.selected_task.attachments = [];
|
||||
me.selected_task.valarms = me.serialize_alarms('#taskedit-alarms');
|
||||
|
||||
// do some basic input validation
|
||||
if (!me.selected_task.title || !me.selected_task.title.length) {
|
||||
|
@ -1289,16 +1267,6 @@ function rcube_tasklist_ui(settings)
|
|||
me.selected_task.tags.push(newtag);
|
||||
}
|
||||
|
||||
// serialize alarm settings
|
||||
var alarm = $('#taskedit select.edit-alarm-type').val();
|
||||
if (alarm) {
|
||||
var val, offset = $('#taskedit select.edit-alarm-offset').val();
|
||||
if (offset == '@')
|
||||
me.selected_task.alarms = '@' + date2unixtime(parse_datetime($('#taskedit input.edit-alarm-time').val(), $('#taskedit input.edit-alarm-date').val())) + ':' + alarm;
|
||||
else if ((val = parseInt($('#taskedit input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
|
||||
me.selected_task.alarms = offset[0] + val + offset[1] + ':' + alarm;
|
||||
}
|
||||
|
||||
// uploaded attachments list
|
||||
for (var i in rcmail.env.attachments) {
|
||||
if (i.match(/^rcmfile(.+)/))
|
||||
|
|
|
@ -406,9 +406,16 @@ class tasklist extends rcube_plugin
|
|||
$rec['tags'] = array_filter((array)$rec['tags']);
|
||||
}
|
||||
|
||||
// alarms cannot work without a date
|
||||
if ($rec['alarms'] && !$rec['date'] && !$rec['startdate'] && strpos($rec['alarms'], '@') === false)
|
||||
$rec['alarms'] = '';
|
||||
// convert the submitted alarm values
|
||||
if ($rec['valarms']) {
|
||||
$valarms = array();
|
||||
foreach (libcalendaring::from_client_alarms($rec['valarms']) as $alarm) {
|
||||
// alarms can only work with a date (either task start, due or absolute alarm date)
|
||||
if (is_a($alarm['trigger'], 'DateTime') || $rec['date'] || $rec['startdate'])
|
||||
$valarms[] = $alarm;
|
||||
}
|
||||
$rec['valarms'] = $valarms;
|
||||
}
|
||||
|
||||
$attachments = array();
|
||||
$taskid = $rec['id'];
|
||||
|
@ -663,8 +670,10 @@ class tasklist extends rcube_plugin
|
|||
}
|
||||
}
|
||||
|
||||
if ($rec['alarms'])
|
||||
$rec['alarms_text'] = libcalendaring::alarms_text($rec['alarms']);
|
||||
if ($rec['valarms']) {
|
||||
$rec['alarms_text'] = libcalendaring::alarms_text($rec['valarms']);
|
||||
$rec['valarms'] = libcalendaring::to_client_alarms($rec['valarms']);
|
||||
}
|
||||
|
||||
foreach ((array)$rec['attachments'] as $k => $attachment) {
|
||||
$rec['attachments'][$k]['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']);
|
||||
|
|
Loading…
Add table
Reference in a new issue