Implement searching for unsubscribed IMAP folders and temporary/session subscriptions

This commit is contained in:
Thomas Bruederli 2014-05-13 17:09:53 +02:00
parent 00b1c7631b
commit 008c5db5d9
11 changed files with 411 additions and 108 deletions

View file

@ -725,9 +725,29 @@ class calendar extends rcube_plugin
$this->rc->output->command('plugin.destroy_source', array('id' => $cal['id'])); $this->rc->output->command('plugin.destroy_source', array('id' => $cal['id']));
break; break;
case "subscribe": case "subscribe":
if (!$this->driver->subscribe_calendar($cal)) if (!$this->driver->subscribe_calendar($cal, intval(get_input_value('perm', RCUBE_INPUT_GPC))))
$this->rc->output->show_message($this->gettext('errorsaving'), 'error'); $this->rc->output->show_message($this->gettext('errorsaving'), 'error');
return; return;
case "search":
$results = array();
$color_mode = $this->rc->config->get('calendar_event_coloring', $this->defaults['calendar_event_coloring']);
foreach ((array)$this->driver->search_calendars(get_input_value('q', RCUBE_INPUT_GPC), get_input_value('source', RCUBE_INPUT_GPC)) as $id => $prop) {
$editname = $prop['editname'];
unset($prop['editname']); // force full name to be displayed
$prop['active'] = false;
// let the UI generate HTML and CSS representation for this calendar
$html = $this->ui->calendar_list_item($id, $prop, $jsenv);
$cal = $jsenv[$id];
$cal['editname'] = $editname;
$cal['html'] = $html;
if (!empty($prop['color']))
$cal['css'] = $this->ui->calendar_css_classes($id, $prop, $color_mode);
$results[] = $cal;
}
$this->rc->output->command('multi_thread_http_response', $results, get_input_value('_reqid', RCUBE_INPUT_GPC));
return;
} }
if ($success) if ($success)

View file

@ -39,6 +39,7 @@ function rcube_calendar_ui(settings)
this.selected_calendar = null; this.selected_calendar = null;
this.search_request = null; this.search_request = null;
this.saving_lock; this.saving_lock;
this.calendars = {};
/*** private vars ***/ /*** private vars ***/
@ -52,6 +53,9 @@ function rcube_calendar_ui(settings)
var event_defaults = { free_busy:'busy', alarms:'' }; var event_defaults = { free_busy:'busy', alarms:'' };
var event_attendees = []; var event_attendees = [];
var calendars_list; var calendars_list;
var calenders_search_list;
var calenders_search_container;
var search_calendars = {};
var attendees_list; var attendees_list;
var resources_list; var resources_list;
var resources_treelist; var resources_treelist;
@ -2667,16 +2671,79 @@ function rcube_calendar_ui(settings)
this.selected_calendar = id; this.selected_calendar = id;
}; };
// render the results for calendar list search
var calendar_search_results = function(results)
{
if (results.length) {
// create treelist widget to present the search results
if (!calenders_search_list) {
calenders_search_container = $('<div class="searchresults"></div>')
.html('<h2 class="boxtitle">' + rcmail.gettext('calsearchresults','calendar') + '</h2>')
.insertAfter(rcmail.gui_objects.calendarslist);
/*** startup code ***/ calenders_search_list = new rcube_treelist_widget('<ul class="treelist listing"></ul>', {
id_prefix: 'rcmlical',
selectable: false
});
// create list of event sources AKA calendars // register click handler on search result's checkboxes to select the given calendar for listing
this.calendars = {}; calenders_search_list.container
var id, li, cal, active, color, brightness, event_sources = []; .appendTo(calenders_search_container)
for (id in rcmail.env.calendars) { .on('click', 'input[type=checkbox]', function(e){
cal = rcmail.env.calendars[id]; var li = $(this).closest('li'),
this.calendars[id] = $.extend({ id = li.attr('id').replace(/^rcmlical/, ''),
url: "./?_task=calendar&_action=load_events&source="+escape(id), prop = search_calendars[id],
parent_id = prop.parent || null;
if (!this.checked)
return;
// find parent node and insert at the right place
if (parent_id && $('#rcmlical'+parent_id, rcmail.gui_objects.calendarslist).length) {
prop.listname = prop.editname;
li.children().first().find('.calname').html(Q(prop.listname));
}
// move this calendar to the calendars_list widget
calendars_list.insert({
id: id,
classes: [],
html: li.children().first()
}, parent_id, parent_id ? true : false);
search_calendars[id].active = true;
add_calendar_source(prop);
li.remove();
// add css classes related to this calendar to document
if (cal.css) {
$('<style type="text/css"></style>')
.html(cal.css)
.appendTo('head');
}
});
}
for (var cal, i=0; i < results.length; i++) {
cal = results[i];
search_calendars[cal.id] = cal;
$('<li>')
.attr('id', 'rcmlical' + cal.id)
.html(cal.html)
.appendTo(calenders_search_list.container);
}
calenders_search_container.show();
}
};
// register the given calendar to the current view
var add_calendar_source = function(cal)
{
var color, brightness, select, id = cal.id;
me.calendars[id] = $.extend({
url: rcmail.url('calendar/load_events', { source: id }),
editable: !cal.readonly, editable: !cal.readonly,
className: 'fc-event-cal-'+id, className: 'fc-event-cal-'+id,
id: id id: id
@ -2689,18 +2756,40 @@ function rcube_calendar_ui(settings)
// http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/ // http://javascriptrules.com/2009/08/05/css-color-brightness-contrast-using-javascript/
brightness = (parseInt(RegExp.$1, 16) * 299 + parseInt(RegExp.$2, 16) * 587 + parseInt(RegExp.$3, 16) * 114) / 1000; brightness = (parseInt(RegExp.$1, 16) * 299 + parseInt(RegExp.$2, 16) * 587 + parseInt(RegExp.$3, 16) * 114) / 1000;
if (brightness > 125) if (brightness > 125)
this.calendars[id].textColor = 'black'; me.calendars[id].textColor = 'black';
} }
me.calendars[id].color = color;
} }
this.calendars[id].color = color; if (fc && cal.active) {
fc.fullCalendar('addEventSource', me.calendars[id]);
if ((active = cal.active || false)) { rcmail.http_post('calendar', { action:'subscribe', c:{ id:id, active:cal.active?1:0 } });
event_sources.push(this.calendars[id]);
} }
// insert to #calendar-select options if writeable
select = $('#edit-calendar');
if (fc && !cal.readonly && select.length && !select.find('option[value="'+id+'"]').length) {
$('<option>').attr('value', id).text(Q(cal.name)).appendTo(select);
}
}
/*** startup code ***/
// create list of event sources AKA calendars
var id, cal, active, event_sources = [];
for (id in rcmail.env.calendars) {
cal = rcmail.env.calendars[id];
active = cal.active || false;
add_calendar_source(cal);
// check active calendars // check active calendars
$('#rcmlical'+id+' > .calendar input').data('id', id).get(0).checked = active; $('#rcmlical'+id+' > .calendar input').get(0).checked = active;
if (active) {
event_sources.push(this.calendars[id]);
}
if (!cal.readonly && !this.selected_calendar) { if (!cal.readonly && !this.selected_calendar) {
this.selected_calendar = id; this.selected_calendar = id;
@ -2720,12 +2809,32 @@ function rcube_calendar_ui(settings)
rcmail.enable_command('calendar-remove', !me.calendars[node.id].readonly); rcmail.enable_command('calendar-remove', !me.calendars[node.id].readonly);
}); });
calendars_list.addEventListener('search', function(search){ calendars_list.addEventListener('search', function(search){
console.log(search); // hide search results
if (calenders_search_list) {
calenders_search_container.hide();
calenders_search_list.reset();
}
search_calendars = {};
// send search request(s) to server
if (search.query && search.execute) {
var sources = [ 'folders' /*, 'users'*/ ];
var reqid = rcmail.multi_thread_http_request({
items: sources,
threads: rcmail.env.autocomplete_threads || 1,
action: 'calendar/calendar',
postdata: { action:'search', q:search.query, source:'%s' },
lock: rcmail.display_message(rcmail.get_label('searching'), 'loading'),
onresponse: calendar_search_results
});
listsearch_data = { id:reqid, sources:sources.slice(), num:sources.length };
}
}); });
// init (delegate) event handler on calendar list checkboxes // init (delegate) event handler on calendar list checkboxes
$(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e){ $(rcmail.gui_objects.calendarslist).on('click', 'input[type=checkbox]', function(e){
var id = $(this).data('id'); var id = this.value;
if (me.calendars[id]) { // add or remove event source on click if (me.calendars[id]) { // add or remove event source on click
var action; var action;
if (this.checked) { if (this.checked) {

View file

@ -165,6 +165,15 @@ abstract class calendar_driver
*/ */
abstract function remove_calendar($prop); abstract function remove_calendar($prop);
/**
* Search for shared or otherwise not listed calendars the user has access
*
* @param string Search string
* @param string Section/source to search
* @return array List of calendars
*/
abstract function search_calendars($query, $source);
/** /**
* Add a single event to the database * Add a single event to the database
* *

View file

@ -247,6 +247,19 @@ class database_driver extends calendar_driver
return $this->rc->db->affected_rows($query); return $this->rc->db->affected_rows($query);
} }
/**
* Search for shared or otherwise not listed calendars the user has access
*
* @param string Search string
* @param string Section/source to search
* @return array List of calendars
*/
public function search_calendars($query, $source)
{
// not implemented
return array();
}
/** /**
* Add a single event to the database * Add a single event to the database
* *

View file

@ -52,11 +52,12 @@ class kolab_calendar
$this->imap_folder = $this->name = $imap_folder; $this->imap_folder = $this->name = $imap_folder;
// ID is derrived from folder name // ID is derrived from folder name
$this->id = kolab_storage::folder_id($this->imap_folder); $this->id = kolab_storage::folder_id($this->imap_folder, true);
$old_id = kolab_storage::folder_id($this->imap_folder, false);
// fetch objects from the given IMAP folder // fetch objects from the given IMAP folder
$this->storage = kolab_storage::get_folder($this->imap_folder); $this->storage = kolab_storage::get_folder($this->imap_folder);
$this->ready = $this->storage && !PEAR::isError($this->storage); $this->ready = $this->storage && !PEAR::isError($this->storage) && $this->storage->type !== null;
// Set readonly and alarms flags according to folder permissions // Set readonly and alarms flags according to folder permissions
if ($this->ready) { if ($this->ready) {
@ -76,6 +77,8 @@ class kolab_calendar
$prefs = $this->cal->rc->config->get('kolab_calendars', array()); $prefs = $this->cal->rc->config->get('kolab_calendars', array());
if (isset($prefs[$this->id]['showalarms'])) if (isset($prefs[$this->id]['showalarms']))
$this->alarms = $prefs[$this->id]['showalarms']; $this->alarms = $prefs[$this->id]['showalarms'];
else if (isset($prefs[$old_id]['showalarms']))
$this->alarms = $prefs[$old_id]['showalarms'];
} }
} }

View file

@ -62,6 +62,9 @@ class kolab_driver extends calendar_driver
$this->alarm_types = array('DISPLAY'); $this->alarm_types = array('DISPLAY');
$this->alarm_absolute = false; $this->alarm_absolute = false;
} }
// calendar uses fully encoded identifiers
kolab_storage::$encode_ids = true;
} }
@ -117,6 +120,9 @@ class kolab_driver extends calendar_driver
foreach ($folders as $id => $cal) { foreach ($folders as $id => $cal) {
$fullname = $cal->get_name(); $fullname = $cal->get_name();
$listname = kolab_storage::folder_displayname($fullname, $names); $listname = kolab_storage::folder_displayname($fullname, $names);
$imap_path = explode('/', $cal->name);
$topname = array_pop($imap_path);
$parent_id = kolab_storage::folder_id(join('/', $imap_path), true);
// special handling for virtual folders // special handling for virtual folders
if ($cal->virtual) { if ($cal->virtual) {
@ -143,6 +149,7 @@ class kolab_driver extends calendar_driver
'active' => $cal->storage->is_active(), 'active' => $cal->storage->is_active(),
'owner' => $cal->get_owner(), 'owner' => $cal->get_owner(),
'children' => true, // TODO: determine if that folder indeed has child folders 'children' => true, // TODO: determine if that folder indeed has child folders
'parent' => $parent_id,
'caldavurl' => $cal->get_caldav_url(), 'caldavurl' => $cal->get_caldav_url(),
); );
} }
@ -212,6 +219,26 @@ class kolab_driver extends calendar_driver
return $calendars; return $calendars;
} }
/**
* Get the kolab_calendar instance for the given calendar ID
*
* @param string Calendar identifier (encoded imap folder name)
* @return object kolab_calendar Object nor null if calendar doesn't exist
*/
protected function get_calendar($id)
{
// create calendar object if necesary
if (!$this->calendars[$id] && $id !== self::BIRTHDAY_CALENDAR_ID) {
$foldername = kolab_storage::id_decode($id);
$calendar = new kolab_calendar($foldername, $this->cal);
if ($calendar->ready)
$this->calendars[$calendar->id] = $calendar;
}
return $this->calendars[$id];
}
/** /**
* Create a new calendar assigned to the current user * Create a new calendar assigned to the current user
* *
@ -257,7 +284,7 @@ class kolab_driver extends calendar_driver
*/ */
public function edit_calendar($prop) public function edit_calendar($prop)
{ {
if ($prop['id'] && ($cal = $this->calendars[$prop['id']])) { if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
$prop['oldname'] = $cal->get_realname(); $prop['oldname'] = $cal->get_realname();
$newfolder = kolab_storage::folder_update($prop); $newfolder = kolab_storage::folder_update($prop);
@ -297,9 +324,10 @@ class kolab_driver extends calendar_driver
* *
* @see calendar_driver::subscribe_calendar() * @see calendar_driver::subscribe_calendar()
*/ */
public function subscribe_calendar($prop) public function subscribe_calendar($prop, $permanent = false)
{ {
if ($prop['id'] && ($cal = $this->calendars[$prop['id']])) { if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
if ($permanent) $cal->storage->subscribe($prop['active']);
return $cal->storage->activate($prop['active']); return $cal->storage->activate($prop['active']);
} }
else { else {
@ -321,8 +349,9 @@ class kolab_driver extends calendar_driver
*/ */
public function remove_calendar($prop) public function remove_calendar($prop)
{ {
if ($prop['id'] && ($cal = $this->calendars[$prop['id']])) { if ($prop['id'] && ($cal = $this->get_calendar($prop['id']))) {
$folder = $cal->get_realname(); $folder = $cal->get_realname();
// TODO: unsubscribe if no admin rights
if (kolab_storage::folder_delete($folder)) { if (kolab_storage::folder_delete($folder)) {
// remove color in user prefs (temp. solution) // remove color in user prefs (temp. solution)
$prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array()); $prefs['kolab_calendars'] = $this->rc->config->get('kolab_calendars', array());
@ -339,6 +368,51 @@ class kolab_driver extends calendar_driver
} }
/**
* Search for shared or otherwise not listed calendars the user has access
*
* @param string Search string
* @param string Section/source to search
* @return array List of calendars
*/
public function search_calendars($query, $source)
{
if (!kolab_storage::setup())
return array();
$this->calendars = array();
$imap = $this->rc->get_storage();
// find unsubscribed IMAP folders that have "event" type
if ($source == 'folders') {
$folders = array();
foreach ((array)kolab_storage::list_folders('', '*', 'event', false, $folderdata) as $foldername) {
// FIXME: only consider the last part of the folder path for searching?
$realname = strtolower(rcube_charset::convert($foldername, 'UTF7-IMAP'));
if (strpos($realname, $query) !== false &&
!kolab_storage::folder_is_subscribed($foldername, true) &&
$imap->folder_namespace($foldername) != 'other'
) {
$folders[$foldername] = new kolab_storage_folder($foldername, $folderdata[$foldername]);
}
}
foreach ($folders as $folder) {
$calendar = new kolab_calendar($folder->name, $this->cal);
$this->calendars[$calendar->id] = $calendar;
}
}
else if ($source == 'users') {
// TODO: implement this
}
// don't list the birthday calendar
$this->rc->config->set('calendar_contact_birthdays', false);
return $this->list_calendars();
}
/** /**
* Fetch a single event * Fetch a single event
* *
@ -356,7 +430,7 @@ class kolab_driver extends calendar_driver
} }
if ($cal) { if ($cal) {
if ($storage = $this->calendars[$cal]) { if ($storage = $this->get_calendar($cal)) {
return $storage->get_event($id); return $storage->get_event($id);
} }
} }
@ -383,7 +457,7 @@ class kolab_driver extends calendar_driver
return false; return false;
$cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars)); $cid = $event['calendar'] ? $event['calendar'] : reset(array_keys($this->calendars));
if ($storage = $this->calendars[$cid]) { if ($storage = $this->get_calendar($cid)) {
// handle attachments to add // handle attachments to add
if (!empty($event['attachments'])) { if (!empty($event['attachments'])) {
foreach ($event['attachments'] as $idx => $attachment) { foreach ($event['attachments'] as $idx => $attachment) {
@ -425,7 +499,7 @@ class kolab_driver extends calendar_driver
*/ */
public function move_event($event) public function move_event($event)
{ {
if (($storage = $this->calendars[$event['calendar']]) && ($ev = $storage->get_event($event['id']))) { if (($storage = $this->get_calendar($event['calendar'])) && ($ev = $storage->get_event($event['id']))) {
unset($ev['sequence']); unset($ev['sequence']);
return $this->update_event($event + $ev); return $this->update_event($event + $ev);
} }
@ -441,7 +515,7 @@ class kolab_driver extends calendar_driver
*/ */
public function resize_event($event) public function resize_event($event)
{ {
if (($storage = $this->calendars[$event['calendar']]) && ($ev = $storage->get_event($event['id']))) { if (($storage = $this->get_calendar($event['calendar'])) && ($ev = $storage->get_event($event['id']))) {
unset($ev['sequence']); unset($ev['sequence']);
return $this->update_event($event + $ev); return $this->update_event($event + $ev);
} }
@ -463,7 +537,7 @@ class kolab_driver extends calendar_driver
$success = false; $success = false;
$savemode = $event['_savemode']; $savemode = $event['_savemode'];
if (($storage = $this->calendars[$event['calendar']]) && ($event = $storage->get_event($event['id']))) { if (($storage = $this->get_calendar($event['calendar'])) && ($event = $storage->get_event($event['id']))) {
$event['_savemode'] = $savemode; $event['_savemode'] = $savemode;
$savemode = 'all'; $savemode = 'all';
$master = $event; $master = $event;
@ -566,7 +640,7 @@ class kolab_driver extends calendar_driver
*/ */
public function restore_event($event) public function restore_event($event)
{ {
if ($storage = $this->calendars[$event['calendar']]) { if ($storage = $this->get_calendar($event['calendar'])) {
if (!empty($_SESSION['calendar_restore_event_data'])) if (!empty($_SESSION['calendar_restore_event_data']))
$success = $storage->update_event($_SESSION['calendar_restore_event_data']); $success = $storage->update_event($_SESSION['calendar_restore_event_data']);
else else
@ -586,12 +660,12 @@ class kolab_driver extends calendar_driver
*/ */
private function update_event($event) private function update_event($event)
{ {
if (!($storage = $this->calendars[$event['calendar']])) if (!($storage = $this->get_calendar($event['calendar'])))
return false; return false;
// move event to another folder/calendar // move event to another folder/calendar
if ($event['_fromcalendar'] && $event['_fromcalendar'] != $event['calendar']) { if ($event['_fromcalendar'] && $event['_fromcalendar'] != $event['calendar']) {
if (!($fromcalendar = $this->calendars[$event['_fromcalendar']])) if (!($fromcalendar = $this->get_calendar($event['_fromcalendar'])))
return false; return false;
if ($event['_savemode'] != 'new') { if ($event['_savemode'] != 'new') {
@ -780,18 +854,19 @@ class kolab_driver extends calendar_driver
{ {
if ($calendars && is_string($calendars)) if ($calendars && is_string($calendars))
$calendars = explode(',', $calendars); $calendars = explode(',', $calendars);
else if (!$calendars)
$calendars = array_keys($this->calendars);
$query = array(); $query = array();
if ($modifiedsince) if ($modifiedsince)
$query[] = array('changed', '>=', $modifiedsince); $query[] = array('changed', '>=', $modifiedsince);
$events = $categories = array(); $events = $categories = array();
foreach (array_keys($this->calendars) as $cid) { foreach ($calendars as $cid) {
if ($calendars && !in_array($cid, $calendars)) if ($storage = $this->get_calendar($cid)) {
continue; $events = array_merge($events, $storage->list_events($start, $end, $search, $virtual, $query));
$categories += $storage->categories;
$events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual, $query)); }
$categories += $this->calendars[$cid]->categories;
} }
// add events from the address books birthday calendar // add events from the address books birthday calendar
@ -928,7 +1003,7 @@ class kolab_driver extends calendar_driver
*/ */
public function list_attachments($event) public function list_attachments($event)
{ {
if (!($storage = $this->calendars[$event['calendar']])) if (!($storage = $this->get_calendar($event['calendar'])))
return false; return false;
$event = $storage->get_event($event['id']); $event = $storage->get_event($event['id']);
@ -941,7 +1016,7 @@ class kolab_driver extends calendar_driver
*/ */
public function get_attachment($id, $event) public function get_attachment($id, $event)
{ {
if (!($storage = $this->calendars[$event['calendar']])) if (!($storage = $this->get_calendar($event['calendar'])))
return false; return false;
$event = $storage->get_event($event['id']); $event = $storage->get_event($event['id']);
@ -963,7 +1038,7 @@ class kolab_driver extends calendar_driver
*/ */
public function get_attachment_body($id, $event) public function get_attachment_body($id, $event)
{ {
if (!($cal = $this->calendars[$event['calendar']])) if (!($cal = $this->get_calendar($event['calendar'])))
return false; return false;
return $cal->storage->get_attachment($event['id'], $id); return $cal->storage->get_attachment($event['id'], $id);
@ -1080,7 +1155,7 @@ class kolab_driver extends calendar_driver
ignore_user_abort(true); ignore_user_abort(true);
$cal = get_input_value('source', RCUBE_INPUT_GPC); $cal = get_input_value('source', RCUBE_INPUT_GPC);
if (!($cal = $this->calendars[$cal])) if (!($cal = $this->get_calendar($cal)))
return false; return false;
// trigger updates on folder // trigger updates on folder
@ -1275,7 +1350,7 @@ class kolab_driver extends calendar_driver
public function calendar_acl_form() public function calendar_acl_form()
{ {
$calid = get_input_value('_id', RCUBE_INPUT_GPC); $calid = get_input_value('_id', RCUBE_INPUT_GPC);
if ($calid && ($cal = $this->calendars[$calid])) { if ($calid && ($cal = $this->get_calendar($calid))) {
$folder = $cal->get_realname(); // UTF7 $folder = $cal->get_realname(); // UTF7
$color = $cal->get_color(); $color = $cal->get_color();
} }

View file

@ -157,31 +157,41 @@ class calendar_ui
foreach ((array)$calendars as $id => $prop) { foreach ((array)$calendars as $id => $prop) {
if (!$prop['color']) if (!$prop['color'])
continue; continue;
$color = $prop['color']; $css .= $this->calendar_css_classes($id, $prop, $mode);
$class = 'cal-' . asciiwords($id, true);
$css .= "li.$class, #eventshow .$class { color: #$color }\n";
if ($mode != 1) {
if ($mode == 3) {
$css .= ".fc-event-$class .fc-event-bg {";
$css .= " opacity: 0.9;";
$css .= " filter: alpha(opacity=90);";
}
else {
$css .= ".fc-event-$class, ";
$css .= ".fc-event-$class .fc-event-inner {";
}
if (!$attrib['printmode'])
$css .= " background-color: #$color;";
if ($mode % 2 == 0)
$css .= " border-color: #$color;";
$css .= "}\n";
}
$css .= ".$class .handle { background-color: #$color; }";
} }
return html::tag('style', array('type' => 'text/css'), $css); return html::tag('style', array('type' => 'text/css'), $css);
} }
/**
*
*/
public function calendar_css_classes($id, $prop, $mode)
{
$color = $prop['color'];
$class = 'cal-' . asciiwords($id, true);
$css .= "li.$class, #eventshow .$class { color: #$color }\n";
if ($mode != 1) {
if ($mode == 3) {
$css .= ".fc-event-$class .fc-event-bg {";
$css .= " opacity: 0.9;";
$css .= " filter: alpha(opacity=90);";
}
else {
$css .= ".fc-event-$class, ";
$css .= ".fc-event-$class .fc-event-inner {";
}
if (!$attrib['printmode'])
$css .= " background-color: #$color;";
if ($mode % 2 == 0)
$css .= " border-color: #$color;";
$css .= "}\n";
}
return $css . ".$class .handle { background-color: #$color; }\n";
}
/** /**
* *
*/ */
@ -224,7 +234,7 @@ class calendar_ui
} }
/** /**
* Return html for a structured list &lt;ul&gt; for the mailbox tree * Return html for a structured list <ul> for the folder tree
*/ */
public function list_tree_html(&$node, &$data, &$jsenv, $attrib) public function list_tree_html(&$node, &$data, &$jsenv, $attrib)
{ {
@ -255,7 +265,7 @@ class calendar_ui
/** /**
* Helper method to build a calendar list item (HTML content and js data) * Helper method to build a calendar list item (HTML content and js data)
*/ */
protected function calendar_list_item($id, $prop, &$jsenv) public function calendar_list_item($id, $prop, &$jsenv)
{ {
unset($prop['user_id']); unset($prop['user_id']);
$prop['alarms'] = $this->cal->driver->alarms; $prop['alarms'] = $this->cal->driver->alarms;
@ -283,7 +293,7 @@ class calendar_ui
if (!$attrib['activeonly'] || $prop['active']) { if (!$attrib['activeonly'] || $prop['active']) {
$content = html::div($class, $content = html::div($class,
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') . ($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
html::span('handle', '&nbsp;')) . html::span(array('class' => 'handle', 'style' => "background-color: #" . ($prop['color'] ?: 'f00')), '&nbsp;')) .
html::span(array('class' => 'calname', 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname']) html::span(array('class' => 'calname', 'title' => $title), $prop['editname'] ? Q($prop['editname']) : $prop['listname'])
); );
} }

View file

@ -86,6 +86,7 @@ $labels['nmonthsback'] = '$nr months back';
$labels['showurl'] = 'Show calendar URL'; $labels['showurl'] = 'Show calendar URL';
$labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.'; $labels['showurldescription'] = 'Use the following address to access (read only) your calendar from other applications. You can copy and paste this into any calendar software that supports the iCal format.';
$labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.'; $labels['caldavurldescription'] = 'Copy this address to a <a href="http://en.wikipedia.org/wiki/CalDAV" target="_blank">CalDAV</a> client application (e.g. Evolution or Mozilla Thunderbird) to fully synchronize this specific calendar with your computer or mobile device.';
$labels['calsearchresults'] = 'Additional Results';
// agenda view // agenda view
$labels['listrange'] = 'Range to display:'; $labels['listrange'] = 'Range to display:';

View file

@ -168,27 +168,23 @@ pre {
top: 68px; top: 68px;
} }
#calendarslist li { #calendars .treelist li {
margin: 0; margin: 0;
position: relative; position: relative;
} }
#calendarslist li label { #calendars .treelist li div.folder,
display: block; #calendars .treelist li div.calendar {
}
#calendarslist li div.folder,
#calendarslist li div.calendar {
position: relative; position: relative;
height: 28px; height: 28px;
} }
#calendarslist li div.virtual { #calendars .treelist li div.virtual {
height: 22px; height: 22px;
} }
#calendarslist li span.calname { #calendars .treelist li span.calname {
display: block; display: block;
padding: 0px 30px 2px 2px; padding: 0px 30px 2px 2px;
position: absolute; position: absolute;
@ -203,17 +199,17 @@ pre {
color: #004458; color: #004458;
} }
#calendarslist li div.virtual > span.calname { #calendars .treelist li div.virtual > span.calname {
color: #aaa; color: #aaa;
top: 4px; top: 4px;
left: 20px; left: 20px;
} }
#calendarslist.flat li span.calname { #calendars .treelist.flat li span.calname {
left: 24px; left: 24px;
} }
#calendarslist li span.handle { #calendars .treelist li span.handle {
display: inline-block; display: inline-block;
position: absolute; position: absolute;
top: 8px; top: 8px;
@ -229,66 +225,77 @@ pre {
box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3); box-shadow: inset 0px 0 1px 1px rgba(0, 0, 0, 0.3);
} }
#calendarslist li input { #calendars .treelist li input {
position: absolute; position: absolute;
top: 5px; top: 5px;
left: 18px; left: 18px;
} }
#calendarslist li div.treetoggle { #calendars .treelist li div.treetoggle {
top: 8px; top: 8px;
} }
#calendarslist li.virtual div.treetoggle { #calendars .treelist li.virtual div.treetoggle {
top: 6px; top: 6px;
} }
#calendarslist.flat li input { #calendars .treelist.flat li input {
left: 4px; left: 4px;
} }
#calendarslist ul li div.folder, #calendars .treelist ul li div.folder,
#calendarslist ul li div.calendar { #calendars .treelist ul li div.calendar {
margin-left: 16px; margin-left: 16px;
} }
#calendarslist ul ul li div.folder, #calendars .treelist ul ul li div.folder,
#calendarslist ul ul li div.calendar { #calendars .treelist ul ul li div.calendar {
margin-left: 32px; margin-left: 32px;
} }
#calendarslist ul ul ul li div.folder, #calendars .treelist ul ul ul li div.folder,
#calendarslist ul ul ul li div.calendar { #calendars .treelist ul ul ul li div.calendar {
margin-left: 48px; margin-left: 48px;
} }
#calendarslist li.selected { #calendars .treelist li.selected {
background-color: #c7e3ef; background-color: #c7e3ef;
} }
#calendarslist li.selected > span.calname { #calendars .treelist li.selected > span.calname {
font-weight: bold; font-weight: bold;
} }
#calendarslist div.readonly span.calname { #calendars .treelist div.readonly span.calname {
background-position: right -20px; background-position: right -20px;
} }
/*
#calendarslist div.other span.calname { #calendars .treelist div.other span.calname {
background-position: right -38px; background-position: right -38px;
} }
#calendarslist div.other.readonly span.calname { #calendars .treelist div.other.readonly span.calname {
background-position: right -56px; background-position: right -56px;
} }
#calendarslist div.shared span.calname { #calendars .treelist div.shared span.calname {
background-position: right -74px; background-position: right -74px;
} }
#calendarslist div.shared.readonly span.calname { #calendars .treelist div.shared.readonly span.calname {
background-position: right -92px; background-position: right -92px;
} }
*/
#calendars .searchresults {
background: #b0ccd7;
margin-top: 8px;
}
#calendars .searchresults .boxtitle {
background: none;
padding: 2px 8px 2px 8px;
}
#calfeedurl, #calfeedurl,
#caldavurl { #caldavurl {

View file

@ -247,7 +247,7 @@ class kolab_addressbook extends rcube_plugin
$names = array(); $names = array();
foreach ($folders as $folder) { foreach ($folders as $folder) {
// create instance of rcube_contacts // create instance of rcube_contacts
$abook_id = kolab_storage::folder_id($folder->name); $abook_id = kolab_storage::folder_id($folder->name, false);
$abook = new rcube_kolab_contacts($folder->name); $abook = new rcube_kolab_contacts($folder->name);
$this->sources[$abook_id] = $abook; $this->sources[$abook_id] = $abook;
} }

View file

@ -7,7 +7,7 @@
* @author Thomas Bruederli <bruederli@kolabsys.com> * @author Thomas Bruederli <bruederli@kolabsys.com>
* @author Aleksander Machniak <machniak@kolabsys.com> * @author Aleksander Machniak <machniak@kolabsys.com>
* *
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> * Copyright (C) 2012-2014, Kolab Systems AG <contact@kolabsys.com>
* *
* This program is free software: you can redistribute it and/or modify * This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as * it under the terms of the GNU Affero General Public License as
@ -37,6 +37,7 @@ class kolab_storage
public static $version = '3.0'; public static $version = '3.0';
public static $last_error; public static $last_error;
public static $encode_ids = false;
private static $ready = false; private static $ready = false;
private static $subscriptions; private static $subscriptions;
@ -226,13 +227,39 @@ class kolab_storage
/** /**
* Creates folder ID from folder name * Creates folder ID from folder name
* *
* @param string $folder Folder name (UTF7-IMAP) * @param string $folder Folder name (UTF7-IMAP)
* * @param boolean $enc Use lossless encoding
* @return string Folder ID string * @return string Folder ID string
*/ */
public static function folder_id($folder) public static function folder_id($folder, $enc = null)
{ {
return asciiwords(strtr($folder, '/.-', '___')); return $enc == true || ($enc === null && self::$encode_ids) ?
self::id_encode($folder) :
asciiwords(strtr($folder, '/.-', '___'));
}
/**
* Encode the given ID to a safe ascii representation
*
* @param string $id Arbitrary identifier string
*
* @return string Ascii representation
*/
public static function id_encode($id)
{
return rtrim(strtr(base64_encode($id), '+/', '-_'), '=');
}
/**
* Convert the given identifier back to it's raw value
*
* @param string $id Ascii identifier
* @return string Raw identifier string
*/
public static function id_decode($id)
{
return base64_decode(str_pad(strtr($id, '-_', '+/'), strlen($id) % 4, '=', STR_PAD_RIGHT));
} }
@ -665,11 +692,16 @@ class kolab_storage
if (!$filter) { if (!$filter) {
// Get ALL folders list, standard way // Get ALL folders list, standard way
if ($subscribed) { if ($subscribed) {
return self::$imap->list_folders_subscribed($root, $mbox); $folders = self::$imap->list_folders_subscribed($root, $mbox);
// add temporarily subscribed folders
if (is_array($_SESSION['kolab_subscribed_folders']))
$folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders']));
} }
else { else {
return self::$imap->list_folders($root, $mbox); $folders = self::$imap->list_folders($root, $mbox);
} }
return $folders;
} }
$prefix = $root . $mbox; $prefix = $root . $mbox;
@ -696,6 +728,10 @@ class kolab_storage
// Get folders list // Get folders list
if ($subscribed) { if ($subscribed) {
$folders = self::$imap->list_folders_subscribed($root, $mbox); $folders = self::$imap->list_folders_subscribed($root, $mbox);
// add temporarily subscribed folders
if (is_array($_SESSION['kolab_subscribed_folders']))
$folders = array_unique(array_merge($folders, $_SESSION['kolab_subscribed_folders']));
} }
else { else {
$folders = self::$imap->list_folders($root, $mbox); $folders = self::$imap->list_folders($root, $mbox);
@ -903,17 +939,19 @@ class kolab_storage
* Check subscription status of this folder * Check subscription status of this folder
* *
* @param string $folder Folder name * @param string $folder Folder name
* @param boolean $temp Include temporary/session subscriptions
* *
* @return boolean True if subscribed, false if not * @return boolean True if subscribed, false if not
*/ */
public static function folder_is_subscribed($folder) public static function folder_is_subscribed($folder, $temp = false)
{ {
if (self::$subscriptions === null) { if (self::$subscriptions === null) {
self::setup(); self::setup();
self::$subscriptions = self::$imap->list_folders_subscribed(); self::$subscriptions = self::$imap->list_folders_subscribed();
} }
return in_array($folder, self::$subscriptions); return in_array($folder, self::$subscriptions) ||
($temp && in_array($folder, (array)$_SESSION['kolab_subscribed_folders']));
} }
@ -921,14 +959,25 @@ class kolab_storage
* Change subscription status of this folder * Change subscription status of this folder
* *
* @param string $folder Folder name * @param string $folder Folder name
* @param boolean $temp Only subscribe temporarily for the current session
* *
* @return True on success, false on error * @return True on success, false on error
*/ */
public static function folder_subscribe($folder) public static function folder_subscribe($folder, $temp = false)
{ {
self::setup(); self::setup();
if (self::$imap->subscribe($folder)) { // temporary/session subscription
if ($temp) {
if (self::folder_is_subscribed($folder)) {
return true;
}
else if (!is_array($_SESSION['kolab_subscribed_folders']) || !in_array($folder, $_SESSION['kolab_subscribed_folders'])) {
$_SESSION['kolab_subscribed_folders'][] = $folder;
return true;
}
}
else if (self::$imap->subscribe($folder)) {
self::$subscriptions === null; self::$subscriptions === null;
return true; return true;
} }
@ -981,6 +1030,8 @@ class kolab_storage
*/ */
public static function folder_activate($folder) public static function folder_activate($folder)
{ {
// activation implies temporary subscription
self::folder_subscribe($folder, true);
return self::set_state($folder, true); return self::set_state($folder, true);
} }
@ -994,6 +1045,11 @@ class kolab_storage
*/ */
public static function folder_deactivate($folder) public static function folder_deactivate($folder)
{ {
// remove from temp subscriptions
if (is_array($_SESSION['kolab_subscribed_folders']) && ($i = array_search($folder, $_SESSION['kolab_subscribed_folders'])) !== false) {
unset($_SESSION['kolab_subscribed_folders'][$i]);
}
return self::set_state($folder, false); return self::set_state($folder, false);
} }