Support Tasks in kolab_delegation (Bifrost#T18853)

This commit is contained in:
Aleksander Machniak 2016-12-27 04:03:28 -05:00
parent 668f4a3712
commit dc3ea3d942
9 changed files with 267 additions and 90 deletions

View file

@ -7,7 +7,7 @@
* @licstart The following is the entire license notice for the
* JavaScript code in this file.
*
* Copyright (C) 2011-2015, Kolab Systems AG <contact@kolabsys.com>
* Copyright (C) 2011-2016, Kolab Systems AG <contact@kolabsys.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU Affero General Public License as
@ -27,20 +27,32 @@
*/
window.rcmail && rcmail.addEventListener('init', function(evt) {
if (rcmail.env.task == 'mail' || rcmail.env.task == 'calendar') {
// set delegator context for calendar requests on invitation message
rcmail.addEventListener('requestcalendar/event', function(o) { rcmail.event_delegator_request(o); });
rcmail.addEventListener('requestcalendar/mailimportevent', function(o) { rcmail.event_delegator_request(o); });
rcmail.addEventListener('requestcalendar/mailimportitip', function(o) { rcmail.event_delegator_request(o); });
rcmail.addEventListener('requestcalendar/itip-status', function(o) { rcmail.event_delegator_request(o); });
rcmail.addEventListener('requestcalendar/itip-remove', function(o) { rcmail.event_delegator_request(o); });
if (rcmail.env.task == 'mail' || rcmail.env.task == 'calendar' || rcmail.env.task == 'tasks') {
// set delegator context for calendar/tasklist requests on invitation message
rcmail.addEventListener('requestcalendar/event', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requestcalendar/mailimportitip', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requestcalendar/itip-status', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requestcalendar/itip-remove', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requesttasks/task', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requesttasks/mailimportitip', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requesttasks/itip-status', function(o) { rcmail.event_delegator_request(o); })
.addEventListener('requesttasks/itip-remove', function(o) { rcmail.event_delegator_request(o); });
// Calendar UI
if (rcmail.env.delegators && window.rcube_calendar_ui) {
rcmail.calendar_identity_init();
rcmail.calendar_identity_init('calendar');
// delegator context for calendar event form
rcmail.addEventListener('calendar-event-init', function(o) { return rcmail.calendar_event_init(o); });
rcmail.addEventListener('calendar-event-init', function(o) { return rcmail.calendar_event_init(o, 'calendar'); });
// change organizer identity on calendar folder change
$('#edit-calendar').change(function() { rcmail.calendar_change(); });
$('#edit-calendar').change(function() { rcmail.calendar_folder_change(this); });
}
// Tasks UI
else if (rcmail.env.delegators && window.rcube_tasklist_ui) {
rcmail.calendar_identity_init('tasklist');
// delegator context for task form
rcmail.addEventListener('tasklist-task-init', function(o) { return rcmail.calendar_event_init(o, 'tasklist'); });
// change organizer identity on tasks folder change
$('#taskedit-tasklist').change(function() { rcmail.calendar_folder_change(this); });
}
}
else if (rcmail.env.task != 'settings')
@ -251,27 +263,31 @@ rcube_webmail.prototype.event_delegator_request = function(data)
return data;
};
// callback for calendar event form initialization
rcube_webmail.prototype.calendar_event_init = function(data)
// callback for calendar event/task form initialization
rcube_webmail.prototype.calendar_event_init = function(data, type)
{
var folder = data.o[type == 'calendar' ? 'calendar' : 'list']
// set identity for delegator context
this.env.calendar_settings.identity = this.calendar_folder_delegator(data.o.calendar);
this.env[type + '_settings'].identity = this.calendar_folder_delegator(folder);
};
// returns delegator's identity data according to selected calendar folder
rcube_webmail.prototype.calendar_folder_delegator = function(calendar)
// returns delegator's identity data according to selected calendar/tasks folder
rcube_webmail.prototype.calendar_folder_delegator = function(folder, type)
{
var d, delegator;
var d, delegator,
settings = this.env[type + '_settings'],
list = this.env[type == 'calendar' ? 'calendars' : 'tasklists'];
// derive delegator from the calendar owner property
if (this.env.calendars[calendar] && this.env.calendars[calendar].owner) {
delegator = this.env.calendars[calendar].owner.replace(/@.+$/, '');
if (list[folder] && list[folder].owner) {
delegator = list[folder].owner.replace(/@.+$/, '');
}
if (delegator && (d = this.env.delegators[delegator])) {
// find delegator's identity id
if (!d.identity_id)
$.each(this.env.calendar_settings.identities, function(i, v) {
$.each(settings.identities, function(i, v) {
if (d.email == v) {
d.identity_id = i;
return false;
@ -288,26 +304,28 @@ rcube_webmail.prototype.calendar_folder_delegator = function(calendar)
return d;
};
// handler for calendar folder change
rcube_webmail.prototype.calendar_change = function()
// handler for calendar/tasklist folder change
rcube_webmail.prototype.calendar_folder_change = function(element)
{
var calendar = $('#edit-calendar').val(),
var folder = $(element).val(),
type = element.id.indexOf('task') > -1 ? 'tasklist' : 'calendar',
sname = type + '_settings',
select = $('#edit-identities-list'),
old = this.env.calendar_settings.identity;
old = this.env[sname].identity;
this.env.calendar_settings.identity = this.calendar_folder_delegator(calendar);
this.env[sname].identity = this.calendar_folder_delegator(folder, type);
// change organizer identity in identity selector
if (select.length && old != this.env.calendar_settings.identity) {
var id = this.env.calendar_settings.identity.identity_id;
if (select.length && old != this.env[sname].identity) {
var id = this.env[sname].identity.identity_id;
select.val(id || select.find('option').first().val()).change();
}
};
// modify default identity of the user
rcube_webmail.prototype.calendar_identity_init = function()
rcube_webmail.prototype.calendar_identity_init = function(type)
{
var identity = this.env.calendar_settings.identity,
var identity = this.env[type + '_settings'].identity,
emails = identity.emails.split(';');
// remove delegators' emails from list of emails of the current user
@ -320,4 +338,4 @@ rcube_webmail.prototype.calendar_identity_init = function()
identity.emails = emails.join(';');
this.env.original_identity = identity;
}
};

View file

@ -25,7 +25,7 @@
class kolab_delegation extends rcube_plugin
{
public $task = 'login|mail|settings|calendar';
public $task = 'login|mail|settings|calendar|tasks';
private $rc;
private $engine;
@ -49,11 +49,12 @@ class kolab_delegation extends rcube_plugin
// on-message-send delegation support
$this->add_hook('message_before_send', array($this, 'message_before_send'));
// delegation support in Calendar plugin
// delegation support in Calendar and Tasklist plugins
$this->add_hook('message_load', array($this, 'message_load'));
$this->add_hook('calendar_user_emails', array($this, 'calendar_user_emails'));
$this->add_hook('calendar_list_filter', array($this, 'calendar_list_filter'));
$this->add_hook('calendar_load_itip', array($this, 'calendar_load_itip'));
$this->add_hook('tasklist_list_filter', array($this, 'tasklist_list_filter'));
// delegation support in kolab_auth plugin
$this->add_hook('kolab_auth_emails', array($this, 'kolab_auth_emails'));
@ -80,8 +81,10 @@ class kolab_delegation extends rcube_plugin
$this->include_stylesheet($this->skin_path . '/style.css');
}
}
// Calendar plugin UI bindings
else if ($this->rc->task == 'calendar' && empty($_REQUEST['_framed'])) {
// Calendar/Tasklist plugin UI bindings
else if (($this->rc->task == 'calendar' || $this->rc->task == 'tasks')
&& empty($_REQUEST['_framed'])
) {
if ($this->rc->output->type == 'html') {
$this->calendar_ui();
}
@ -200,7 +203,7 @@ class kolab_delegation extends rcube_plugin
return $args;
}
$engine = $this->engine();
$engine = $this->engine();
$context = $engine->delegator_context_from_message($args['object']);
if ($context) {
@ -237,7 +240,23 @@ class kolab_delegation extends rcube_plugin
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_folder_filter($args);
$engine->delegator_folder_filter($args, 'calendars');
}
return $args;
}
/**
* tasklist_driver::get_lists() handler
*/
public function tasklist_list_filter($args)
{
// In delegator context we'll use delegator's folders
// instead of current user folders
if (!empty($_SESSION['delegators'])) {
$engine = $this->engine();
$engine->delegator_folder_filter($args, 'tasklists');
}
return $args;
@ -260,7 +279,7 @@ class kolab_delegation extends rcube_plugin
}
/**
* Delegation support in Calendar plugin UI
* Delegation support in Calendar/Tasks plugin UI
*/
public function calendar_ui()
{

View file

@ -777,11 +777,11 @@ class kolab_delegation_engine
}
/**
* Filters list of calendars according to delegator context
* Filters list of calendar/task folders according to delegator context
*
* @param array $args Reference to plugin hook arguments
*/
public function delegator_folder_filter(&$args)
public function delegator_folder_filter(&$args, $mode = 'calendars')
{
$context = $this->delegator_context();
@ -789,28 +789,40 @@ class kolab_delegation_engine
return $args;
}
$storage = $this->rc->get_storage();
$other_ns = $storage->get_namespace('other');
$delim = $storage->get_hierarchy_delimiter();
$editable = $args['filter'] & calendar_driver::FILTER_WRITEABLE;
$active = $args['filter'] & calendar_driver::FILTER_ACTIVE;
$personal = $args['filter'] & calendar_driver::FILTER_PERSONAL;
$shared = $args['filter'] & calendar_driver::FILTER_SHARED;
$calendars = array();
$storage = $this->rc->get_storage();
$other_ns = $storage->get_namespace('other');
$delim = $storage->get_hierarchy_delimiter();
// code parts derived from kolab_driver::filter_calendars()
foreach ($args['list'] as $cal) {
if (!$cal->ready) {
if ($mode == 'calendars') {
$editable = $args['filter'] & calendar_driver::FILTER_WRITEABLE;
$active = $args['filter'] & calendar_driver::FILTER_ACTIVE;
$personal = $args['filter'] & calendar_driver::FILTER_PERSONAL;
$shared = $args['filter'] & calendar_driver::FILTER_SHARED;
}
else {
$editable = $args['filter'] & tasklist_driver::FILTER_WRITEABLE;
$active = $args['filter'] & tasklist_driver::FILTER_ACTIVE;
$personal = $args['filter'] & tasklist_driver::FILTER_PERSONAL;
$shared = $args['filter'] & tasklist_driver::FILTER_SHARED;
}
$folders = array();
foreach ($args['list'] as $folder) {
if (isset($folder->ready) && !$folder->ready) {
continue;
}
if ($editable && !$cal->editable) {
if ($editable && !$folder->editable) {
continue;
}
if ($active && !$cal->storage->is_active()) {
if ($active && !$folder->storage->is_active()) {
continue;
}
if ($personal || $shared) {
$ns = $cal->get_namespace();
$ns = $folder->get_namespace();
if ($personal && $ns == 'personal') {
continue;
@ -818,8 +830,8 @@ class kolab_delegation_engine
else if ($personal && $ns == 'other') {
$found = false;
foreach ($other_ns as $ns) {
$folder = $ns[0] . $context . $delim;
if (strpos($cal->name, $folder) === 0) {
$c_folder = $ns[0] . $context . $delim;
if (strpos($folder->name, $c_folder) === 0) {
$found = true;
}
}
@ -833,11 +845,11 @@ class kolab_delegation_engine
}
}
$calendars[$cal->id] = $cal;
$folders[$folder->id] = $folder;
}
$args['calendars'] = $calendars;
$args['abort'] = true;
$args[$mode] = $folders;
$args['abort'] = true;
}
/**

View file

@ -89,7 +89,7 @@ class tasklist_database_driver extends tasklist_driver
/**
* Get a list of available tasks lists from this source
*/
public function get_lists()
public function get_lists($filter = 0)
{
// attempt to create a default list for this user
if (empty($this->lists)) {
@ -361,10 +361,13 @@ class tasklist_database_driver extends tasklist_driver
/**
* Return data of a specific task
*
* @param mixed Hash array with task properties or task UID
* @param mixed Hash array with task properties or task UID
* @param integer Bitmask defining filter criterias.
* See FILTER_* constants for possible values.
*
* @return array Hash array with task properties or false if not found
*/
public function get_task($prop)
public function get_task($prop, $filter = 0)
{
if (is_string($prop))
$prop['uid'] = $prop;

View file

@ -120,10 +120,10 @@ class tasklist_kolab_driver extends tasklist_driver
$alarms = false;
$rights = 'lr';
$editable = false;
if (($myrights = $folder->get_myrights()) && !PEAR::isError($myrights)) {
if ($myrights = $folder->get_myrights()) {
$rights = $myrights;
if (strpos($rights, 't') !== false || strpos($rights, 'd') !== false)
$editable = strpos($rights, 'i');
$editable = strpos($rights, 'i') !== false;
}
$info = $folder->get_folder_info();
$norename = $readonly || $info['norename'] || $info['protected'];
@ -147,6 +147,7 @@ class tasklist_kolab_driver extends tasklist_driver
'rights' => $rights,
'norename' => $norename,
'active' => $folder->is_active(),
'owner' => $folder->get_owner(),
'parentfolder' => $folder->get_parent(),
'default' => $folder->default,
'virtual' => $folder->virtual,
@ -163,8 +164,11 @@ class tasklist_kolab_driver extends tasklist_driver
/**
* Get a list of available task lists from this source
*
* @param integer Bitmask defining filter criterias.
* See FILTER_* constants for possible values.
*/
public function get_lists(&$tree = null)
public function get_lists($filter = 0, &$tree = null)
{
$this->_read_lists();
@ -175,12 +179,7 @@ class tasklist_kolab_driver extends tasklist_driver
$this->_read_lists(true);
}
$folders = array();
foreach ($this->lists as $id => $list) {
if (!empty($this->folders[$id])) {
$folders[] = $this->folders[$id];
}
}
$folders = $this->filter_folders($filter);
// include virtual folders for a full folder tree
if (!is_null($tree)) {
@ -251,6 +250,80 @@ class tasklist_kolab_driver extends tasklist_driver
return $lists;
}
/**
* Get list of folders according to specified filters
*
* @param integer Bitmask defining restrictions. See FILTER_* constants for possible values.
*
* @return array List of task folders
*/
protected function filter_folders($filter)
{
$this->_read_lists();
$folders = array();
foreach ($this->lists as $id => $list) {
if (!empty($this->folders[$id])) {
$folder = $this->folders[$id];
if ($folder->get_namespace() == 'personal') {
$folder->editable = true;
}
else if ($rights = $folder->get_myrights()) {
if (strpos($rights, 't') !== false || strpos($rights, 'd') !== false) {
$folder->editable = strpos($rights, 'i') !== false;
}
}
$folders[] = $folder;
}
}
$plugin = $this->rc->plugins->exec_hook('tasklist_list_filter', array(
'list' => $folders,
'filter' => $filter,
'tasklists' => $folders,
));
if ($plugin['abort'] || !$filter) {
return $plugin['tasklists'];
}
$personal = $filter & self::FILTER_PERSONAL;
$shared = $filter & self::FILTER_SHARED;
$tasklists = array();
foreach ($folders as $folder) {
if (($filter & self::FILTER_WRITEABLE) && !$folder->editable) {
continue;
}
/*
if (($filter & self::FILTER_INSERTABLE) && !$folder->insert) {
continue;
}
if (($filter & self::FILTER_ACTIVE) && !$folder->is_active()) {
continue;
}
if (($filter & self::FILTER_PRIVATE) && $folder->subtype != 'private') {
continue;
}
if (($filter & self::FILTER_CONFIDENTIAL) && $folder->subtype != 'confidential') {
continue;
}
*/
if ($personal || $shared) {
$ns = $folder->get_namespace();
if (!(($personal && $ns == 'personal') || ($shared && $ns == 'shared'))) {
continue;
}
}
$tasklists[$folder->id] = $folder;
}
return $tasklists;
}
/**
* Get the kolab_calendar instance for the given calendar ID
*
@ -616,20 +689,24 @@ class tasklist_kolab_driver extends tasklist_driver
/**
* Return data of a specific task
*
* @param mixed Hash array with task properties or task UID
* @param mixed Hash array with task properties or task UID
* @param integer Bitmask defining filter criterias for folders.
* See FILTER_* constants for possible values.
*
* @return array Hash array with task properties or false if not found
*/
public function get_task($prop)
public function get_task($prop, $filter = 0)
{
$this->_read_lists();
$this->_parse_id($prop);
$id = $prop['uid'];
$list_id = $prop['list'];
$folders = $list_id ? array($list_id => $this->get_folder($list_id)) : $this->folders;
$folders = $list_id ? array($list_id => $this->get_folder($list_id)) : $this->get_lists($filter);
// find task in the available folders
foreach ($folders as $list_id => $folder) {
if (is_array($folder))
$folder = $this->folders[$list_id];
if (is_numeric($list_id) || !$folder)
continue;
if (!$this->tasks[$id] && ($object = $folder->get_object($id))) {

View file

@ -79,10 +79,22 @@ abstract class tasklist_driver
public $alarm_absolute = true;
public $last_error;
const FILTER_ALL = 0;
const FILTER_WRITEABLE = 1;
const FILTER_INSERTABLE = 2;
const FILTER_ACTIVE = 4;
const FILTER_PERSONAL = 8;
const FILTER_PRIVATE = 16;
const FILTER_CONFIDENTIAL = 32;
const FILTER_SHARED = 64;
/**
* Get a list of available task lists from this source
* @param integer Bitmask defining filter criterias.
* See FILTER_* constants for possible values.
*/
abstract function get_lists();
abstract function get_lists($filter = 0);
/**
* Create a new list assigned to the current user
@ -196,10 +208,13 @@ abstract class tasklist_driver
/**
* Return data of a specific task
*
* @param mixed Hash array with task properties or task UID
* @param mixed Hash array with task properties or task UID
* @param integer Bitmask defining filter criterias for folders.
* See FILTER_* constants for possible values.
*
* @return array Hash array with task properties or false if not found
*/
abstract public function get_task($prop);
abstract public function get_task($prop, $filter = 0);
/**
* Get decendents of the given task record

View file

@ -2373,6 +2373,9 @@ function rcube_tasklist_ui(settings)
// reset dialog first
$('#taskeditform').get(0).reset();
// allow other plugins to do actions when task form is opened
rcmail.triggerEvent('tasklist-task-init', {o: rec});
// fill form data
var title = $('#taskedit-title').val(rec.title || '');
var description = $('#taskedit-description').val(rec.description || '');

View file

@ -1871,9 +1871,12 @@ class tasklist extends rcube_plugin
/**
* Get properties of the tasklist this user has specified as default
*/
public function get_default_tasklist($sensitivity = null)
public function get_default_tasklist($sensitivity = null, $lists = null)
{
$lists = $this->driver->get_lists();
if ($lists === null) {
$lists = $this->driver->get_lists(tasklist_driver::FILTER_PERSONAL | tasklist_driver::FILTER_WRITEABLE);
}
$list = null;
foreach ($lists as $l) {
@ -2005,15 +2008,19 @@ class tasklist extends rcube_plugin
unset($task['comment']);
}
$mode = tasklist_driver::FILTER_PERSONAL
| tasklist_driver::FILTER_SHARED
| tasklist_driver::FILTER_WRITEABLE;
// find writeable list to store the task
$list_id = !empty($_REQUEST['_folder']) ? rcube_utils::get_input_value('_folder', rcube_utils::INPUT_POST) : null;
$lists = $this->driver->get_lists();
$lists = $this->driver->get_lists($mode);
$list = $lists[$list_id];
$dontsave = ($_REQUEST['_folder'] === '' && $task['_method'] == 'REQUEST');
// select default list except user explicitly selected 'none'
if (!$list && !$dontsave) {
$list = $this->get_default_tasklist($task['sensitivity']);
$list = $this->get_default_tasklist($task['sensitivity'], $lists);
}
$metadata = array(
@ -2061,7 +2068,7 @@ class tasklist extends rcube_plugin
$task['list'] = $list['id'];
// check for existing task with the same UID
$existing = $this->driver->get_task($task['uid']);
$existing = $this->find_task($task['uid'], $mode);
if ($existing) {
// only update attendee status
@ -2225,6 +2232,28 @@ class tasklist extends rcube_plugin
$this->mail_import_itip();
}
/**
* Find a task in user tasklists
*/
protected function find_task($task, &$mode)
{
$this->load_driver();
// We search for writeable folders in personal namespace by default
$mode = tasklist_driver::FILTER_WRITEABLE | tasklist_driver::FILTER_PERSONAL;
$result = $this->driver->get_task($task, $mode);
// ... now check shared folders if not found
if (!$result) {
$result = $this->driver->get_task($task, tasklist_driver::FILTER_WRITEABLE | tasklist_driver::FILTER_SHARED);
if ($result) {
$mode |= tasklist_driver::FILTER_SHARED;
}
}
return $result;
}
/**
* Handler for task/itip-status requests
*/
@ -2233,13 +2262,14 @@ class tasklist extends rcube_plugin
$data = rcube_utils::get_input_value('data', rcube_utils::INPUT_POST, true);
// find local copy of the referenced task
$existing = $this->driver->get_task($data);
$itip = $this->load_itip();
$response = $itip->get_itip_status($data, $existing);
$existing = $this->find_task($data, $mode);
$is_shared = $mode & tasklist_driver::FILTER_SHARED;
$itip = $this->load_itip();
$response = $itip->get_itip_status($data, $existing);
// get a list of writeable lists to save new tasks to
if (!$existing && $response['action'] == 'rsvp' || $response['action'] == 'import') {
$lists = $this->driver->get_lists();
if ((!$existing || $is_shared) && $response['action'] == 'rsvp' || $response['action'] == 'import') {
$lists = $this->driver->get_lists($mode);
$select = new html_select(array('name' => 'tasklist', 'id' => 'itip-saveto', 'is_escaped' => true));
$select->add('--', '');
@ -2251,9 +2281,9 @@ class tasklist extends rcube_plugin
}
if ($select) {
$default_list = $this->get_default_tasklist($data['sensitivity']);
$default_list = $this->get_default_tasklist($data['sensitivity'], $lists);
$response['select'] = html::span('folder-select', $this->gettext('saveintasklist') . '&nbsp;' .
$select->show($default_list['id']));
$select->show($is_shared ? $existing['list'] : $default_list['id']));
}
$this->rc->output->command('plugin.update_itip_object_status', $response);

View file

@ -178,7 +178,7 @@ class tasklist_ui
{
$tree = true;
$jsenv = array();
$lists = $this->plugin->driver->get_lists($tree);
$lists = $this->plugin->driver->get_lists(0, $tree);
// walk folder tree
if (is_object($tree)) {