Show complete folder hierarchy in calendars and tasklist listings with non-clickable virtual parent folders
This commit is contained in:
parent
8459eb1c78
commit
9ffd3031e1
9 changed files with 217 additions and 37 deletions
|
@ -33,6 +33,7 @@ class kolab_calendar
|
|||
public $alarms = false;
|
||||
public $categories = array();
|
||||
public $storage;
|
||||
public $name;
|
||||
|
||||
private $cal;
|
||||
private $events = array();
|
||||
|
@ -48,7 +49,7 @@ class kolab_calendar
|
|||
$this->cal = $calendar;
|
||||
|
||||
if (strlen($imap_folder))
|
||||
$this->imap_folder = $imap_folder;
|
||||
$this->imap_folder = $this->name = $imap_folder;
|
||||
|
||||
// ID is derrived from folder name
|
||||
$this->id = kolab_storage::folder_id($this->imap_folder);
|
||||
|
|
|
@ -105,31 +105,79 @@ class kolab_driver extends calendar_driver
|
|||
}
|
||||
}
|
||||
|
||||
$calendars = $this->filter_calendars(false, $active, $personal);
|
||||
$names = array();
|
||||
$folders = $this->filter_calendars(false, $active, $personal);
|
||||
$calendars = $names = array();
|
||||
|
||||
foreach ($calendars as $id => $cal) {
|
||||
$name = kolab_storage::folder_displayname($cal->get_name(), $names);
|
||||
// include virtual folders for a full folder tree
|
||||
if (!$active && !$personal && !$this->rc->output->ajax_call)
|
||||
$folders = $this->_folder_hierarchy($folders, $this->rc->get_storage()->get_hierarchy_delimiter());
|
||||
|
||||
$calendars[$id] = array(
|
||||
'id' => $cal->id,
|
||||
'name' => $name,
|
||||
'editname' => $cal->get_foldername(),
|
||||
'color' => $cal->get_color(),
|
||||
'readonly' => $cal->readonly,
|
||||
'showalarms' => $cal->alarms,
|
||||
'class_name' => $cal->get_namespace(),
|
||||
'default' => $cal->storage->default,
|
||||
'active' => $cal->storage->is_active(),
|
||||
'owner' => $cal->get_owner(),
|
||||
'children' => true, // TODO: determine if that folder indeed has child folders
|
||||
'caldavurl' => $cal->get_caldav_url(),
|
||||
);
|
||||
foreach ($folders as $id => $cal) {
|
||||
$fullname = $cal->get_name();
|
||||
$name = kolab_storage::folder_displayname($fullname, $names);
|
||||
|
||||
// special handling for virtual folders
|
||||
if ($cal->virtual) {
|
||||
$calendars[$cal->id] = array(
|
||||
'id' => $cal->id,
|
||||
'name' => $name,
|
||||
'virtual' => true,
|
||||
);
|
||||
}
|
||||
else {
|
||||
$calendars[$cal->id] = array(
|
||||
'id' => $cal->id,
|
||||
'name' => $name,
|
||||
'altname' => $fullname,
|
||||
'editname' => $cal->get_foldername(),
|
||||
'color' => $cal->get_color(),
|
||||
'readonly' => $cal->readonly,
|
||||
'showalarms' => $cal->alarms,
|
||||
'class_name' => $cal->get_namespace(),
|
||||
'default' => $cal->storage->default,
|
||||
'active' => $cal->storage->is_active(),
|
||||
'owner' => $cal->get_owner(),
|
||||
'children' => true, // TODO: determine if that folder indeed has child folders
|
||||
'caldavurl' => $cal->get_caldav_url(),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
return $calendars;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the folder tree and add the missing parents as virtual folders
|
||||
*/
|
||||
private function _folder_hierarchy($folders, $delim)
|
||||
{
|
||||
$parents = array();
|
||||
$existing = array_map(function($folder){ return $folder->get_name(); }, $folders);
|
||||
foreach ($folders as $id => $folder) {
|
||||
$path = explode($delim, $folder->name);
|
||||
array_pop($path);
|
||||
|
||||
// skip top folders or ones with a custom displayname
|
||||
if (count($path) <= 1 || kolab_storage::custom_displayname($folder->name))
|
||||
continue;
|
||||
|
||||
while (count($path) > 1 && ($parent = join($delim, $path))) {
|
||||
if (!in_array($parent, $existing) && !$parents[$parent]) {
|
||||
$name = kolab_storage::object_name($parent, $folder->get_namespace());
|
||||
$parents[$parent] = new virtual_kolab_calendar($name, $folder->get_namespace());
|
||||
$parents[$parent]->id = kolab_storage::folder_id($parent);
|
||||
}
|
||||
array_pop($path);
|
||||
}
|
||||
}
|
||||
|
||||
// add virtual parents to the list and sort again
|
||||
if (count($parents)) {
|
||||
$folders = kolab_storage::sort_folders(array_merge($folders, array_values($parents)));
|
||||
}
|
||||
|
||||
return $folders;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get list of calendars according to specified filters
|
||||
|
@ -1036,7 +1084,7 @@ class kolab_driver extends calendar_driver
|
|||
// Disable folder name input
|
||||
if (!empty($options) && ($options['norename'] || $options['protected'])) {
|
||||
$input_name = new html_hiddenfield(array('name' => 'name', 'id' => 'calendar-name'));
|
||||
$formfields['name']['value'] = Q(str_replace($delim, ' » ', kolab_storage::object_name($folder)))
|
||||
$formfields['name']['value'] = kolab_storage::object_name($folder)
|
||||
. $input_name->show($folder);
|
||||
}
|
||||
|
||||
|
@ -1224,3 +1272,32 @@ class kolab_driver extends calendar_driver
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Helper class that represents a virtual IMAP folder
|
||||
* with a subset of the kolab_calendar API.
|
||||
*/
|
||||
class virtual_kolab_calendar
|
||||
{
|
||||
public $name;
|
||||
public $namespace;
|
||||
public $virtual = true;
|
||||
|
||||
public function __construct($name, $ns)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->namespace = $ns;
|
||||
}
|
||||
|
||||
public function get_name()
|
||||
{
|
||||
return $this->name;
|
||||
}
|
||||
|
||||
public function get_namespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -194,21 +194,24 @@ class calendar_ui
|
|||
$prop['attachments'] = $this->cal->driver->attachments;
|
||||
$prop['undelete'] = $this->cal->driver->undelete;
|
||||
$prop['feedurl'] = $this->cal->get_url(array('_cal' => $this->cal->ical_feed_hash($id) . '.ics', 'action' => 'feed'));
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
if (!$prop['virtual'])
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
$html_id = html_identifier($id);
|
||||
$class = 'cal-' . asciiwords($id, true);
|
||||
$listname = html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET);
|
||||
$title = strlen($listname) > 25 ? $listname : '';
|
||||
$title = !empty($prop['altname']) && $prop['altname'] != $prop['name'] ? html_entity_decode($prop['altname'], ENT_COMPAT, RCMAIL_CHARSET) : '';
|
||||
|
||||
if ($prop['readonly'])
|
||||
if ($prop['virtual'])
|
||||
$class .= ' virtual';
|
||||
else if ($prop['readonly'])
|
||||
$class .= ' readonly';
|
||||
if ($prop['class_name'])
|
||||
$class .= ' '.$prop['class_name'];
|
||||
|
||||
$li .= html::tag('li', array('id' => 'rcmlical' . $html_id, 'class' => $class),
|
||||
html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
||||
html::span('handle', ' ') .
|
||||
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_cal[]', 'value' => $id, 'checked' => $prop['active']), '') .
|
||||
html::span('handle', ' ')) .
|
||||
html::span(array('class' => 'calname', 'title' => $title), $prop['name']));
|
||||
}
|
||||
|
||||
|
|
|
@ -164,6 +164,10 @@ pre {
|
|||
background-position: 0 -92px;
|
||||
}
|
||||
|
||||
#calendarslist li.virtual span.calname {
|
||||
color: #666;
|
||||
}
|
||||
|
||||
#calfeedurl,
|
||||
#caldavurl {
|
||||
width: 98%;
|
||||
|
|
|
@ -157,6 +157,10 @@ pre {
|
|||
position: relative;
|
||||
}
|
||||
|
||||
#calendarslist li.virtual {
|
||||
padding-top: 2px;
|
||||
}
|
||||
|
||||
#calendarslist li label {
|
||||
display: block;
|
||||
}
|
||||
|
@ -225,6 +229,10 @@ pre {
|
|||
background-position: right -92px;
|
||||
}
|
||||
|
||||
#calendarslist li.virtual span.calname {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
#calfeedurl,
|
||||
#caldavurl {
|
||||
width: 98%;
|
||||
|
|
|
@ -80,21 +80,36 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
}
|
||||
|
||||
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
|
||||
$prefs = $this->rc->config->get('kolab_tasklists', array());
|
||||
$listnames = array();
|
||||
|
||||
$prefs = $this->rc->config->get('kolab_tasklists', array());
|
||||
// include virtual folders for a full folder tree
|
||||
if (!$this->rc->output->ajax_call)
|
||||
$folders = $this->_folder_hierarchy($folders, $delim);
|
||||
|
||||
foreach ($folders as $folder) {
|
||||
$utf7name = $folder->name;
|
||||
$this->folders[$folder->name] = $folder;
|
||||
|
||||
$path_imap = explode($delim, $utf7name);
|
||||
$editname = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP'); // pop off raw name part
|
||||
$path_imap = join($delim, $path_imap);
|
||||
|
||||
$name = kolab_storage::folder_displayname(kolab_storage::object_name($utf7name), $listnames);
|
||||
$fullname = kolab_storage::object_name($utf7name);
|
||||
$name = kolab_storage::folder_displayname($fullname, $listnames);
|
||||
|
||||
// special handling for virtual folders
|
||||
if ($folder->virtual) {
|
||||
$list_id = kolab_storage::folder_id($utf7name);
|
||||
$this->lists[$list_id] = array(
|
||||
'id' => $list_id,
|
||||
'name' => $name,
|
||||
'virtual' => true,
|
||||
);
|
||||
continue;
|
||||
}
|
||||
|
||||
if ($folder->get_namespace() == 'personal') {
|
||||
$norename = false;
|
||||
$readonly = false;
|
||||
$alarms = true;
|
||||
}
|
||||
|
@ -105,16 +120,20 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
if (strpos($rights, 'i') !== false)
|
||||
$readonly = false;
|
||||
}
|
||||
$info = $folder->get_folder_info();
|
||||
$norename = $readonly || $info['norename'] || $info['protected'];
|
||||
}
|
||||
|
||||
$list_id = kolab_storage::folder_id($utf7name);
|
||||
$tasklist = array(
|
||||
'id' => $list_id,
|
||||
'name' => $name,
|
||||
'altname' => $fullname,
|
||||
'editname' => $editname,
|
||||
'color' => $folder->get_color('0000CC'),
|
||||
'showalarms' => isset($prefs[$list_id]['showalarms']) ? $prefs[$list_id]['showalarms'] : $alarms,
|
||||
'editable' => !$readonly,
|
||||
'editable' => !$readionly,
|
||||
'norename' => $norename,
|
||||
'active' => $folder->is_active(),
|
||||
'parentfolder' => $path_imap,
|
||||
'default' => $folder->default,
|
||||
|
@ -123,9 +142,42 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
);
|
||||
$this->lists[$tasklist['id']] = $tasklist;
|
||||
$this->folders[$tasklist['id']] = $folder;
|
||||
$this->folders[$folder->name] = $folder;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the folder tree and add the missing parents as virtual folders
|
||||
*/
|
||||
private function _folder_hierarchy($folders, $delim)
|
||||
{
|
||||
$parents = array();
|
||||
$existing = array_map(function($folder){ return $folder->name; }, $folders);
|
||||
foreach ($folders as $id => $folder) {
|
||||
$path = explode($delim, $folder->name);
|
||||
array_pop($path);
|
||||
|
||||
// skip top folders or ones with a custom displayname
|
||||
if (count($path) <= 1 || kolab_storage::custom_displayname($folder->name))
|
||||
continue;
|
||||
|
||||
while (count($path) > 1 && ($parent = join($delim, $path))) {
|
||||
if (!in_array($parent, $existing) && !$parents[$parent]) {
|
||||
$parents[$parent] = new virtual_kolab_storage_folder($parent, $folder->get_namespace());
|
||||
}
|
||||
array_pop($path);
|
||||
}
|
||||
}
|
||||
|
||||
// add virtual parents to the list and sort again
|
||||
if (count($parents)) {
|
||||
$folders = kolab_storage::sort_folders(array_merge($folders, array_values($parents)));
|
||||
}
|
||||
|
||||
return $folders;
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Get a list of available task lists from this source
|
||||
*/
|
||||
|
@ -848,3 +900,26 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* Helper class that represents a virtual IMAP folder
|
||||
* with a subset of the kolab_storage_folder API.
|
||||
*/
|
||||
class virtual_kolab_storage_folder
|
||||
{
|
||||
public $name;
|
||||
public $namespace;
|
||||
public $virtual = true;
|
||||
|
||||
public function __construct($name, $ns)
|
||||
{
|
||||
$this->name = $name;
|
||||
$this->namespace = $ns;
|
||||
}
|
||||
|
||||
public function get_namespace()
|
||||
{
|
||||
return $this->namespace;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
@ -195,6 +195,11 @@ body.attachmentwin #topnav .topright {
|
|||
white-space: nowrap;
|
||||
}
|
||||
|
||||
#tasklists li.virtual {
|
||||
padding-top: 4px;
|
||||
height: 16px;
|
||||
}
|
||||
|
||||
#tasklists li label {
|
||||
display: block;
|
||||
}
|
||||
|
@ -240,6 +245,10 @@ body.attachmentwin #topnav .topright {
|
|||
background-position: right -214px;
|
||||
}
|
||||
|
||||
#tasklists li.virtual span.listname {
|
||||
color: #aaa;
|
||||
}
|
||||
|
||||
#tasklists li input {
|
||||
position: absolute;
|
||||
top: 5px;
|
||||
|
|
|
@ -1415,7 +1415,7 @@ function rcube_tasklist_ui(settings)
|
|||
list = { name:'', editable:true, showalarms:true };
|
||||
|
||||
// fill edit form
|
||||
var name = $('#taskedit-tasklistame').prop('disabled', !list.editable).val(list.editname || list.name),
|
||||
var name = $('#taskedit-tasklistame').prop('disabled', list.norename||false).val(list.editname || list.name),
|
||||
alarms = $('#taskedit-showalarms').prop('checked', list.showalarms).get(0),
|
||||
parent = $('#taskedit-parentfolder').val(list.parentfolder);
|
||||
|
||||
|
@ -1467,7 +1467,7 @@ function rcube_tasklist_ui(settings)
|
|||
function list_remove(id)
|
||||
{
|
||||
var list = me.tasklists[id];
|
||||
if (list && list.editable && confirm(rcmail.gettext(list.children ? 'deletelistconfirmrecursive' : 'deletelistconfirm', 'tasklist'))) {
|
||||
if (list && !list.norename && confirm(rcmail.gettext(list.children ? 'deletelistconfirmrecursive' : 'deletelistconfirm', 'tasklist'))) {
|
||||
saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
|
||||
rcmail.http_post('tasklist', { action:'remove', l:{ id:list.id } });
|
||||
return true;
|
||||
|
|
|
@ -100,20 +100,23 @@ class tasklist_ui
|
|||
$prop['undelete'] = $this->plugin->driver->undelete;
|
||||
$prop['sortable'] = $this->plugin->driver->sortable;
|
||||
$prop['attachments'] = $this->plugin->driver->attachments;
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
if (!$prop['virtual'])
|
||||
$jsenv[$id] = $prop;
|
||||
|
||||
$html_id = html_identifier($id);
|
||||
$class = 'tasks-' . asciiwords($id, true);
|
||||
$listname = html_entity_decode($prop['name'], ENT_COMPAT, RCMAIL_CHARSET);
|
||||
$title = strlen($listname) > 25 ? $listname : '';
|
||||
$title = !empty($prop['altname']) && $prop['altname'] != $prop['name'] ? html_entity_decode($prop['altname'], ENT_COMPAT, RCMAIL_CHARSET) : '';
|
||||
|
||||
if (!$prop['editable'])
|
||||
if ($prop['virtual'])
|
||||
$class .= ' virtual';
|
||||
else if (!$prop['editable'])
|
||||
$class .= ' readonly';
|
||||
if ($prop['class_name'])
|
||||
$class .= ' '.$prop['class_name'];
|
||||
|
||||
$li .= html::tag('li', array('id' => 'rcmlitasklist' . $html_id, 'class' => $class),
|
||||
html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'])) .
|
||||
($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active']))) .
|
||||
html::span('handle', ' ') .
|
||||
html::span(array('class' => 'listname', 'title' => $title), $prop['name']));
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue