diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php
index df6e418f..91975a3d 100644
--- a/plugins/calendar/calendar.php
+++ b/plugins/calendar/calendar.php
@@ -108,6 +108,8 @@ class calendar extends rcube_plugin
$this->register_action('export_events', array($this, 'export_events'));
$this->register_action('upload', array($this, 'attachment_upload'));
$this->register_action('get-attachment', array($this, 'attachment_get'));
+ $this->register_action('freebusy-status', array($this, 'freebusy_status'));
+ $this->register_action('freebusy-times', array($this, 'freebusy_times'));
$this->register_action('randomdata', array($this, 'generate_randomdata'));
}
else if ($this->rc->task == 'settings') {
@@ -169,10 +171,12 @@ class calendar extends rcube_plugin
$this->register_handler('plugin.recurrence_form', array($this->ui, 'recurrence_form'));
$this->register_handler('plugin.attachments_form', array($this->ui, 'attachments_form'));
$this->register_handler('plugin.attachments_list', array($this->ui, 'attachments_list'));
+ $this->register_handler('plugin.attendees_list', array($this->ui, 'attendees_list'));
+ $this->register_handler('plugin.attendees_form', array($this->ui, 'attendees_form'));
$this->register_handler('plugin.edit_recurring_warning', array($this->ui, 'recurring_event_warning'));
$this->register_handler('plugin.searchform', array($this->rc->output, 'search_form')); // use generic method from rcube_template
- $this->rc->output->add_label('low','normal','high','delete','cancel','uploading');
+ $this->rc->output->add_label('low','normal','high','delete','cancel','uploading','noemailwarning');
$this->rc->output->send("calendar.calendar");
}
@@ -442,29 +446,41 @@ class calendar extends rcube_plugin
case "new":
// create UID for new event
$event['uid'] = $this->generate_uid();
+
+ // set current user as organizer
+ if (!$event['attendees']) {
+ $identity = $this->rc->user->get_identity();
+ $event['attendees'][] = array('role' => 'OWNER', 'name' => $identity['name'], 'email' => $identity['email']);
+ }
+
$this->prepare_event($event);
if ($success = $this->driver->new_event($event))
$this->cleanup_event($event);
$reload = true;
break;
+
case "edit":
$this->prepare_event($event);
if ($success = $this->driver->edit_event($event))
$this->cleanup_event($event);
$reload = true;
break;
+
case "resize":
$success = $this->driver->resize_event($event);
$reload = true;
break;
+
case "move":
$success = $this->driver->move_event($event);
$reload = true;
break;
+
case "remove":
$removed = $this->driver->remove_event($event);
$reload = true;
break;
+
case "dismiss":
foreach (explode(',', $event['id']) as $id)
$success |= $this->driver->dismiss_alarm($id, $event['snooze']);
@@ -606,6 +622,10 @@ class calendar extends rcube_plugin
// user prefs
$settings['hidden_calendars'] = array_filter(explode(',', $this->rc->config->get('hidden_calendars', '')));
+
+ // get user identity to create default attendee
+ $identity = $this->rc->user->get_identity();
+ $settings['event_owner'] = array('name' => $identity['name'], 'email' => $identity['email']);
return $settings;
}
@@ -1113,5 +1133,59 @@ class calendar extends rcube_plugin
unset($_SESSION['event_session']);
}
}
+
+ /**
+ * Echo simple free/busy status text for the given user and time range
+ */
+ public function freebusy_status()
+ {
+ $email = get_input_value('email', RCUBE_INPUT_GPC);
+ $start = get_input_value('start', RCUBE_INPUT_GET);
+ $end = get_input_value('end', RCUBE_INPUT_GET);
+
+ if (!$start) $start = time();
+ if (!$end) $end = $start + 3600;
+
+ $status = 'UNKNOWN';
+
+ // if the backend has free-busy information
+ $fblist = $this->driver->get_freebusy_list($email, $start, $end);
+ if (is_array($fblist)) {
+ $status = 'FREE';
+
+ foreach ($fblist as $slot) {
+ list($from, $to) = $slot;
+ if ($from <= $end && $to >= $start) {
+ $status = 'BUSY';
+ break;
+ }
+ }
+ }
+
+ echo $status;
+ exit;
+ }
+
+ /**
+ * Return a list of free/busy time slots within the given period
+ * Echo data in JSON encoding
+ */
+ public function freebusy_times()
+ {
+ $email = get_input_value('email', RCUBE_INPUT_GPC);
+ $start = get_input_value('start', RCUBE_INPUT_GET);
+ $end = get_input_value('end', RCUBE_INPUT_GET);
+
+ if (!$start) $start = time();
+ if (!$end) $end = $start + 86400 * 30;
+
+ $fblist = $this->driver->get_freebusy_list($email, $start, $end);
+ $result = array();
+
+ // TODO: build a list from $start till $end with blocks representing the fb-status
+
+ echo json_encode($result);
+ exit;
+ }
}
diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js
index e71467e7..fe0925ca 100644
--- a/plugins/calendar/calendar_ui.js
+++ b/plugins/calendar/calendar_ui.js
@@ -42,6 +42,8 @@ function rcube_calendar_ui(settings)
var gmt_offset = (new Date().getTimezoneOffset() / -60) - (settings.timezone || 0);
var day_clicked = day_clicked_ts = 0;
var ignore_click = false;
+ var event_attendees = null;
+ var attendees_list;
// general datepicker settings
var datepicker_settings = {
@@ -66,6 +68,27 @@ function rcube_calendar_ui(settings)
{
return String(str).replace(/\n/g, "
");
};
+
+ //
+ var explode_quoted_string = function(str, delimiter)
+ {
+ var result = [],
+ strlen = str.length,
+ q, p, i;
+
+ for (q = p = i = 0; i < strlen; i++) {
+ if (str[i] == '"' && str[i-1] != '\\') {
+ q = !q;
+ }
+ else if (!q && str[i] == delimiter) {
+ result.push(str.substring(p, i));
+ p = i + 1;
+ }
+ }
+
+ result.push(str.substr(p));
+ return result;
+ };
// from time and date strings to a real date object
var parse_datetime = function(time, date)
@@ -390,6 +413,14 @@ function rcube_calendar_ui(settings)
}
else
$('#edit-recurring-warning').hide();
+
+ // attendees
+ event_attendees = [];
+ attendees_list = $('#edit-attendees-table > tbody').html('');
+ if (calendar.attendees && event.attendees) {
+ for (var j=0; j < event.attendees.length; j++)
+ add_attendee(event.attendees[j]);
+ }
// attachments
if (calendar.attachments) {
@@ -436,6 +467,7 @@ function rcube_calendar_ui(settings)
sensitivity: sensitivity.val(),
recurrence: '',
alarms: '',
+ attendees: event_attendees,
deleted_attachments: rcmail.env.deleted_attachments
};
@@ -531,18 +563,87 @@ function rcube_calendar_ui(settings)
$dialog.dialog({
modal: true,
resizable: true,
+ closeOnEscape: false,
title: rcmail.gettext((action == 'edit' ? 'edit_event' : 'new_event'), 'calendar'),
close: function() {
$dialog.dialog("destroy").hide();
},
buttons: buttons,
- minWidth: 440,
- width: 480
+ minWidth: 500,
+ width: 580
}).show();
title.select();
};
+ // add the given list of participants
+ var add_attendees = function(names)
+ {
+ names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
+
+ // parse name/email pairs
+ var item, email, name, success = false;
+ for (var i=0; i < names.length; i++) {
+ email = name = null;
+ item = $.trim(names[i]);
+
+ if (!item.length) {
+ continue;
+ } // address in brackets without name (do nothing)
+ else if (item.match(/^<[^@]+@[^>]+>$/)) {
+ email = item.replace(/[<>]/g, '');
+ } // address without brackets and without name (add brackets)
+ else if (rcube_check_email(item)) {
+ email = item;
+ } // address with name
+ else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
+ email = RegExp.$1;
+ name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
+ }
+
+ if (email) {
+ add_attendee({ email:email, name:name, role:'REQUIRED', status:'unknown' });
+ success = true;
+ }
+ else {
+ alert(rcmail.gettext('noemailwarning'));
+ }
+ }
+
+ return success;
+ };
+
+ // add the given attendee to the list
+ var add_attendee = function(data)
+ {
+ var dispname = (data.email && data.name) ? data.name + ' <' + data.email + '>' : (data.email || data.name);
+
+ // delete icon
+ var icon = rcmail.env.deleteicon ? '' : rcmail.gettext('delete');
+ var dellink = '' + icon + '';
+
+ var html = '