diff --git a/plugins/kolab_addressbook/kolab_addressbook.js b/plugins/kolab_addressbook/kolab_addressbook.js
new file mode 100644
index 00000000..dbb35004
--- /dev/null
+++ b/plugins/kolab_addressbook/kolab_addressbook.js
@@ -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');
+ $('').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 0) {
+ for (i=level; i>0; i--)
+ name = name.replace(/^ /, '');
+ }
+ else if (level < 0) {
+ for (i=level; i<0; i++)
+ name = ' ' + 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 : '';
+};
diff --git a/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php
new file mode 100644
index 00000000..6bac21b1
--- /dev/null
+++ b/plugins/kolab_addressbook/lib/kolab_addressbook_ui.php
@@ -0,0 +1,273 @@
+
+ *
+ * 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, ' » ', $path);
+
+ $folderpath = $radio1->show($path_imap) . Q(rcube_label('none')) . ' '
+ .$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('', $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']) ? '' : '';
+
+ $EDIT_FORM = !empty($attrib['form']) ? $attrib['form'] : 'form';
+ $this->rc->output->add_gui_object('editform', $EDIT_FORM);
+
+ return array($form_start, $form_end);
+ }
+
+}
diff --git a/plugins/kolab_addressbook/rcube_kolab_contacts.php b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
similarity index 88%
rename from plugins/kolab_addressbook/rcube_kolab_contacts.php
rename to plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
index af3daa8d..3c578e6d 100644
--- a/plugins/kolab_addressbook/rcube_kolab_contacts.php
+++ b/plugins/kolab_addressbook/lib/rcube_kolab_contacts.php
@@ -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, ' » ', $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';
}
diff --git a/plugins/kolab_addressbook/skins/default/kolab_addressbook.css b/plugins/kolab_addressbook/skins/default/kolab_addressbook.css
new file mode 100644
index 00000000..da3abed2
--- /dev/null
+++ b/plugins/kolab_addressbook/skins/default/kolab_addressbook.css
@@ -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;
+}
diff --git a/plugins/kolab_addressbook/skins/default/kolab_folders.png b/plugins/kolab_addressbook/skins/default/kolab_folders.png
new file mode 100644
index 00000000..cd6228a7
Binary files /dev/null and b/plugins/kolab_addressbook/skins/default/kolab_folders.png differ
diff --git a/plugins/kolab_addressbook/skins/default/templates/bookedit.html b/plugins/kolab_addressbook/skins/default/templates/bookedit.html
new file mode 100644
index 00000000..717ed8c4
--- /dev/null
+++ b/plugins/kolab_addressbook/skins/default/templates/bookedit.html
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+