summaryrefslogtreecommitdiff
path: root/plugins/acl
diff options
context:
space:
mode:
authorthomascube <thomas@roundcube.net>2011-08-16 19:44:01 +0000
committerthomascube <thomas@roundcube.net>2011-08-16 19:44:01 +0000
commit5375e84b75eb23e730f9fd44d042a413970bdc7f (patch)
tree6cb6d62fdd5aa764a64cb7c522d813e8a408e406 /plugins/acl
parentaf80aae480b28a82a1c248bfdd6abf96b02ae6d8 (diff)
Copying plugins into 0.6 release branch
Diffstat (limited to 'plugins/acl')
-rw-r--r--plugins/acl/acl.js338
-rw-r--r--plugins/acl/acl.php695
-rw-r--r--plugins/acl/config.inc.php.dist19
-rw-r--r--plugins/acl/localization/en_US.inc83
-rw-r--r--plugins/acl/localization/pl_PL.inc83
-rw-r--r--plugins/acl/skins/default/acl.css94
-rw-r--r--plugins/acl/skins/default/images/enabled.pngbin0 -> 674 bytes
-rw-r--r--plugins/acl/skins/default/images/partial.pngbin0 -> 389 bytes
-rw-r--r--plugins/acl/skins/default/templates/table.html54
9 files changed, 1366 insertions, 0 deletions
diff --git a/plugins/acl/acl.js b/plugins/acl/acl.js
new file mode 100644
index 000000000..3cca7bf1e
--- /dev/null
+++ b/plugins/acl/acl.js
@@ -0,0 +1,338 @@
+/**
+ * ACL plugin script
+ *
+ * @version 0.5
+ * @author Aleksander Machniak <alec@alec.pl>
+ */
+
+if (window.rcmail) {
+ rcmail.addEventListener('init', function() {
+ if (rcmail.gui_objects.acltable) {
+ rcmail.acl_list_init();
+ // enable autocomplete on user input
+ if (rcmail.env.acl_users_source) {
+ rcmail.init_address_input_events($('#acluser'), {action:'plugin.acl-autocomplete'});
+ // fix inserted value
+ rcmail.addEventListener('autocomplete_insert', function(e) {
+ if (e.field.id != 'acluser')
+ return;
+
+ var value = e.insert;
+ // get UID from the entry value
+ if (value.match(/\s*\(([^)]+)\)[, ]*$/))
+ value = RegExp.$1;
+ e.field.value = value;
+ });
+ }
+ }
+
+ rcmail.enable_command('acl-create', 'acl-save', 'acl-cancel', 'acl-mode-switch', true);
+ rcmail.enable_command('acl-delete', 'acl-edit', false);
+ });
+}
+
+// Display new-entry form
+rcube_webmail.prototype.acl_create = function()
+{
+ this.acl_init_form();
+}
+
+// Display ACL edit form
+rcube_webmail.prototype.acl_edit = function()
+{
+ // @TODO: multi-row edition
+ var id = this.acl_list.get_single_selection();
+ if (id)
+ this.acl_init_form(id);
+}
+
+// ACL entry delete
+rcube_webmail.prototype.acl_delete = function()
+{
+ var users = this.acl_get_usernames();
+
+ if (users && users.length && confirm(this.get_label('acl.deleteconfirm'))) {
+ this.http_request('plugin.acl', '_act=delete&_user='+urlencode(users.join(','))
+ + '&_mbox='+urlencode(this.env.mailbox),
+ this.set_busy(true, 'acl.deleting'));
+ }
+}
+
+// Save ACL data
+rcube_webmail.prototype.acl_save = function()
+{
+ var user = $('#acluser').val(), rights = '', type;
+
+ $(':checkbox', this.env.acl_advanced ? $('#advancedrights') : sim_ul = $('#simplerights')).map(function() {
+ if (this.checked)
+ rights += this.value;
+ });
+
+ if (type = $('input:checked[name=usertype]').val()) {
+ if (type != 'user')
+ user = type;
+ }
+
+ if (!user) {
+ alert(this.get_label('acl.nouser'));
+ return;
+ }
+ if (!rights) {
+ alert(this.get_label('acl.norights'));
+ return;
+ }
+
+ this.http_request('plugin.acl', '_act=save'
+ + '&_user='+urlencode(user)
+ + '&_acl=' +rights
+ + '&_mbox='+urlencode(this.env.mailbox)
+ + (this.acl_id ? '&_old='+this.acl_id : ''),
+ this.set_busy(true, 'acl.saving'));
+}
+
+// Cancel/Hide form
+rcube_webmail.prototype.acl_cancel = function()
+{
+ this.ksearch_blur();
+ this.acl_form.hide();
+}
+
+// Update data after save (and hide form)
+rcube_webmail.prototype.acl_update = function(o)
+{
+ // delete old row
+ if (o.old)
+ this.acl_remove_row(o.old);
+ // make sure the same ID doesn't exist
+ else if (this.env.acl[o.id])
+ this.acl_remove_row(o.id);
+
+ // add new row
+ this.acl_add_row(o, true);
+ // hide autocomplete popup
+ this.ksearch_blur();
+ // hide form
+ this.acl_form.hide();
+}
+
+// Switch table display mode
+rcube_webmail.prototype.acl_mode_switch = function(elem)
+{
+ this.env.acl_advanced = !this.env.acl_advanced;
+ this.enable_command('acl-delete', 'acl-edit', false);
+ this.http_request('plugin.acl', '_act=list'
+ + '&_mode='+(this.env.acl_advanced ? 'advanced' : 'simple')
+ + '&_mbox='+urlencode(this.env.mailbox),
+ this.set_busy(true, 'loading'));
+}
+
+// ACL table initialization
+rcube_webmail.prototype.acl_list_init = function()
+{
+ this.acl_list = new rcube_list_widget(this.gui_objects.acltable,
+ {multiselect:true, draggable:false, keyboard:true, toggleselect:true});
+ this.acl_list.addEventListener('select', function(o) { rcmail.acl_list_select(o); });
+ this.acl_list.addEventListener('dblclick', function(o) { rcmail.acl_list_dblclick(o); });
+ this.acl_list.addEventListener('keypress', function(o) { rcmail.acl_list_keypress(o); });
+ this.acl_list.init();
+}
+
+// ACL table row selection handler
+rcube_webmail.prototype.acl_list_select = function(list)
+{
+ rcmail.enable_command('acl-delete', list.selection.length > 0);
+ rcmail.enable_command('acl-edit', list.selection.length == 1);
+ list.focus();
+}
+
+// ACL table double-click handler
+rcube_webmail.prototype.acl_list_dblclick = function(list)
+{
+ this.acl_edit();
+}
+
+// ACL table keypress handler
+rcube_webmail.prototype.acl_list_keypress = function(list)
+{
+ if (list.key_pressed == list.ENTER_KEY)
+ this.command('acl-edit');
+ else if (list.key_pressed == list.DELETE_KEY || list.key_pressed == list.BACKSPACE_KEY)
+ if (!this.acl_form || !this.acl_form.is(':visible'))
+ this.command('acl-delete');
+}
+
+// Reloads ACL table
+rcube_webmail.prototype.acl_list_update = function(html)
+{
+ $(this.gui_objects.acltable).html(html);
+ this.acl_list_init();
+}
+
+// Returns names of users in selected rows
+rcube_webmail.prototype.acl_get_usernames = function()
+{
+ var users = [], n, len, cell, row,
+ list = this.acl_list,
+ selection = list.get_selection();
+
+ for (n=0, len=selection.length; n<len; n++) {
+ if (this.env.acl_specials.length && $.inArray(selection[n], this.env.acl_specials) >= 0) {
+ users.push(selection[n]);
+ }
+ else {
+ row = list.rows[selection[n]].obj;
+ cell = $('td.user', row);
+ if (cell.length == 1)
+ users.push(cell.text());
+ }
+ }
+
+ return users;
+}
+
+// Removes ACL table row
+rcube_webmail.prototype.acl_remove_row = function(id)
+{
+ this.acl_list.remove_row(id);
+ // we don't need it anymore (remove id conflict)
+ $('#rcmrow'+id).remove();
+ this.env.acl[id] = null;
+}
+
+// Adds ACL table row
+rcube_webmail.prototype.acl_add_row = function(o, sel)
+{
+ var n, len, ids = [], spec = [], id = o.id, list = this.acl_list,
+ items = this.env.acl_advanced ? [] : this.env.acl_items,
+ table = this.gui_objects.acltable,
+ row = $('thead > tr', table).clone();
+
+ // Update new row
+ $('td', row).map(function() {
+ var r, cl = this.className.replace(/^acl/, '');
+
+ if (items && items[cl])
+ cl = items[cl];
+
+ if (cl == 'user')
+ $(this).text(o.username);
+ else
+ $(this).addClass(rcmail.acl_class(o.acl, cl)).text('');
+ });
+
+ row.attr('id', 'rcmrow'+id);
+ row = row.get(0);
+
+ this.env.acl[id] = o.acl;
+
+ // sorting... (create an array of user identifiers, then sort it)
+ for (n in this.env.acl) {
+ if (this.env.acl[n]) {
+ if (this.env.acl_specials.length && $.inArray(n, this.env.acl_specials) >= 0)
+ spec.push(n);
+ else
+ ids.push(n);
+ }
+ }
+ ids.sort();
+ // specials on the top
+ ids = spec.concat(ids);
+
+ // find current id
+ for (n=0, len=ids.length; n<len; n++)
+ if (ids[n] == id)
+ break;
+
+ // add row
+ if (n && n < len) {
+ $('#rcmrow'+ids[n-1]).after(row);
+ list.init_row(row);
+ list.rowcount++;
+ }
+ else
+ list.insert_row(row);
+
+ if (sel)
+ list.select_row(o.id);
+}
+
+// Initializes and shows ACL create/edit form
+rcube_webmail.prototype.acl_init_form = function(id)
+{
+ var ul, row, val = '', type = 'user', li_elements, body = $('body'),
+ adv_ul = $('#advancedrights'), sim_ul = $('#simplerights'),
+ name_input = $('#acluser');
+
+ if (!this.acl_form) {
+ var fn = function () { $('input[value=user]').prop('checked', true); };
+ name_input.click(fn).keypress(fn);
+ }
+
+ this.acl_form = $('#aclform');
+
+ // Hide unused items
+ if (this.env.acl_advanced) {
+ adv_ul.show();
+ sim_ul.hide();
+ ul = adv_ul;
+ }
+ else {
+ sim_ul.show();
+ adv_ul.hide();
+ ul = sim_ul;
+ }
+
+ // initialize form fields
+ li_elements = $(':checkbox', ul);
+ li_elements.attr('checked', false);
+
+ if (id) {
+ row = this.acl_list.rows[id].obj;
+ li_elements.map(function() {
+ var val = this.value, td = $('td.'+this.id, row);
+ if (td && td.hasClass('enabled'))
+ this.checked = true;
+ });
+
+ if (!this.env.acl_specials.length || $.inArray(id, this.env.acl_specials) < 0)
+ val = $('td.user', row).text();
+ else
+ type = id;
+ }
+
+ name_input.val(val);
+ $('input[value='+type+']').prop('checked', true);
+
+ this.acl_id = id;
+
+ // position the form horizontally
+ var bw = body.width(), mw = this.acl_form.width();
+
+ if (bw >= mw)
+ this.acl_form.css({left: parseInt((bw - mw)/2)+'px'});
+
+ // display it
+ this.acl_form.show();
+ if (type == 'user')
+ name_input.focus();
+}
+
+// Returns class name according to ACL comparision result
+rcube_webmail.prototype.acl_class = function(acl1, acl2)
+{
+ var i, len, found = 0;
+
+ acl1 = String(acl1);
+ acl2 = String(acl2);
+
+ for (i=0, len=acl2.length; i<len; i++)
+ if (acl1.indexOf(acl2[i]) > -1)
+ found++;
+
+ if (found == len)
+ return 'enabled';
+ else if (found)
+ return 'partial';
+
+ return 'disabled';
+}
diff --git a/plugins/acl/acl.php b/plugins/acl/acl.php
new file mode 100644
index 000000000..33f84dbc3
--- /dev/null
+++ b/plugins/acl/acl.php
@@ -0,0 +1,695 @@
+<?php
+
+/**
+ * Folders Access Control Lists Management (RFC4314, RFC2086)
+ *
+ * @version 0.5
+ * @author Aleksander Machniak <alec@alec.pl>
+ *
+ *
+ * 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 acl extends rcube_plugin
+{
+ public $task = 'settings|addressbook';
+
+ private $rc;
+ private $supported = null;
+ private $mbox;
+ private $ldap;
+ private $specials = array('anyone', 'anonymous');
+
+ /**
+ * Plugin initialization
+ */
+ function init()
+ {
+ $this->rc = rcmail::get_instance();
+
+ // Register hooks
+ $this->add_hook('folder_form', array($this, 'folder_form'));
+ // kolab_addressbook plugin
+ $this->add_hook('addressbook_form', array($this, 'folder_form'));
+ // Plugin actions
+ $this->register_action('plugin.acl', array($this, 'acl_actions'));
+ $this->register_action('plugin.acl-autocomplete', array($this, 'acl_autocomplete'));
+ }
+
+ /**
+ * Handler for plugin actions (AJAX)
+ */
+ function acl_actions()
+ {
+ $action = trim(get_input_value('_act', RCUBE_INPUT_GPC));
+
+ // Connect to IMAP
+ $this->rc->imap_init();
+ $this->rc->imap_connect();
+
+ // Load localization and configuration
+ $this->add_texts('localization/');
+ $this->load_config();
+
+ if ($action == 'save') {
+ $this->action_save();
+ }
+ else if ($action == 'delete') {
+ $this->action_delete();
+ }
+ else if ($action == 'list') {
+ $this->action_list();
+ }
+
+ // Only AJAX actions
+ $this->rc->output->send();
+ }
+
+ /**
+ * Handler for user login autocomplete request
+ */
+ function acl_autocomplete()
+ {
+ $this->load_config();
+
+ $search = get_input_value('_search', RCUBE_INPUT_GPC, true);
+ $users = array();
+
+ if ($this->init_ldap()) {
+ $this->ldap->set_pagesize(15);
+ $result = $this->ldap->search('*', $search);
+
+ foreach ($result->records as $record) {
+ $user = $record['uid'];
+
+ if (is_array($user)) {
+ $user = array_filter($user);
+ $user = $user[0];
+ }
+
+ if ($user) {
+ if ($record['name'])
+ $user = $record['name'] . ' (' . $user . ')';
+
+ $users[] = $user;
+ }
+ }
+ }
+
+ sort($users, SORT_LOCALE_STRING);
+
+ $this->rc->output->command('ksearch_query_results', $users, $search);
+ $this->rc->output->send();
+ }
+
+ /**
+ * Handler for 'folder_form' hook
+ *
+ * @param array $args Hook arguments array (form data)
+ *
+ * @return array Hook arguments array
+ */
+ function folder_form($args)
+ {
+ // Edited folder name (empty in create-folder mode)
+ $mbox_imap = $args['options']['name'];
+ if (!strlen($mbox_imap)) {
+ return $args;
+ }
+/*
+ // Do nothing on protected folders (?)
+ if ($args['options']['protected']) {
+ return $args;
+ }
+*/
+ // Namespace root
+ if ($args['options']['is_root']) {
+ return $args;
+ }
+
+ // Get MYRIGHTS
+ if (!($myrights = $args['options']['rights'])) {
+ return $args;
+ }
+
+ // Do nothing if no ACL support
+ if (!$this->rc->imap->get_capability('ACL')) {
+ return $args;
+ }
+
+ // Load localization and include scripts
+ $this->load_config();
+ $this->add_texts('localization/', array('deleteconfirm', 'norights',
+ 'nouser', 'deleting', 'saving'));
+ $this->include_script('acl.js');
+ $this->rc->output->include_script('list.js');
+ $this->include_stylesheet($this->local_skin_path().'/acl.css');
+
+ // add Info fieldset if it doesn't exist
+ if (!isset($args['form']['props']['fieldsets']['info']))
+ $args['form']['props']['fieldsets']['info'] = array(
+ 'name' => rcube_label('info'),
+ 'content' => array());
+
+ // Display folder rights to 'Info' fieldset
+ $args['form']['props']['fieldsets']['info']['content']['myrights'] = array(
+ 'label' => Q($this->gettext('myrights')),
+ 'value' => $this->acl2text($myrights)
+ );
+
+ // Return if not folder admin
+ if (!in_array('a', $myrights)) {
+ return $args;
+ }
+
+ // The 'Sharing' tab
+ $this->mbox = $mbox_imap;
+ $this->rc->output->set_env('acl_users_source', (bool) $this->rc->config->get('acl_users_source'));
+ $this->rc->output->set_env('mailbox', $mbox_imap);
+ $this->rc->output->add_handlers(array(
+ 'acltable' => array($this, 'templ_table'),
+ 'acluser' => array($this, 'templ_user'),
+ 'aclrights' => array($this, 'templ_rights'),
+ ));
+
+ $args['form']['sharing'] = array(
+ 'name' => Q($this->gettext('sharing')),
+ 'content' => $this->rc->output->parse('acl.table', false, false),
+ );
+
+ return $args;
+ }
+
+ /**
+ * Creates ACL rights table
+ *
+ * @param array $attrib Template object attributes
+ *
+ * @return string HTML Content
+ */
+ function templ_table($attrib)
+ {
+ if (empty($attrib['id']))
+ $attrib['id'] = 'acl-table';
+
+ $out = $this->list_rights($attrib);
+
+ $this->rc->output->add_gui_object('acltable', $attrib['id']);
+
+ return $out;
+ }
+
+ /**
+ * Creates ACL rights form (rights list part)
+ *
+ * @param array $attrib Template object attributes
+ *
+ * @return string HTML Content
+ */
+ function templ_rights($attrib)
+ {
+ // Get supported rights
+ $supported = $this->rights_supported();
+
+ // depending on server capability either use 'te' or 'd' for deleting msgs
+ $deleteright = implode(array_intersect(str_split('ted'), $supported));
+
+ $out = '';
+ $ul = '';
+ $input = new html_checkbox();
+
+ // Advanced rights
+ $attrib['id'] = 'advancedrights';
+ foreach ($supported as $val) {
+ $id = "acl$val";
+ $ul .= html::tag('li', null,
+ $input->show('', array(
+ 'name' => "acl[$val]", 'value' => $val, 'id' => $id))
+ . html::label(array('for' => $id, 'title' => $this->gettext('longacl'.$val)),
+ $this->gettext('acl'.$val)));
+ }
+
+ $out = html::tag('ul', $attrib, $ul, html::$common_attrib);
+
+ // Simple rights
+ $ul = '';
+ $attrib['id'] = 'simplerights';
+ $items = array(
+ 'read' => 'lrs',
+ 'write' => 'wi',
+ 'delete' => $deleteright,
+ 'other' => preg_replace('/[lrswi'.$deleteright.']/', '', implode($supported)),
+ );
+
+ foreach ($items as $key => $val) {
+ $id = "acl$key";
+ $ul .= html::tag('li', null,
+ $input->show('', array(
+ 'name' => "acl[$val]", 'value' => $val, 'id' => $id))
+ . html::label(array('for' => $id, 'title' => $this->gettext('longacl'.$key)),
+ $this->gettext('acl'.$key)));
+ }
+
+ $out .= "\n" . html::tag('ul', $attrib, $ul, html::$common_attrib);
+
+ $this->rc->output->set_env('acl_items', $items);
+
+ return $out;
+ }
+
+ /**
+ * Creates ACL rights form (user part)
+ *
+ * @param array $attrib Template object attributes
+ *
+ * @return string HTML Content
+ */
+ function templ_user($attrib)
+ {
+ // Create username input
+ $attrib['name'] = 'acluser';
+
+ $textfield = new html_inputfield($attrib);
+
+ $fields['user'] = html::label(array('for' => 'iduser'), $this->gettext('username'))
+ . ' ' . $textfield->show();
+
+ // Add special entries
+ if (!empty($this->specials)) {
+ foreach ($this->specials as $key) {
+ $fields[$key] = html::label(array('for' => 'id'.$key), $this->gettext($key));
+ }
+ }
+
+ $this->rc->output->set_env('acl_specials', $this->specials);
+
+ // Create list with radio buttons
+ if (count($fields) > 1) {
+ $ul = '';
+ $radio = new html_radiobutton(array('name' => 'usertype'));
+ foreach ($fields as $key => $val) {
+ $ul .= html::tag('li', null, $radio->show($key == 'user' ? 'user' : '',
+ array('value' => $key, 'id' => 'id'.$key))
+ . $val);
+ }
+
+ $out = html::tag('ul', array('id' => 'usertype'), $ul, html::$common_attrib);
+ }
+ // Display text input alone
+ else {
+ $out = $fields['user'];
+ }
+
+ return $out;
+ }
+
+ /**
+ * Creates ACL rights table
+ *
+ * @param array $attrib Template object attributes
+ *
+ * @return string HTML Content
+ */
+ private function list_rights($attrib=array())
+ {
+ // Get ACL for the folder
+ $acl = $this->rc->imap->get_acl($this->mbox);
+
+ if (!is_array($acl)) {
+ $acl = array();
+ }
+
+ // Keep special entries (anyone/anonymous) on top of the list
+ if (!empty($this->specials) && !empty($acl)) {
+ foreach ($this->specials as $key) {
+ if (isset($acl[$key])) {
+ $acl_special[$key] = $acl[$key];
+ unset($acl[$key]);
+ }
+ }
+ }
+
+ // Sort the list by username
+ uksort($acl, 'strnatcasecmp');
+
+ if (!empty($acl_special)) {
+ $acl = array_merge($acl_special, $acl);
+ }
+
+ // Get supported rights and build column names
+ $supported = $this->rights_supported();
+
+ // depending on server capability either use 'te' or 'd' for deleting msgs
+ $deleteright = implode(array_intersect(str_split('ted'), $supported));
+
+ // Use advanced or simple (grouped) rights
+ $advanced = $this->rc->config->get('acl_advanced_mode');
+
+ if ($advanced) {
+ $items = array();
+ foreach ($supported as $sup) {
+ $items[$sup] = $sup;
+ }
+ }
+ else {
+ $items = array(
+ 'read' => 'lrs',
+ 'write' => 'wi',
+ 'delete' => $deleteright,
+ 'other' => preg_replace('/[lrswi'.$deleteright.']/', '', implode($supported)),
+ );
+ }
+
+ // Create the table
+ $attrib['noheader'] = true;
+ $table = new html_table($attrib);
+
+ // Create table header
+ $table->add_header('user', $this->gettext('identifier'));
+ foreach (array_keys($items) as $key) {
+ $table->add_header('acl'.$key, $this->gettext('shortacl'.$key));
+ }
+
+ $i = 1;
+ $js_table = array();
+ foreach ($acl as $user => $rights) {
+ if ($this->rc->imap->conn->user == $user) {
+ continue;
+ }
+
+ // filter out virtual rights (c or d) the server may return
+ $userrights = array_intersect($rights, $supported);
+ $userid = html_identifier($user);
+
+ if (!empty($this->specials) && in_array($user, $this->specials)) {
+ $user = $this->gettext($user);
+ }
+
+ $table->add_row(array('id' => 'rcmrow'.$userid));
+ $table->add('user', Q($user));
+
+ foreach ($items as $key => $right) {
+ $in = $this->acl_compare($userrights, $right);
+ switch ($in) {
+ case 2: $class = 'enabled'; break;
+ case 1: $class = 'partial'; break;
+ default: $class = 'disabled'; break;
+ }
+ $table->add('acl' . $key . ' ' . $class, '');
+ }
+
+ $js_table[$userid] = implode($userrights);
+ }
+
+ $this->rc->output->set_env('acl', $js_table);
+ $this->rc->output->set_env('acl_advanced', $advanced);
+
+ $out = $table->show();
+
+ return $out;
+ }
+
+ /**
+ * Handler for ACL update/create action
+ */
+ private function action_save()
+ {
+ $mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true, 'UTF7-IMAP'));
+ $user = trim(get_input_value('_user', RCUBE_INPUT_GPC));
+ $acl = trim(get_input_value('_acl', RCUBE_INPUT_GPC));
+ $oldid = trim(get_input_value('_old', RCUBE_INPUT_GPC));
+
+ $acl = array_intersect(str_split($acl), $this->rights_supported());
+
+ if (!empty($this->specials) && in_array($user, $this->specials)) {
+ $username = $this->gettext($user);
+ }
+ else {
+ if (!strpos($user, '@') && ($realm = $this->get_realm())) {
+ $user .= '@' . rcube_idn_to_ascii(preg_replace('/^@/', '', $realm));
+ }
+ $username = $user;
+ }
+
+ if ($acl && $user && $user != $_SESSION['username'] && strlen($mbox)) {
+ $result = $this->rc->imap->set_acl($mbox, $user, $acl);
+ }
+
+ if ($result) {
+ $ret = array('id' => html_identifier($user),
+ 'username' => $username, 'acl' => implode($acl), 'old' => $oldid);
+ $this->rc->output->command('acl_update', $ret);
+ $this->rc->output->show_message($oldid ? 'acl.updatesuccess' : 'acl.createsuccess', 'confirmation');
+ }
+ else {
+ $this->rc->output->show_message($oldid ? 'acl.updateerror' : 'acl.createerror', 'error');
+ }
+ }
+
+ /**
+ * Handler for ACL delete action
+ */
+ private function action_delete()
+ {
+ $mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true, 'UTF7-IMAP'));
+ $user = trim(get_input_value('_user', RCUBE_INPUT_GPC));
+
+ $user = explode(',', $user);
+
+ foreach ($user as $u) {
+ if ($this->rc->imap->delete_acl($mbox, $u)) {
+ $this->rc->output->command('acl_remove_row', html_identifier($u));
+ }
+ else {
+ $error = true;
+ }
+ }
+
+ if (!$error) {
+ $this->rc->output->show_message('acl.deletesuccess', 'confirmation');
+ }
+ else {
+ $this->rc->output->show_message('acl.deleteerror', 'error');
+ }
+ }
+
+ /**
+ * Handler for ACL list update action (with display mode change)
+ */
+ private function action_list()
+ {
+ if (in_array('acl_advanced_mode', (array)$this->rc->config->get('dont_override'))) {
+ return;
+ }
+
+ $this->mbox = trim(get_input_value('_mbox', RCUBE_INPUT_GPC, true, 'UTF7-IMAP'));
+ $advanced = trim(get_input_value('_mode', RCUBE_INPUT_GPC));
+ $advanced = $advanced == 'advanced' ? true : false;
+
+ // Save state in user preferences
+ $this->rc->user->save_prefs(array('acl_advanced_mode' => $advanced));
+
+ $out = $this->list_rights();
+
+ $out = preg_replace(array('/^<table[^>]+>/', '/<\/table>$/'), '', $out);
+
+ $this->rc->output->command('acl_list_update', $out);
+ }
+
+ /**
+ * Creates <UL> list with descriptive access rights
+ *
+ * @param array $rights MYRIGHTS result
+ *
+ * @return string HTML content
+ */
+ function acl2text($rights)
+ {
+ if (empty($rights)) {
+ return '';
+ }
+
+ $supported = $this->rights_supported();
+ $list = array();
+ $attrib = array(
+ 'name' => 'rcmyrights',
+ 'style' => 'padding: 0 15px;',
+ );
+
+ foreach ($supported as $right) {
+ if (in_array($right, $rights)) {
+ $list[] = html::tag('li', null, Q($this->gettext('acl' . $right)));
+ }
+ }
+
+ if (count($list) == count($supported))
+ return Q($this->gettext('aclfull'));
+
+ return html::tag('ul', $attrib, implode("\n", $list));
+ }
+
+ /**
+ * Compares two ACLs (according to supported rights)
+ *
+ * @param array $acl1 ACL rights array (or string)
+ * @param array $acl2 ACL rights array (or string)
+ *
+ * @param int Comparision result, 2 - full match, 1 - partial match, 0 - no match
+ */
+ function acl_compare($acl1, $acl2)
+ {
+ if (!is_array($acl1)) $acl1 = str_split($acl1);
+ if (!is_array($acl2)) $acl2 = str_split($acl2);
+
+ $rights = $this->rights_supported();
+
+ $acl1 = array_intersect($acl1, $rights);
+ $acl2 = array_intersect($acl2, $rights);
+ $res = array_intersect($acl1, $acl2);
+
+ $cnt1 = count($res);
+ $cnt2 = count($acl2);
+
+ if ($cnt1 == $cnt2)
+ return 2;
+ else if ($cnt1)
+ return 1;
+ else
+ return 0;
+ }
+
+ /**
+ * Get list of supported access rights (according to RIGHTS capability)
+ *
+ * @return array List of supported access rights abbreviations
+ */
+ function rights_supported()
+ {
+ if ($this->supported !== null) {
+ return $this->supported;
+ }
+
+ $capa = $this->rc->imap->get_capability('RIGHTS');
+
+ if (is_array($capa)) {
+ $rights = strtolower($capa[0]);
+ }
+ else {
+ $rights = 'cd';
+ }
+
+ return $this->supported = str_split('lrswi' . $rights . 'pa');
+ }
+
+ /**
+ * Username realm detection.
+ *
+ * @return string Username realm (domain)
+ */
+ private function get_realm()
+ {
+ // When user enters a username without domain part, realm
+ // alows to add it to the username (and display correct username in the table)
+
+ if (isset($_SESSION['acl_username_realm'])) {
+ return $_SESSION['acl_username_realm'];
+ }
+
+ // find realm in username of logged user (?)
+ list($name, $domain) = explode('@', $_SESSION['username']);
+
+ // Use (always existent) ACL entry on the INBOX for the user to determine
+ // whether or not the user ID in ACL entries need to be qualified and how
+ // they would need to be qualified.
+ if (empty($domain)) {
+ $acl = $this->rc->imap->get_acl('INBOX');
+ if (is_array($acl)) {
+ $regexp = '/^' . preg_quote($_SESSION['username'], '/') . '@(.*)$/';
+ $regexp = '/^' . preg_quote('aleksander.machniak', '/') . '@(.*)$/';
+ foreach (array_keys($acl) as $name) {
+ if (preg_match($regexp, $name, $matches)) {
+ $domain = $matches[1];
+ break;
+ }
+ }
+ }
+ }
+
+ return $_SESSION['acl_username_realm'] = $domain;
+ }
+
+ /**
+ * Initializes autocomplete LDAP backend
+ */
+ private function init_ldap()
+ {
+ if ($this->ldap)
+ return $this->ldap->ready;
+
+ // get LDAP config
+ $config = $this->rc->config->get('acl_users_source');
+
+ if (empty($config)) {
+ return false;
+ }
+
+ // not an array, use configured ldap_public source
+ if (!is_array($config)) {
+ $ldap_config = (array) $this->rc->config->get('ldap_public');
+ $config = $ldap_config[$config];
+ }
+
+ $uid_field = $this->rc->config->get('acl_users_field', 'mail');
+ $filter = $this->rc->config->get('acl_users_filter');
+
+ if (empty($uid_field) || empty($config)) {
+ return false;
+ }
+
+ // get name attribute
+ if (!empty($config['fieldmap'])) {
+ $name_field = $config['fieldmap']['name'];
+ }
+ // ... no fieldmap, use the old method
+ if (empty($name_field)) {
+ $name_field = $config['name_field'];
+ }
+
+ // add UID field to fieldmap, so it will be returned in a record with name
+ $config['fieldmap'] = array(
+ 'name' => $name_field,
+ 'uid' => $uid_field,
+ );
+
+ // search in UID and name fields
+ $config['search_fields'] = array_values($config['fieldmap']);
+ $config['required_fields'] = array($uid_field);
+
+ // set search filter
+ if ($filter)
+ $config['filter'] = $filter;
+
+ // disable vlv
+ $config['vlv'] = false;
+
+ // Initialize LDAP connection
+ $this->ldap = new rcube_ldap($config,
+ $this->rc->config->get('ldap_debug'),
+ $this->rc->config->mail_domain($_SESSION['imap_host']));
+
+ return $this->ldap->ready;
+ }
+}
diff --git a/plugins/acl/config.inc.php.dist b/plugins/acl/config.inc.php.dist
new file mode 100644
index 000000000..f957a233a
--- /dev/null
+++ b/plugins/acl/config.inc.php.dist
@@ -0,0 +1,19 @@
+<?php
+
+// Default look of access rights table
+// In advanced mode all access rights are displayed separately
+// In simple mode access rights are grouped into four groups: read, write, delete, full
+$rcmail_config['acl_advanced_mode'] = false;
+
+// LDAP addressbook that would be searched for user names autocomplete.
+// That should be an array refering to the $rcmail_config['ldap_public'] array key
+// or complete addressbook configuration array.
+$rcmail_config['acl_users_source'] = '';
+
+// The LDAP attribute which will be used as ACL user identifier
+$rcmail_config['acl_users_field'] = 'mail';
+
+// The LDAP search filter will be &'d with search queries
+$rcmail_config['acl_users_filter'] = '';
+
+?>
diff --git a/plugins/acl/localization/en_US.inc b/plugins/acl/localization/en_US.inc
new file mode 100644
index 000000000..f5b1ae64d
--- /dev/null
+++ b/plugins/acl/localization/en_US.inc
@@ -0,0 +1,83 @@
+<?php
+
+$labels['sharing'] = 'Sharing';
+$labels['myrights'] = 'Access Rights';
+$labels['username'] = 'User:';
+$labels['advanced'] = 'advanced mode';
+$labels['newuser'] = 'Add entry';
+$labels['actions'] = 'Access right actions...';
+$labels['anyone'] = 'All users (anyone)';
+$labels['anonymous'] = 'Guests (anonymous)';
+$labels['identifier'] = 'Identifier';
+
+$labels['acll'] = 'Lookup';
+$labels['aclr'] = 'Read messages';
+$labels['acls'] = 'Keep Seen state';
+$labels['aclw'] = 'Write flags';
+$labels['acli'] = 'Insert (Copy into)';
+$labels['aclp'] = 'Post';
+$labels['aclc'] = 'Create subfolders';
+$labels['aclk'] = 'Create subfolders';
+$labels['acld'] = 'Delete messages';
+$labels['aclt'] = 'Delete messages';
+$labels['acle'] = 'Expunge';
+$labels['aclx'] = 'Delete folder';
+$labels['acla'] = 'Administer';
+
+$labels['aclfull'] = 'Full control';
+$labels['aclother'] = 'Other';
+$labels['aclread'] = 'Read';
+$labels['aclwrite'] = 'Write';
+$labels['acldelete'] = 'Delete';
+
+$labels['shortacll'] = 'Lookup';
+$labels['shortaclr'] = 'Read';
+$labels['shortacls'] = 'Keep';
+$labels['shortaclw'] = 'Write';
+$labels['shortacli'] = 'Insert';
+$labels['shortaclp'] = 'Post';
+$labels['shortaclc'] = 'Create';
+$labels['shortaclk'] = 'Create';
+$labels['shortacld'] = 'Delete';
+$labels['shortaclt'] = 'Delete';
+$labels['shortacle'] = 'Expunge';
+$labels['shortaclx'] = 'Folder delete';
+$labels['shortacla'] = 'Administer';
+
+$labels['shortaclother'] = 'Other';
+$labels['shortaclread'] = 'Read';
+$labels['shortaclwrite'] = 'Write';
+$labels['shortacldelete'] = 'Delete';
+
+$labels['longacll'] = 'The folder is visible on lists and can be subscribed to';
+$labels['longaclr'] = 'The folder can be opened for reading';
+$labels['longacls'] = 'Messages Seen flag can be changed';
+$labels['longaclw'] = 'Messages flags and keywords can be changed, except Seen and Deleted';
+$labels['longacli'] = 'Messages can be written or copied to the folder';
+$labels['longaclp'] = 'Messages can be posted to this folder';
+$labels['longaclc'] = 'Folders can be created (or renamed) directly under this folder';
+$labels['longaclk'] = 'Folders can be created (or renamed) directly under this folder';
+$labels['longacld'] = 'Messages Delete flag can be changed';
+$labels['longaclt'] = 'Messages Delete flag can be changed';
+$labels['longacle'] = 'Messages can be expunged';
+$labels['longaclx'] = 'The folder can be deleted or renamed';
+$labels['longacla'] = 'The folder access rights can be changed';
+
+$labels['longaclfull'] = 'Full control including folder administration';
+$labels['longaclread'] = 'The folder can be opened for reading';
+$labels['longaclwrite'] = 'Messages can be marked, written or copied to the folder';
+$labels['longacldelete'] = 'Messages can be deleted';
+
+$messages['deleting'] = 'Deleting access rights...';
+$messages['saving'] = 'Saving access rights...';
+$messages['updatesuccess'] = 'Successfully changed access rights';
+$messages['deletesuccess'] = 'Successfully deleted access rights';
+$messages['createsuccess'] = 'Successfully added access rights';
+$messages['updateerror'] = 'Ubable to update access rights';
+$messages['deleteerror'] = 'Unable to delete access rights';
+$messages['createerror'] = 'Unable to add access rights';
+$messages['deleteconfirm'] = 'Are you sure, you want to remove access rights of selected user(s)?';
+$messages['norights'] = 'No rights has been specified!';
+$messages['nouser'] = 'No username has been specified!';
+
+?>
diff --git a/plugins/acl/localization/pl_PL.inc b/plugins/acl/localization/pl_PL.inc
new file mode 100644
index 000000000..0b3689927
--- /dev/null
+++ b/plugins/acl/localization/pl_PL.inc
@@ -0,0 +1,83 @@
+<?php
+
+$labels['sharing'] = 'Udostępnianie';
+$labels['myrights'] = 'Prawa dostępu';
+$labels['username'] = 'Użytkownik:';
+$labels['advanced'] = 'tryb zaawansowany';
+$labels['newuser'] = 'Dodaj rekord';
+$labels['actions'] = 'Akcje na prawach...';
+$labels['anyone'] = 'Wszyscy (anyone)';
+$labels['anonymous'] = 'Goście (anonymous)';
+$labels['identifier'] = 'Identyfikator';
+
+$labels['acll'] = 'Podgląd (Lookup)';
+$labels['aclr'] = 'Odczyt (Read)';
+$labels['acls'] = 'Zmiana stanu wiadomości (Keep)';
+$labels['aclw'] = 'Zmiana flag wiadomości (Write)';
+$labels['acli'] = 'Dodawanie/Kopiowanie do (Insert)';
+$labels['aclp'] = 'Wysyłanie (Post)';
+$labels['aclc'] = 'Tworzenie podfolderów (Create)';
+$labels['aclk'] = 'Tworzenie podfolderów (Create)';
+$labels['acld'] = 'Usuwanie wiadomości (Delete)';
+$labels['aclt'] = 'Usuwanie wiadomości (Delete)';
+$labels['acle'] = 'Porządkowanie folderu (Expunge)';
+$labels['aclx'] = 'Usuwanie folderu (Delete)';
+$labels['acla'] = 'Administracja (Administer)';
+
+$labels['aclfull'] = 'Wszystkie';
+$labels['aclother'] = 'Inne';
+$labels['aclread'] = 'Odczyt';
+$labels['aclwrite'] = 'Zapis';
+$labels['acldelete'] = 'Usuwanie';
+
+$labels['shortacll'] = 'Podgląd';
+$labels['shortaclr'] = 'Odczyt';
+$labels['shortacls'] = 'Zmiana';
+$labels['shortaclw'] = 'Zmiana flag';
+$labels['shortacli'] = 'Dodawanie';
+$labels['shortaclp'] = 'Wysyłanie';
+$labels['shortaclc'] = 'Tworzenie';
+$labels['shortaclk'] = 'Tworzenie';
+$labels['shortacld'] = 'Usuwanie';
+$labels['shortaclt'] = 'Usuwanie';
+$labels['shortacle'] = 'Porządkowanie';
+$labels['shortaclx'] = 'Usuwanie folderu';
+$labels['shortacla'] = 'Administracja';
+
+$labels['shortaclother'] = 'Pozostałe';
+$labels['shortaclread'] = 'Odczyt';
+$labels['shortaclwrite'] = 'Zapis';
+$labels['shortacldelete'] = 'Usuwanie';
+
+$labels['longacll'] = 'Pozwala na subskrybowanie folderu i powoduje, że jest on widoczny na liście';
+$labels['longaclr'] = 'Pozwala na otwarcie folderu w trybie do odczytu';
+$labels['longacls'] = 'Pozwala na zmienę stanu wiadomości';
+$labels['longaclw'] = 'Pozwala zmieniać wszystkie flagi wiadomości, oprócz "Przeczytano" i "Usunięto"';
+$labels['longacli'] = 'Pozwala zapisywać wiadomości i kopiować do folderu';
+$labels['longaclp'] = 'Pozwala wysyłać wiadomości do folderu';
+$labels['longaclc'] = 'Pozwala tworzyć (lub zmieniać nazwę) podfoldery';
+$labels['longaclk'] = 'Pozwala tworzyć (lub zmieniać nazwę) podfoldery';
+$labels['longacld'] = 'Pozwala zmianiać flagę "Usunięto" wiadomości';
+$labels['longaclt'] = 'Pozwala zmianiać flagę "Usunięto" wiadomości';
+$labels['longacle'] = 'Pozwala na usuwanie wiadomości oznaczonych do usunięcia';
+$labels['longaclx'] = 'Pozwala na zmianę nazwy lub usunięcie folderu';
+$labels['longacla'] = 'Pozwala na zmiane praw dostępu do folderu';
+
+$labels['longaclfull'] = 'Pełna kontrola włącznie z administrowaniem folderem';
+$labels['longaclread'] = 'Folder może być otwarty w trybie do odczytu';
+$labels['longaclwrite'] = 'Wiadomości mogą być oznaczane, zapisywane i kopiowane do folderu';
+$labels['longacldelete'] = 'Wiadomości mogą być usuwane';
+
+$messages['deleting'] = 'Usuwanie praw dostępu...';
+$messages['saving'] = 'Zapisywanie praw dostępu...';
+$messages['updatesuccess'] = 'Pomyślnie zmieniono prawa dostępu';
+$messages['deletesuccess'] = 'Pomyślnie usunięto prawa dostępu';
+$messages['createsuccess'] = 'Pomyślnie dodano prawa dostępu';
+$messages['updateerror'] = 'Nie udało się zmienić praw dostępu';
+$messages['deleteerror'] = 'Nie udało się usunąć praw dostępu';
+$messages['createerror'] = 'Nie udało się dodać praw dostępu';
+$messages['deleteconfirm'] = 'Czy na pewno chcesz usunąć prawa wybranym użytkownikom?';
+$messages['norights'] = 'Nie wybrano praw dostępu!';
+$messages['nouser'] = 'Nie podano nazwy użytkownika!';
+
+?>
diff --git a/plugins/acl/skins/default/acl.css b/plugins/acl/skins/default/acl.css
new file mode 100644
index 000000000..cd8f4b72e
--- /dev/null
+++ b/plugins/acl/skins/default/acl.css
@@ -0,0 +1,94 @@
+#aclmanager
+{
+ position: relative;
+ border: 1px solid #999;
+ min-height: 302px;
+}
+
+#aclcontainer
+{
+ overflow-x: auto;
+}
+
+#acltable
+{
+ width: 100%;
+ border-collapse: collapse;
+ background-color: #F9F9F9;
+}
+
+#acltable td
+{
+ width: 1%;
+ white-space: nowrap;
+}
+
+#acltable thead td
+{
+ padding: 0 4px 0 2px;
+}
+
+#acltable tbody td
+{
+ text-align: center;
+ padding: 2px;
+ border-bottom: 1px solid #999999;
+ cursor: default;
+}
+
+#acltable tbody td.user
+{
+ width: 96%;
+ text-align: left;
+ overflow: hidden;
+ text-overflow: ellipsis;
+ -o-text-overflow: ellipsis;
+}
+
+#acltable tbody td.partial
+{
+ background: url(images/partial.png) center no-repeat;
+}
+
+#acltable tbody td.enabled
+{
+ background: url(images/enabled.png) center no-repeat;
+}
+
+#acltable tr.selected td
+{
+ color: #FFFFFF;
+ background-color: #CC3333;
+}
+
+#acladvswitch
+{
+ position: absolute;
+ right: 4px;
+ text-align: right;
+ line-height: 22px;
+}
+
+#acladvswitch input
+{
+ vertical-align: middle;
+}
+
+#acladvswitch span
+{
+ display: block;
+}
+
+#aclform
+{
+ top: 100px;
+ width: 480px;
+ padding: 10px;
+}
+
+#aclform div
+{
+ padding: 0;
+ text-align: center;
+ clear: both;
+}
diff --git a/plugins/acl/skins/default/images/enabled.png b/plugins/acl/skins/default/images/enabled.png
new file mode 100644
index 000000000..98215f68c
--- /dev/null
+++ b/plugins/acl/skins/default/images/enabled.png
Binary files differ
diff --git a/plugins/acl/skins/default/images/partial.png b/plugins/acl/skins/default/images/partial.png
new file mode 100644
index 000000000..12023f057
--- /dev/null
+++ b/plugins/acl/skins/default/images/partial.png
Binary files differ
diff --git a/plugins/acl/skins/default/templates/table.html b/plugins/acl/skins/default/templates/table.html
new file mode 100644
index 000000000..2365ef757
--- /dev/null
+++ b/plugins/acl/skins/default/templates/table.html
@@ -0,0 +1,54 @@
+<!--[if lte IE 6]>
+ <style type="text/css">
+ #aclmanager { height: expression(Math.min(302, parseInt(document.documentElement.clientHeight))+'px'); }
+ </style>
+<![endif]-->
+
+<div id="aclmanager">
+<div id="aclcontainer" class="boxlistcontent" style="top:0">
+ <roundcube:object name="acltable" id="acltable" class="records-table" />
+</div>
+<div class="boxfooter">
+ <roundcube:button command="acl-create" id="aclcreatelink" type="link" title="acl.newuser" class="buttonPas addgroup" classAct="button addgroup" content=" " />
+ <roundcube:button name="aclmenulink" id="aclmenulink" type="link" title="acl.actions" class="button groupactions" onclick="show_aclmenu(); return false" content=" " />
+ <roundcube:if condition="!in_array('acl_advanced_mode', (array)config:dont_override)" />
+ <div id="acladvswitch" class="pagenav">
+ <span><label for="acl-switch"><roundcube:label name="acl.advanced" /></label>
+ <input type="checkbox" id="acl-switch" onclick="rcmail.command('acl-mode-switch')"<roundcube:exp expression="config:acl_advanced_mode == true ? ' checked=checked' : ''" /> />
+ </span>
+ </div>
+ <roundcube:endif />
+</div>
+</div>
+
+<div id="aclmenu" class="popupmenu">
+ <ul>
+ <li><roundcube:button command="acl-edit" label="edit" classAct="active" /></li>
+ <li><roundcube:button command="acl-delete" label="delete" classAct="active" /></li>
+ </ul>
+</div>
+
+<div id="aclform" class="popupmenu">
+ <fieldset class="thinbordered"><legend><roundcube:label name="acl.identifier" /></legend>
+ <roundcube:object name="acluser" class="toolbarmenu" id="acluser" size="35" />
+ </fieldset>
+ <fieldset class="thinbordered"><legend><roundcube:label name="acl.myrights" /></legend>
+ <roundcube:object name="aclrights" class="toolbarmenu" />
+ </fieldset>
+ <div>
+ <roundcube:button command="acl-cancel" type="input" class="button" label="cancel" />
+ <roundcube:button command="acl-save" type="input" class="button mainaction" label="save" />
+ </div>
+</div>
+
+<script type="text/javascript">
+function show_aclmenu()
+{
+ if (!rcmail_ui) {
+ rcube_init_mail_ui();
+ rcmail_ui.popups.aclmenu = {id:'aclmenu', above:1, obj: $('#aclmenu')};
+ }
+
+ rcmail_ui.show_popup('aclmenu');
+}
+</script>