Added addressbook directories list styling with readonly and namespace icons.

Improved addressbook folders naming/structure representation on the list.
Added addressbook folders management features including (delete/create/rename).
This commit is contained in:
Aleksander Machniak (Kolab Systems) 2011-06-24 20:13:43 +02:00
parent a69febd59f
commit 17e5d1b7c3
6 changed files with 721 additions and 8 deletions

View file

@ -0,0 +1,256 @@
if (window.rcmail) {
rcmail.addEventListener('init', function() {
rcmail.set_book_actions();
if (rcmail.gui_objects.editform && rcmail.env.action.match(/^plugin\.book/)) {
rcmail.enable_command('book-save', true);
}
});
rcmail.addEventListener('listupdate', function() {
rcmail.set_book_actions();
});
}
// (De-)activates address book management commands
rcube_webmail.prototype.set_book_actions = function()
{
var source = this.env.source,
sources = this.env.address_sources;
this.enable_command('book-create', true);
this.enable_command('book-edit', 'book-delete', source && sources[source] && sources[source].kolab && sources[source].editable);
};
rcube_webmail.prototype.book_create = function()
{
this.book_show_contentframe('create');
};
rcube_webmail.prototype.book_edit = function()
{
this.book_show_contentframe('edit');
};
rcube_webmail.prototype.book_delete = function()
{
if (this.env.source != '' && confirm(this.get_label('kolab_addressbook.bookdeleteconfirm'))) {
var lock = this.set_busy(true, 'kolab_addressbook.bookdeleting');
this.http_request('plugin.book', '_act=delete&_source='+urlencode(this.book_realname()), lock);
}
};
// displays page with book edit/create form
rcube_webmail.prototype.book_show_contentframe = function(action, framed)
{
var add_url = '', target = window;
// unselect contact
this.contact_list.clear_selection();
this.enable_command('edit', 'delete', 'compose', false);
if (this.env.contentframe && window.frames && window.frames[this.env.contentframe]) {
add_url = '&_framed=1';
target = window.frames[this.env.contentframe];
this.show_contentframe(true);
}
else if (framed)
return false;
if (action) {
this.set_busy(true);
this.location_href(this.env.comm_path+'&_action=plugin.book&_act='+action
+'&_source='+urlencode(this.book_realname())
+add_url, target);
}
return true;
};
// submits book create/update form
rcube_webmail.prototype.book_save = function()
{
var form = this.gui_objects.editform,
input = $("input[name='_name']", form)
if (input.length && input.val() == '') {
alert(this.get_label('kolab_addressbook.nobooknamewarning'));
input.focus();
return;
}
input = this.display_message(this.get_label('kolab_addressbook.booksaving'), 'loading');
$('<input type="hidden" name="_unlock" />').val(input).appendTo(form);
form.submit();
};
// action executed after book delete
rcube_webmail.prototype.book_delete_done = function(id)
{
var n, g, li = this.get_folder_li(id), groups = this.env.contactgroups;
// remove folder and its groups rows
for (n in groups)
if (groups[n].source == id && (g = this.get_folder_li(n))) {
$(g).remove();
delete this.env.contactgroups[n];
}
$(li).remove();
delete this.env.address_sources[id];
delete this.env.contactfolders[id];
};
// action executed after book create/update
rcube_webmail.prototype.book_update = function(data, old)
{
var n, i, id, len, row, refrow, olddata, name = '', realname = '', sources, level,
folders = [], class_name = 'addressbook',
list = this.gui_objects.folderlist,
groups = this.env.contactgroups;
this.env.contactfolders[data.id] = this.env.address_sources[data.id] = data;
this.show_contentframe(false);
// update
if (old && old != data.id) {
olddata = this.env.address_sources[old];
delete this.env.address_sources[old];
delete this.env.contactfolders[old];
// update source ID in groups
for (n in groups)
if (groups[n].source == old)
this.env.contactgroups[n].source = data.id;
refrow = $('#rcmli'+old);
}
// create
else if (!old) {
refrow = $('li', list).get(0);
// this shouldn't happen
if (!refrow)
this.redirect(this.get_task_url('addressbook'));
}
if (!refrow)
return;
sources = this.env.address_sources;
// clone a table row if there are existing rows
row = $(refrow).clone();
// set row attributes
if (data.readonly)
class_name += ' readonly';
if (data.class)
class_name += ' '+data.class;
// updated currently selected book
if (this.env.source != '' && this.env.source == old) {
class_name += ' selected';
this.env.source = data.id;
}
row.attr({id: 'rcmli'+data.id, 'class': class_name});
$('a', row).html(data.name).attr({onclick: '', rel: data.id, href: '#'})
.click({id: data.id}, function(e) { return rcmail.command('list', e.data.id, this); });
// sort kolab folders, to put the new one in order
for (n in sources)
if (sources[n].kolab && (name = sources[n].realname))
folders.push(name);
folders.sort();
// find current id
for (n=0, len=folders.length; n<len; n++)
if (folders[n] == data.realname)
break;
// add row
if (n && n < len) {
// find the row before
name = folders[n-1];
for (n in sources)
if (sources[n].realname && sources[n].realname == name) {
row.insertAfter('#rcmli'+n);
break;
}
}
else if (olddata) {
row.insertBefore(refrow);
}
else {
row.appendTo(list);
}
if (olddata) {
// remove old row (just after the new row has been inserted)
refrow.remove();
old += '-';
level = olddata.realname.split(this.env.delimiter).length - data.realname.split(this.env.delimiter).length;
// update (realname and ID of) subfolders
for (n in sources) {
if (n.indexOf(old) == 0) {
// new ID
id = data.id + '-' + n.substr(old.length);
name = sources[n].name;
realname = data.realname + sources[n].realname.substr(olddata.realname.length);
// update display name
if (level > 0) {
for (i=level; i>0; i--)
name = name.replace(/^&nbsp;&nbsp;/, '');
}
else if (level < 0) {
for (i=level; i<0; i++)
name = '&nbsp;&nbsp;' + name;
}
// update existing row
refrow = $('#rcmli'+n);
refrow.remove().attr({id: 'rcmli'+id});
$('a', refrow).html(name).attr({onclick: '', rel: id, href: '#'})
.click({id: id}, function(e) { return rcmail.command('list', e.data.id, this); });
// move the row to the new place
refrow.insertAfter(row);
row = refrow;
// update list data
sources[n].id = id;
sources[n].name = name;
sources[n].realname = realname;
this.env.address_sources[id] = this.env.contactfolders[id] = sources[n];
delete this.env.address_sources[n];
delete this.env.contactfolders[n];
// update groups
for (i in groups) {
if (groups[i].source == n) {
// update existing row
refrow = $('#rcmli'+i);
refrow.remove().attr({id: 'rcmliG'+id+groups[i].id});
$('a', refrow).attr('onclick', '')
.click({source: id, id: groups[i].id}, function(e) {
return rcmail.command('listgroup', {'source': e.data.source, 'id': e.data.id}, this); });
refrow.insertAfter(row);
row = refrow;
// update group data
groups[i].source = id;
this.env.contactgroups['G'+id+groups[i].id] = groups[i];
delete this.env.contactgroups[i];
}
}
}
}
}
};
// returns real IMAP folder name
rcube_webmail.prototype.book_realname = function()
{
var source = this.env.source, sources = this.env.address_sources;
return source != '' && sources[source] && sources[source].realname ? sources[source].realname : '';
};

View file

@ -0,0 +1,273 @@
<?php
/**
* Kolab address book UI
*
* @author Aleksander Machniak <machniak@kolabsys.com>
*
* Copyright (C) 2011, Kolab Systems AG
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License version 2
* as published by the Free Software Foundation.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
class kolab_addressbook_ui
{
private $plugin;
private $rc;
/**
* Class constructor
*
* @param kolab_addressbook $plugin Plugin object
*/
public function __construct($plugin)
{
$this->rc = rcmail::get_instance();
$this->plugin = $plugin;
$this->init_ui();
}
/**
* Adds folders management functionality to Addressbook UI
*/
private function init_ui()
{
if (!empty($this->rc->action) && !preg_match('/^plugin\.book/', $this->rc->action)) {
return;
}
// Include script
$this->plugin->include_script('kolab_addressbook.js');
if (empty($this->rc->action)) {
// Include stylesheet (for directorylist)
$this->plugin->include_stylesheet($this->plugin->local_skin_path().'/kolab_addressbook.css');
// Add actions on address books
$options = array('book-create', 'book-edit', 'book-delete');
$idx = 0;
foreach ($options as $command) {
$content = html::tag('li', $idx ? null : array('class' => 'separator_above'),
$this->plugin->api->output->button(array(
'label' => 'kolab_addressbook.'.str_replace('-', '', $command),
'domain' => $this->ID,
'classact' => 'active',
'command' => $command
)));
$this->plugin->api->add_content($content, 'groupoptions');
$idx++;
}
$this->rc->output->add_label('kolab_addressbook.bookdeleteconfirm',
'kolab_addressbook.bookdeleting');
}
// book create/edit form
else {
$this->rc->output->add_label('kolab_addressbook.nobooknamewarning',
'kolab_addressbook.booksaving');
}
}
/**
* Handler for address book create/edit action
*/
public function book_edit()
{
$this->rc->output->add_handler('bookdetails', array($this, 'book_form'));
$this->rc->output->send('kolab_addressbook.bookedit');
}
/**
* Handler for 'bookdetails' object returning form content for book create/edit
*
* @param array $attr Object attributes
*
* @return string HTML output
*/
public function book_form($attrib)
{
$action = trim(get_input_value('_act', RCUBE_INPUT_GPC));
$folder = trim(get_input_value('_source', RCUBE_INPUT_GPC, true)); // UTF8
$name = trim(get_input_value('_name', RCUBE_INPUT_GPC, true)); // UTF8
$old = trim(get_input_value('_oldname', RCUBE_INPUT_GPC, true)); // UTF7-IMAP
$path_imap = trim(get_input_value('_parent', RCUBE_INPUT_GPC, true)); // UTF7-IMAP
$hidden_fields[] = array('name' => '_source', 'value' => $folder);
$folder = rcube_charset_convert($folder, RCMAIL_CHARSET, 'UTF7-IMAP');
$delim = $_SESSION['imap_delimiter'];
$form = array();
if ($this->rc->action == 'plugin.book-save') {
// save error
$path_imap = $folder;
$hidden_fields[] = array('name' => '_oldname', 'value' => $old);
if (strlen($old)) {
$this->rc->imap_connect();
$options = $this->rc->imap->mailbox_info($old);
}
}
else if ($action == 'edit') {
$path_imap = explode($delim, $folder);
$name = rcube_charset_convert(array_pop($path_imap), 'UTF7-IMAP');
$path_imap = implode($path_imap, $delim);
$this->rc->imap_connect();
$options = $this->rc->imap->mailbox_info($folder);
$hidden_fields[] = array('name' => '_oldname', 'value' => $folder);
}
else {
$path_imap = $folder;
$name = '';
}
// General tab
$form['props'] = array(
'name' => $this->rc->gettext('properties'),
);
$foldername = new html_inputfield(array('name' => '_name', 'id' => '_name', 'size' => 30));
$foldername = $foldername->show($name);
$form['props']['fieldsets']['location'] = array(
'name' => $this->rc->gettext('location'),
'content' => array(
'name' => array(
'label' => $this->plugin->gettext('bookname'),
'value' => $foldername,
),
),
);
if (strlen($path_imap)) {
$path = rcube_charset_convert($path_imap, 'UTF7-IMAP');
// @TODO: $options
if (!empty($options) && ($options['norename'] || $options['namespace'] != 'personal')) {
// prevent user from moving folder
$hidden_fields[] = array('name' => '_parent', 'value' => $path_imap);
}
else {
$radio1 = new html_radiobutton(array('name' => '_parent', 'value' => ''));
$radio2 = new html_radiobutton(array('name' => '_parent', 'value' => $path_imap));
$html_path = str_replace($delim, ' &raquo; ', $path);
$folderpath = $radio1->show($path_imap) . Q(rcube_label('none')) . '&nbsp;'
.$radio2->show($path_imap) . Q($html_path);
$form['props']['fieldsets']['location']['content']['path'] = array(
'label' => $this->plugin->gettext('parentbook'),
'value' => $folderpath,
);
}
}
// Allow plugins to modify address book form content (e.g. with ACL form)
$plugin = $this->rc->plugins->exec_hook('addressbook_form',
array('form' => $form, 'options' => $options, 'name' => $folder));
$form = $plugin['form'];
// Set form tags and hidden fields
list($form_start, $form_end) = $this->get_form_tags($attrib, 'plugin.book-save', null, $hidden_fields);
unset($attrib['form']);
// return the complete edit form as table
$out = "$form_start\n";
// Create form output
foreach ($form as $tab) {
if (!empty($tab['fieldsets']) && is_array($tab['fieldsets'])) {
$content = '';
foreach ($tab['fieldsets'] as $fieldset) {
$subcontent = $this->get_form_part($fieldset);
if ($subcontent) {
$content .= html::tag('fieldset', null, html::tag('legend', null, Q($fieldset['name'])) . $subcontent) ."\n";
}
}
}
else {
$content = $this->get_form_part($tab);
}
if ($content) {
$out .= html::tag('fieldset', null, html::tag('legend', null, Q($tab['name'])) . $content) ."\n";
}
}
$out .= "\n$form_end";
return $out;
}
private function get_form_part($form)
{
$content = '';
if (is_array($form['content']) && !empty($form['content'])) {
$table = new html_table(array('cols' => 2));
foreach ($form['content'] as $col => $colprop) {
$colprop['id'] = '_'.$col;
$label = !empty($colprop['label']) ? $colprop['label'] : rcube_label($col);
$table->add('title', sprintf('<label for="%s">%s</label>', $colprop['id'], Q($label)));
$table->add(null, $colprop['value']);
}
$content = $table->show();
}
else {
$content = $form['content'];
}
return $content;
}
private function get_form_tags($attrib, $action, $id = null, $hidden = null)
{
$form_start = $form_end = '';
$request_key = $action . (isset($id) ? '.'.$id : '');
$form_start = $this->rc->output->request_form(array(
'name' => 'form',
'method' => 'post',
'task' => $this->rc->task,
'action' => $action,
'request' => $request_key,
'noclose' => true,
) + $attrib);
if (is_array($hidden)) {
foreach ($hidden as $field) {
$hiddenfield = new html_hiddenfield($field);
$form_start .= $hiddenfield->show();
}
}
$form_end = !strlen($attrib['form']) ? '</form>' : '';
$EDIT_FORM = !empty($attrib['form']) ? $attrib['form'] : 'form';
$this->rc->output->add_gui_object('editform', $EDIT_FORM);
return array($form_start, $form_end);
}
}

View file

@ -13,8 +13,9 @@
class rcube_kolab_contacts extends rcube_addressbook
{
public $primary_key = 'ID';
public $readonly = false;
public $groups = true;
public $readonly = true;
public $editable = false;
public $groups = false;
public $coltypes = array(
'name' => array('limit' => 1),
'firstname' => array('limit' => 1),
@ -55,9 +56,6 @@ class rcube_kolab_contacts extends rcube_addressbook
);
private $gid;
private $imap;
private $kolab;
private $folder;
private $contactstorage;
private $liststorage;
private $contacts;
@ -66,6 +64,7 @@ class rcube_kolab_contacts extends rcube_addressbook
private $id2uid;
private $filter;
private $result;
private $namespace;
private $imap_folder = 'INBOX/Contacts';
private $gender_map = array(0 => 'male', 1 => 'female');
private $phonetypemap = array('home' => 'home1', 'work' => 'business1', 'work2' => 'business2', 'workfax' => 'businessfax');
@ -120,6 +119,25 @@ class rcube_kolab_contacts extends rcube_addressbook
$this->liststorage = rcube_kolab::get_storage($this->imap_folder, 'distributionlist');
$this->ready = !PEAR::isError($this->contactstorage) && !PEAR::isError($this->liststorage);
// Set readonly and editable flags according to folder permissions
if ($this->ready) {
if ($this->get_owner() == $_SESSION['username']) {
$this->editable = true;
$this->readonly = false;
}
else {
$acl = $this->contactstorage->_folder->getACL();
$acl = $acl[$_SESSION['username']];
if (strpos($acl, 'i') !== false)
$this->readonly = false;
if (strpos($acl, 'a') !== false || strpos($acl, 'x') !== false)
$this->editable = true;
}
if (!$this->readonly)
$this->groups = true;
}
}
@ -130,9 +148,113 @@ class rcube_kolab_contacts extends rcube_addressbook
*/
public function get_name()
{
$folder = rcube_charset_convert($this->imap_folder, 'UTF7-IMAP');
// @TODO: use namespace prefixes
return strtr(preg_replace('!^(INBOX|user)/!i', '', $folder), '/', ':');
$folder = $this->imap_folder;
$namespace = $_SESSION['imap_namespace']; // from rcube_imap class
$found = false;
if (!empty($namespace['shared'])) {
foreach ($namespace['shared'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
$prefix = '';
$folder = substr($folder, strlen($ns[0]));
$delim = $ns[1];
$found = true;
$this->namespace = 'shared';
break;
}
}
}
if (!$found && !empty($namespace['other'])) {
foreach ($namespace['other'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
// remove namespace prefix
$folder = substr($folder, strlen($ns[0]));
$delim = $ns[1];
// get username
$pos = strpos($folder, $delim);
$prefix = '('.substr($folder, 0, $pos).') ';
$found = true;
$this->namespace = 'other';
break;
}
}
}
if (!$found && !empty($namespace['personal'])) {
foreach ($namespace['personal'] as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
// remove namespace prefix
$folder = substr($folder, strlen($ns[0]));
$prefix = '';
$delim = $ns[1];
$found = true;
$this->namespace = 'personal';
break;
}
}
}
if (empty($delim))
$delim = $_SESSION['imap_delimiter']; // from rcube_imap class
$folder = rcube_charset_convert($folder, 'UTF7-IMAP');
$folder = str_replace($delim, ' &raquo; ', $folder);
if ($prefix)
$folder = $prefix . ' ' . $folder;
return $folder;
}
/**
* Getter for the IMAP folder name
*
* @return string Name of the IMAP folder
*/
public function get_realname()
{
return $this->imap_folder;
}
/**
* Getter for the IMAP folder owner
*
* @return string Name of the folder owner
*/
public function get_owner()
{
return $this->contactstorage->_folder->getOwner();
}
/**
* Getter for the name of the namespace to which the IMAP folder belongs
*
* @return string Name of the namespace (personal, other, shared)
*/
public function get_namespace()
{
if ($this->namespace) {
return $this->namespace;
}
$folder = $this->imap_folder;
$namespace = $_SESSION['imap_namespace']; // from rcube_imap class
if (!empty($namespace)) {
foreach ($namespace as $nsname => $nsvalue) {
if (in_array($nsname, array('personal', 'other', 'shared')) && !empty($nsvalue)) {
foreach ($nsvalue as $ns) {
if (strlen($ns[0]) && strpos($folder, $ns[0]) === 0) {
return $this->namespace = $nsname;
}
}
}
}
}
return $this->namespace = 'personal';
}

View file

@ -0,0 +1,35 @@
#directorylist li.addressbook.readonly
{
/* don't use 'background' to not reset background color */
background-image: url(kolab_folders.png);
background-position: 5px 0px;
background-repeat: no-repeat;
}
#directorylist li.addressbook.shared
{
background-image: url(kolab_folders.png);
background-position: 5px -54px;
background-repeat: no-repeat;
}
#directorylist li.addressbook.shared.readonly
{
background-image: url(kolab_folders.png);
background-position: 5px -72px;
background-repeat: no-repeat;
}
#directorylist li.addressbook.other
{
background-image: url(kolab_folders.png);
background-position: 5px -18px;
background-repeat: no-repeat;
}
#directorylist li.addressbook.other.readonly
{
background-image: url(kolab_folders.png);
background-position: 5px -36px;
background-repeat: no-repeat;
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View file

@ -0,0 +1,27 @@
<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
<title><roundcube:object name="pagetitle" /></title>
<roundcube:include file="/includes/links.html" />
<link rel="stylesheet" type="text/css" href="/settings.css" />
<script type="text/javascript" src="/functions.js"></script>
</head>
<body class="iframe">
<div id="folder-title" class="boxtitle"><roundcube:label name="kolab_addressbook.bookproperties" /></div>
<div id="folder-details" class="boxcontent">
<roundcube:object name="bookdetails" />
<p>
<!--
<roundcube:if condition="!strlen(request:_source)" />
<input type="button" value="<roundcube:label name="cancel" />" class="button" onclick="history.back()" />&nbsp;
<roundcube:endif />
-->
<roundcube:button command="book-save" type="input" class="button mainaction" label="save" />
</p>
</div>
<script type="text/javascript">rcube_init_tabs('folder-details > form')</script>
</body>
</html>