More changes for Assignments tab (#1165)
This commit is contained in:
parent
6fad5ede55
commit
f2c6a3851d
11 changed files with 458 additions and 113 deletions
|
@ -25,16 +25,17 @@
|
|||
class tasklist_kolab_driver extends tasklist_driver
|
||||
{
|
||||
// features supported by the backend
|
||||
public $alarms = false;
|
||||
public $alarms = false;
|
||||
public $attachments = true;
|
||||
public $undelete = false; // task undelete action
|
||||
public $attendees = true;
|
||||
public $undelete = false; // task undelete action
|
||||
public $alarm_types = array('DISPLAY','AUDIO');
|
||||
|
||||
private $rc;
|
||||
private $plugin;
|
||||
private $lists;
|
||||
private $folders = array();
|
||||
private $tasks = array();
|
||||
private $tasks = array();
|
||||
|
||||
|
||||
/**
|
||||
|
@ -772,6 +773,7 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
'status' => $record['status'],
|
||||
'parent_id' => $record['parent_id'],
|
||||
'recurrence' => $record['recurrence'],
|
||||
'attendees' => $record['attendees'],
|
||||
);
|
||||
|
||||
// convert from DateTime to internal date format
|
||||
|
@ -898,6 +900,14 @@ class tasklist_kolab_driver extends tasklist_driver
|
|||
unset($object['attachments']);
|
||||
}
|
||||
|
||||
// set current user as ORGANIZER
|
||||
$identity = $this->rc->user->get_identity();
|
||||
if (empty($object['attendees']) && $identity['email']) {
|
||||
$object['attendees'] = array(array('role' => 'ORGANIZER', 'name' => $identity['name'], 'email' => $identity['email']));
|
||||
}
|
||||
|
||||
$object['_owner'] = $identity['email'];
|
||||
|
||||
unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags']);
|
||||
return $object;
|
||||
}
|
||||
|
|
|
@ -72,6 +72,7 @@ abstract class tasklist_driver
|
|||
// features supported by the backend
|
||||
public $alarms = false;
|
||||
public $attachments = false;
|
||||
public $attendees = false;
|
||||
public $undelete = false; // task undelete action
|
||||
public $sortable = false;
|
||||
public $alarm_types = array('DISPLAY');
|
||||
|
|
|
@ -132,3 +132,9 @@ $labels['itipdeclineevent'] = 'Do you want to decline your assignment to this ta
|
|||
$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined task from your tasks list?';
|
||||
$labels['itipcomment'] = 'Invitation/notification comment';
|
||||
$labels['itipcommenttitle'] = 'This comment will be attached to the invitation/notification message sent to participants';
|
||||
$labels['itipsendsuccess'] = 'Invitation sent to participants.';
|
||||
$labels['errornotifying'] = 'Failed to send notifications to task participants';
|
||||
|
||||
$labels['andnmore'] = '$nr more...';
|
||||
$labels['delegatedto'] = 'Delegated to: ';
|
||||
$labels['delegatedfrom'] = 'Delegated from: ';
|
||||
|
|
BIN
plugins/tasklist/skins/larry/images/attendee-status.png
Normal file
BIN
plugins/tasklist/skins/larry/images/attendee-status.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 2.9 KiB |
BIN
plugins/tasklist/skins/larry/images/sendinvitation.png
Normal file
BIN
plugins/tasklist/skins/larry/images/sendinvitation.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 1.2 KiB |
|
@ -1013,6 +1013,49 @@ label.block {
|
|||
outline: none;
|
||||
}
|
||||
|
||||
.task-attendees span.attendee {
|
||||
padding-right: 18px;
|
||||
margin-right: 0.5em;
|
||||
background: url(images/attendee-status.png) right 0 no-repeat;
|
||||
}
|
||||
|
||||
.task-attendees span.attendee a.mailtolink {
|
||||
text-decoration: none;
|
||||
white-space: nowrap;
|
||||
outline: none;
|
||||
}
|
||||
|
||||
.task-attendees span.attendee a.mailtolink:hover {
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
.task-attendees span.accepted {
|
||||
background-position: right -20px;
|
||||
}
|
||||
|
||||
.task-attendees span.declined {
|
||||
background-position: right -40px;
|
||||
}
|
||||
|
||||
.task-attendees span.tentative {
|
||||
background-position: right -60px;
|
||||
}
|
||||
|
||||
.task-attendees span.delegated {
|
||||
background-position: right -180px;
|
||||
}
|
||||
|
||||
.task-attendees span.organizer {
|
||||
background-position: right -80px;
|
||||
}
|
||||
|
||||
#all-task-attendees span.attendee {
|
||||
display: block;
|
||||
margin-bottom: 0.4em;
|
||||
padding-bottom: 0.3em;
|
||||
border-bottom: 1px solid #ddd;
|
||||
}
|
||||
|
||||
.tasklistview .uidialog .tabbed {
|
||||
min-width: 600px;
|
||||
}
|
||||
|
@ -1025,6 +1068,22 @@ label.block {
|
|||
width: 20em;
|
||||
}
|
||||
|
||||
.ui-dialog .task-update-confirm {
|
||||
padding: 0 0.5em 0.5em 0.5em;
|
||||
}
|
||||
|
||||
.task-dialog-message,
|
||||
.task-update-confirm .message {
|
||||
margin-top: 0.5em;
|
||||
padding: 0.8em;
|
||||
border: 1px solid #ffdf0e;
|
||||
background-color: #fef893;
|
||||
}
|
||||
|
||||
.task-dialog-message .message,
|
||||
.task-update-confirm .message {
|
||||
margin-bottom: 0.5em;
|
||||
}
|
||||
|
||||
/** Special hacks for IE7 **/
|
||||
/** They need to be in this file to also affect the task-create dialog embedded in mail view **/
|
||||
|
|
|
@ -156,6 +156,19 @@
|
|||
<label><roundcube:label name="tasklist.alarms" /></label>
|
||||
<span class="task-text"></span>
|
||||
</div>
|
||||
<div class="form-section task-attendees" id="task-attendees">
|
||||
<h5 class="label"><roundcube:label name="tasklist.tabassignments" /></h5>
|
||||
<div class="task-text"></div>
|
||||
</div>
|
||||
<!--
|
||||
<div class="form-section" id="task-partstat">
|
||||
<label><roundcube:label name="tasklist.mystatus" /></label>
|
||||
<span class="changersvp" role="button" tabindex="0" title="<roundcube:label name='tasklist.changepartstat' />">
|
||||
<span class="task-text"></span>
|
||||
<a class="iconbutton edit"><roundcube:label name='tasklist.changepartstat' /></a>
|
||||
</span>
|
||||
</div>
|
||||
-->
|
||||
<div id="task-list" class="form-section">
|
||||
<label><roundcube:label name="tasklist.list" /></label>
|
||||
<span class="task-text"></span>
|
||||
|
|
|
@ -98,5 +98,5 @@
|
|||
<roundcube:object name="plugin.filedroparea" id="taskedit-tab-2" />
|
||||
</div>
|
||||
</form>
|
||||
<roundcube:object name="plugin.edit_attendees_notify" id="edit-attendees-notify" class="taskedit-dialog-message" style="display:none" />
|
||||
<roundcube:object name="plugin.edit_attendees_notify" id="edit-attendees-notify" class="task-dialog-message" style="display:none" />
|
||||
</div>
|
||||
|
|
|
@ -572,6 +572,19 @@ function rcube_tasklist_ui(settings)
|
|||
input.val('');
|
||||
}
|
||||
});
|
||||
|
||||
// handle change of "send invitations" checkbox
|
||||
$('#edit-attendees-invite').change(function() {
|
||||
$('#edit-attendees-donotify,input.edit-attendee-reply').prop('checked', this.checked);
|
||||
// hide/show comment field
|
||||
$('.attendees-commentbox')[this.checked ? 'show' : 'hide']();
|
||||
});
|
||||
|
||||
// delegate change task to "send invitations" checkbox
|
||||
$('#edit-attendees-donotify').change(function() {
|
||||
$('#edit-attendees-invite').click();
|
||||
return false;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -1338,11 +1351,13 @@ function rcube_tasklist_ui(settings)
|
|||
// check if the current user is an attendee of this task
|
||||
var is_attendee = function(task, role, email)
|
||||
{
|
||||
var i, emails = email ? ';' + email.toLowerCase() : settings.identity.emails;
|
||||
var i, attendee, emails = email ? ';' + email.toLowerCase() : settings.identity.emails;
|
||||
|
||||
for (i=0; task.attendees && i < task.attendees.length; i++) {
|
||||
if ((!role || task.attendees[i].role == role) && task.attendees[i].email && emails.indexOf(';'+task.attendees[i].email.toLowerCase()) >= 0)
|
||||
return task.attendees[i];
|
||||
attendee = task.attendees[i];
|
||||
if ((!role || attendee.role == role) && attendee.email && emails.indexOf(';'+attendee.email.toLowerCase()) >= 0) {
|
||||
return attendee;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
|
@ -1357,140 +1372,144 @@ function rcube_tasklist_ui(settings)
|
|||
// add the given list of participants
|
||||
var add_attendees = function(names, params)
|
||||
{
|
||||
names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
|
||||
names = explode_quoted_string(names.replace(/,\s*$/, ''), ',');
|
||||
|
||||
// parse name/email pairs
|
||||
var item, email, name, success = false;
|
||||
for (var i=0; i < names.length; i++) {
|
||||
email = name = '';
|
||||
item = $.trim(names[i]);
|
||||
// parse name/email pairs
|
||||
var i, item, email, name, success = false;
|
||||
for (i=0; i < names.length; i++) {
|
||||
email = name = '';
|
||||
item = $.trim(names[i]);
|
||||
|
||||
if (!item.length) {
|
||||
continue;
|
||||
} // address in brackets without name (do nothing)
|
||||
else if (item.match(/^<[^@]+@[^>]+>$/)) {
|
||||
email = item.replace(/[<>]/g, '');
|
||||
} // address without brackets and without name (add brackets)
|
||||
else if (rcube_check_email(item)) {
|
||||
email = item;
|
||||
} // address with name
|
||||
else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
|
||||
email = RegExp.$1;
|
||||
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
|
||||
}
|
||||
if (email) {
|
||||
add_attendee($.extend({ email:email, name:name }, params));
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
alert(rcmail.gettext('noemailwarning'));
|
||||
}
|
||||
}
|
||||
if (!item.length) {
|
||||
continue;
|
||||
}
|
||||
// address in brackets without name (do nothing)
|
||||
else if (item.match(/^<[^@]+@[^>]+>$/)) {
|
||||
email = item.replace(/[<>]/g, '');
|
||||
}
|
||||
// address without brackets and without name (add brackets)
|
||||
else if (rcube_check_email(item)) {
|
||||
email = item;
|
||||
}
|
||||
// address with name
|
||||
else if (item.match(/([^\s<@]+@[^>]+)>*$/)) {
|
||||
email = RegExp.$1;
|
||||
name = item.replace(email, '').replace(/^["\s<>]+/, '').replace(/["\s<>]+$/, '');
|
||||
}
|
||||
|
||||
return success;
|
||||
if (email) {
|
||||
add_attendee($.extend({ email:email, name:name }, params));
|
||||
success = true;
|
||||
}
|
||||
else {
|
||||
alert(rcmail.gettext('noemailwarning'));
|
||||
}
|
||||
}
|
||||
|
||||
return success;
|
||||
};
|
||||
|
||||
// add the given attendee to the list
|
||||
var add_attendee = function(data, readonly)
|
||||
{
|
||||
if (!me.selected_task)
|
||||
return false;
|
||||
if (!me.selected_task)
|
||||
return false;
|
||||
|
||||
// check for dupes...
|
||||
var exists = false;
|
||||
$.each(task_attendees, function(i, v) { exists |= (v.email == data.email); });
|
||||
if (exists)
|
||||
return false;
|
||||
// check for dupes...
|
||||
var exists = false;
|
||||
$.each(task_attendees, function(i, v) { exists |= (v.email == data.email); });
|
||||
if (exists)
|
||||
return false;
|
||||
|
||||
var list = me.selected_task && me.tasklists[me.selected_task.list] ? me.tasklists[me.selected_task.list] : me.tasklists[me.selected_list];
|
||||
// var list = me.selected_task && me.tasklists[me.selected_task.list] ? me.tasklists[me.selected_task.list] : me.tasklists[me.selected_list];
|
||||
|
||||
var dispname = Q(data.name || data.email);
|
||||
if (data.email)
|
||||
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
|
||||
var dispname = Q(data.name || data.email);
|
||||
if (data.email)
|
||||
dispname = '<a href="mailto:' + data.email + '" title="' + Q(data.email) + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
|
||||
|
||||
// role selection
|
||||
var organizer = data.role == 'ORGANIZER';
|
||||
var opts = {};
|
||||
if (organizer)
|
||||
opts.ORGANIZER = rcmail.gettext('.roleorganizer');
|
||||
opts['REQ-PARTICIPANT'] = rcmail.gettext('tasklist.rolerequired');
|
||||
opts['OPT-PARTICIPANT'] = rcmail.gettext('tasklist.roleoptional');
|
||||
opts['NON-PARTICIPANT'] = rcmail.gettext('tasklist.rolenonparticipant');
|
||||
// role selection
|
||||
var opts = {}, organizer = data.role == 'ORGANIZER';
|
||||
if (organizer)
|
||||
opts.ORGANIZER = rcmail.gettext('tasklist.roleorganizer');
|
||||
opts['REQ-PARTICIPANT'] = rcmail.gettext('tasklist.rolerequired');
|
||||
opts['OPT-PARTICIPANT'] = rcmail.gettext('tasklist.roleoptional');
|
||||
opts['NON-PARTICIPANT'] = rcmail.gettext('tasklist.rolenonparticipant');
|
||||
|
||||
if (data.cutype != 'RESOURCE')
|
||||
opts['CHAIR'] = rcmail.gettext('tasklist.rolechair');
|
||||
if (data.cutype != 'RESOURCE')
|
||||
opts['CHAIR'] = rcmail.gettext('tasklist.rolechair');
|
||||
|
||||
if (organizer && !readonly)
|
||||
dispname = rcmail.env['identities-selector'];
|
||||
if (organizer && !readonly)
|
||||
dispname = rcmail.env['identities-selector'];
|
||||
|
||||
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + ' aria-label="' + rcmail.gettext('role','tasklist') + '">';
|
||||
for (var r in opts)
|
||||
select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
|
||||
select += '</select>';
|
||||
var select = '<select class="edit-attendee-role"' + (organizer || readonly ? ' disabled="true"' : '') + ' aria-label="' + rcmail.gettext('role','tasklist') + '">';
|
||||
for (var r in opts)
|
||||
select += '<option value="'+ r +'" class="' + r.toLowerCase() + '"' + (data.role == r ? ' selected="selected"' : '') +'>' + Q(opts[r]) + '</option>';
|
||||
select += '</select>';
|
||||
|
||||
// availability
|
||||
var avail = data.email ? 'loading' : 'unknown';
|
||||
// availability
|
||||
var avail = data.email ? 'loading' : 'unknown';
|
||||
|
||||
// delete icon
|
||||
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
|
||||
var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
|
||||
var tooltip = data.status || '';
|
||||
// delete icon
|
||||
var icon = rcmail.env.deleteicon ? '<img src="' + rcmail.env.deleteicon + '" alt="" />' : rcmail.gettext('delete');
|
||||
var dellink = '<a href="#delete" class="iconlink delete deletelink" title="' + Q(rcmail.gettext('delete')) + '">' + icon + '</a>';
|
||||
var tooltip = data.status || '';
|
||||
|
||||
// send invitation checkbox
|
||||
var invbox = '<input type="checkbox" class="edit-attendee-reply" value="' + Q(data.email) +'" title="' + Q(rcmail.gettext('tasklist.sendinvitations')) + '" '
|
||||
+ (!data.noreply ? 'checked="checked" ' : '') + '/>';
|
||||
// send invitation checkbox
|
||||
var invbox = '<input type="checkbox" class="edit-attendee-reply" value="' + Q(data.email) +'" title="' + Q(rcmail.gettext('tasklist.sendinvitations')) + '" '
|
||||
+ (!data.noreply ? 'checked="checked" ' : '') + '/>';
|
||||
|
||||
if (data['delegated-to'])
|
||||
tooltip = rcmail.gettext('delegatedto', 'tasklist') + data['delegated-to'];
|
||||
else if (data['delegated-from'])
|
||||
tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from'];
|
||||
if (data['delegated-to'])
|
||||
tooltip = rcmail.gettext('delegatedto', 'tasklist') + data['delegated-to'];
|
||||
else if (data['delegated-from'])
|
||||
tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from'];
|
||||
|
||||
var html = '<td class="role">' + select + '</td>' +
|
||||
'<td class="name">' + dispname + '</td>' +
|
||||
// '<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" data-email="' + data.email + '" alt="" /></td>' +
|
||||
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + Q(data.status || '') + '</span></td>' +
|
||||
(data.cutype != 'RESOURCE' ? '<td class="sendmail">' + (organizer || readonly || !invbox ? '' : invbox) + '</td>' : '') +
|
||||
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
|
||||
var html = '<td class="role">' + select + '</td>' +
|
||||
'<td class="name">' + dispname + '</td>' +
|
||||
// '<td class="availability"><img src="./program/resources/blank.gif" class="availabilityicon ' + avail + '" data-email="' + data.email + '" alt="" /></td>' +
|
||||
'<td class="confirmstate"><span class="' + String(data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + Q(data.status || '') + '</span></td>' +
|
||||
(data.cutype != 'RESOURCE' ? '<td class="sendmail">' + (organizer || readonly || !invbox ? '' : invbox) + '</td>' : '') +
|
||||
'<td class="options">' + (organizer || readonly ? '' : dellink) + '</td>';
|
||||
|
||||
var table = rcmail.env.tasklist_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
|
||||
var tr = $('<tr>')
|
||||
.addClass(String(data.role).toLowerCase())
|
||||
.html(html)
|
||||
.appendTo(table);
|
||||
var table = rcmail.env.tasklist_resources && data.cutype == 'RESOURCE' ? resources_list : attendees_list;
|
||||
var tr = $('<tr>')
|
||||
.addClass(String(data.role).toLowerCase())
|
||||
.html(html)
|
||||
.appendTo(table);
|
||||
|
||||
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
|
||||
tr.find('a.mailtolink').click(task_attendee_click);
|
||||
tr.find('input.edit-attendee-reply').click(function() {
|
||||
var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length;
|
||||
$('p.attendees-commentbox')[enabled ? 'show' : 'hide']();
|
||||
});
|
||||
tr.find('a.deletelink').click({ id:(data.email || data.name) }, function(e) { remove_attendee(this, e.data.id); return false; });
|
||||
tr.find('a.mailtolink').click(task_attendee_click);
|
||||
tr.find('input.edit-attendee-reply').click(function() {
|
||||
var enabled = $('#edit-attendees-invite:checked').length || $('input.edit-attendee-reply:checked').length;
|
||||
$('p.attendees-commentbox')[enabled ? 'show' : 'hide']();
|
||||
});
|
||||
|
||||
// select organizer identity
|
||||
if (data.identity_id)
|
||||
$('#edit-identities-list').val(data.identity_id);
|
||||
// select organizer identity
|
||||
if (data.identity_id)
|
||||
$('#edit-identities-list').val(data.identity_id);
|
||||
|
||||
// check free-busy status
|
||||
// if (avail == 'loading') {
|
||||
// check_freebusy_status(tr.find('img.availabilityicon'), data.email, me.selected_task);
|
||||
// }
|
||||
|
||||
task_attendees.push(data);
|
||||
return true;
|
||||
task_attendees.push(data);
|
||||
return true;
|
||||
};
|
||||
|
||||
// event handler for clicks on an attendee link
|
||||
var task_attendee_click = function(e)
|
||||
{
|
||||
var cutype = $(this).attr('data-cutype'),
|
||||
mailto = this.href.substr(7);
|
||||
var cutype = $(this).attr('data-cutype'),
|
||||
mailto = this.href.substr(7);
|
||||
|
||||
if (rcmail.env.calendar_resources && cutype == 'RESOURCE') {
|
||||
event_resources_dialog(mailto);
|
||||
}
|
||||
else {
|
||||
rcmail.redirect(rcmail.url('mail/compose', { _to:mailto }));
|
||||
}
|
||||
return false;
|
||||
if (rcmail.env.tasklist_resources && cutype == 'RESOURCE') {
|
||||
task_resources_dialog(mailto);
|
||||
}
|
||||
else {
|
||||
rcmail.redirect(rcmail.url('mail/compose', {_to: mailto}));
|
||||
}
|
||||
|
||||
return false;
|
||||
};
|
||||
|
||||
// remove an attendee from the list
|
||||
|
@ -1528,6 +1547,7 @@ function rcube_tasklist_ui(settings)
|
|||
$('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%');
|
||||
$('#task-status')[(rec.status ? 'show' : 'hide')]().children('.task-text').html(rcmail.gettext('status-'+String(rec.status).toLowerCase(),'tasklist'));
|
||||
$('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : ''));
|
||||
$('#task-attendees').hide();
|
||||
|
||||
var itags = get_inherited_tags(rec);
|
||||
var taglist = $('#task-tags')[(rec.tags && rec.tags.length || itags.length ? 'show' : 'hide')]().children('.task-text').empty();
|
||||
|
@ -1565,6 +1585,90 @@ function rcube_tasklist_ui(settings)
|
|||
}
|
||||
}
|
||||
|
||||
// list task attendees
|
||||
if (list.attendees && rec.attendees) {
|
||||
/*
|
||||
// sort resources to the end
|
||||
rec.attendees.sort(function(a,b) {
|
||||
var j = a.cutype == 'RESOURCE' ? 1 : 0,
|
||||
k = b.cutype == 'RESOURCE' ? 1 : 0;
|
||||
return (j - k);
|
||||
});
|
||||
*/
|
||||
var j, data, dispname, tooltip, organizer = false, rsvp = false, mystatus = null, line, morelink, html = '', overflow = '';
|
||||
for (j=0; j < rec.attendees.length; j++) {
|
||||
data = rec.attendees[j];
|
||||
dispname = Q(data.name || data.email);
|
||||
tooltip = '';
|
||||
|
||||
if (data.email) {
|
||||
tooltip = data.email;
|
||||
dispname = '<a href="mailto:' + data.email + '" class="mailtolink" data-cutype="' + data.cutype + '">' + dispname + '</a>';
|
||||
if (data.role == 'ORGANIZER')
|
||||
organizer = true;
|
||||
else if (settings.identity.emails.indexOf(';'+data.email) >= 0) {
|
||||
mystatus = data.status.toLowerCase();
|
||||
if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
|
||||
rsvp = mystatus;
|
||||
}
|
||||
}
|
||||
|
||||
if (data['delegated-to'])
|
||||
tooltip = rcmail.gettext('delegatedto', 'tasklist') + data['delegated-to'];
|
||||
else if (data['delegated-from'])
|
||||
tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from'];
|
||||
|
||||
line = '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + dispname + '</span> ';
|
||||
|
||||
if (morelink)
|
||||
overflow += line;
|
||||
else
|
||||
html += line;
|
||||
|
||||
// stop listing attendees
|
||||
if (j == 7 && rec.attendees.length >= 7) {
|
||||
morelink = $('<a href="#more" class="morelink"></a>').html(rcmail.gettext('andnmore', 'tasklist').replace('$nr', rec.attendees.length - j - 1));
|
||||
}
|
||||
}
|
||||
|
||||
if (html && (rec.attendees.length > 1 || !organizer)) {
|
||||
$('#task-attendees').show()
|
||||
.children('.task-text')
|
||||
.html(html)
|
||||
.find('a.mailtolink').click(task_attendee_click);
|
||||
|
||||
// display all attendees in a popup when clicking the "more" link
|
||||
if (morelink) {
|
||||
$('#task-attendees .task-text').append(morelink);
|
||||
morelink.click(function(e) {
|
||||
rcmail.show_popup_dialog(
|
||||
'<div id="all-task-attendees" class="task-attendees">' + html + overflow + '</div>',
|
||||
rcmail.gettext('tabattendees', 'tasklist'),
|
||||
null,
|
||||
{width: 450, modal: false}
|
||||
);
|
||||
$('#all-task-attendees a.mailtolink').click(task_attendee_click);
|
||||
return false;
|
||||
});
|
||||
}
|
||||
}
|
||||
/*
|
||||
if (mystatus && !rsvp) {
|
||||
$('#task-partstat').show().children('.changersvp')
|
||||
.removeClass('accepted tentative declined delegated needs-action')
|
||||
.addClass(mystatus)
|
||||
.children('.task-text')
|
||||
.html(Q(rcmail.gettext('itip' + mystatus, 'libcalendaring')));
|
||||
}
|
||||
|
||||
$('#task-rsvp')[(rsvp && !is_organizer(event) && rec.status != 'CANCELLED' ? 'show' : 'hide')]();
|
||||
$('#task-rsvp .rsvp-buttons input').prop('disabled', false).filter('input[rel='+mystatus+']').prop('disabled', true);
|
||||
|
||||
$('#task-rsvp a.reply-comment-toggle').show();
|
||||
$('#task-rsvp .itip-reply-comment textarea').hide().val('');
|
||||
*/
|
||||
}
|
||||
|
||||
// define dialog buttons
|
||||
var buttons = [];
|
||||
if (list.editable && !rec.readonly) {
|
||||
|
@ -1874,7 +1978,7 @@ function rcube_tasklist_ui(settings)
|
|||
// set dialog size according to content
|
||||
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
|
||||
|
||||
if (tasklist.attendees)
|
||||
if (list.attendees)
|
||||
window.setTimeout(load_attendees_tab, 1);
|
||||
}
|
||||
|
||||
|
|
|
@ -55,6 +55,7 @@ class tasklist extends rcube_plugin
|
|||
public $home; // declare public to be used in other classes
|
||||
|
||||
private $collapsed_tasks = array();
|
||||
private $itip;
|
||||
|
||||
|
||||
/**
|
||||
|
@ -64,7 +65,7 @@ class tasklist extends rcube_plugin
|
|||
{
|
||||
$this->require_plugin('libcalendaring');
|
||||
|
||||
$this->rc = rcube::get_instance();
|
||||
$this->rc = rcube::get_instance();
|
||||
$this->lib = libcalendaring::get_instance();
|
||||
|
||||
$this->register_task('tasks', 'tasklist');
|
||||
|
@ -188,7 +189,7 @@ class tasklist extends rcube_plugin
|
|||
{
|
||||
$filter = intval(get_input_value('filter', RCUBE_INPUT_GPC));
|
||||
$action = get_input_value('action', RCUBE_INPUT_GPC);
|
||||
$rec = get_input_value('t', RCUBE_INPUT_POST, true);
|
||||
$rec = get_input_value('t', RCUBE_INPUT_POST, true);
|
||||
$oldrec = $rec;
|
||||
$success = $refresh = false;
|
||||
|
||||
|
@ -318,8 +319,24 @@ class tasklist extends rcube_plugin
|
|||
$this->rc->output->show_message('successfullysaved', 'confirmation');
|
||||
$this->update_counts($oldrec, $refresh);
|
||||
}
|
||||
else
|
||||
else {
|
||||
$this->rc->output->show_message('tasklist.errorsaving', 'error');
|
||||
}
|
||||
|
||||
// send out notifications
|
||||
if ($success && $rec['_notify'] && ($rec['attendees'] || $oldrec['attendees'])) {
|
||||
// make sure we have the complete record
|
||||
$task = $action == 'delete' ? $oldrec : $this->driver->get_task($rec);
|
||||
|
||||
// only notify if data really changed (TODO: do diff check on client already)
|
||||
if (!$oldrec || $action == 'delete' || self::task_diff($event, $old)) {
|
||||
$sent = $this->notify_attendees($task, $oldrec, $action, $rec['_comment']);
|
||||
if ($sent > 0)
|
||||
$this->rc->output->show_message('tasklist.itipsendsuccess', 'confirmation');
|
||||
else if ($sent < 0)
|
||||
$this->rc->output->show_message('tasklist.errornotifying', 'error');
|
||||
}
|
||||
}
|
||||
|
||||
// unlock client
|
||||
$this->rc->output->command('plugin.unlock_saving');
|
||||
|
@ -336,6 +353,23 @@ class tasklist extends rcube_plugin
|
|||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Load iTIP functions
|
||||
*/
|
||||
private function load_itip()
|
||||
{
|
||||
if (!$this->itip) {
|
||||
require_once realpath(__DIR__ . '/../libcalendaring/lib/libcalendaring_itip.php');
|
||||
$this->itip = new libcalendaring_itip($this, 'tasklist');
|
||||
|
||||
// if ($this->rc->config->get('kolab_invitation_tasklists')) {
|
||||
// $this->itip->set_rsvp_actions(array('accepted','tentative','declined','needs-action'));
|
||||
// }
|
||||
}
|
||||
|
||||
return $this->itip;
|
||||
}
|
||||
|
||||
/**
|
||||
* repares new/edited task properties before save
|
||||
*/
|
||||
|
@ -586,6 +620,106 @@ class tasklist extends rcube_plugin
|
|||
return $clone;
|
||||
}
|
||||
|
||||
/**
|
||||
* Send out an invitation/notification to all task attendees
|
||||
*/
|
||||
private function notify_attendees($task, $old, $action = 'edit', $comment = null)
|
||||
{
|
||||
if ($action == 'delete' || ($task['status'] == 'CANCELLED' && $old['status'] != $task['status'])) {
|
||||
$task['cancelled'] = true;
|
||||
$is_cancelled = true;
|
||||
}
|
||||
|
||||
$itip = $this->load_itip();
|
||||
$emails = $this->lib->get_user_emails();
|
||||
|
||||
// add comment to the iTip attachment
|
||||
$task['comment'] = $comment;
|
||||
|
||||
// needed to generate VTODO instead of VEVENT entry
|
||||
$task['_type'] = 'task';
|
||||
|
||||
// compose multipart message using PEAR:Mail_Mime
|
||||
$method = $action == 'delete' ? 'CANCEL' : 'REQUEST';
|
||||
$message = $itip->compose_itip_message($task, $method);
|
||||
|
||||
// list existing attendees from the $old task
|
||||
$old_attendees = array();
|
||||
foreach ((array)$old['attendees'] as $attendee) {
|
||||
$old_attendees[] = $attendee['email'];
|
||||
}
|
||||
|
||||
// send to every attendee
|
||||
$sent = 0; $current = array();
|
||||
foreach ((array)$task['attendees'] as $attendee) {
|
||||
$current[] = strtolower($attendee['email']);
|
||||
|
||||
// skip myself for obvious reasons
|
||||
if (!$attendee['email'] || in_array(strtolower($attendee['email']), $emails)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// skip if notification is disabled for this attendee
|
||||
if ($attendee['noreply']) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// which template to use for mail text
|
||||
$is_new = !in_array($attendee['email'], $old_attendees);
|
||||
$bodytext = $is_cancelled ? 'eventcancelmailbody' : ($is_new ? 'invitationmailbody' : 'eventupdatemailbody');
|
||||
$subject = $is_cancelled ? 'eventcancelsubject' : ($is_new ? 'invitationsubject' : ($task['title'] ? 'eventupdatesubject' : 'eventupdatesubjectempty'));
|
||||
|
||||
// finally send the message
|
||||
if ($itip->send_itip_message($task, $method, $attendee, $subject, $bodytext, $message))
|
||||
$sent++;
|
||||
else
|
||||
$sent = -100;
|
||||
}
|
||||
|
||||
// send CANCEL message to removed attendees
|
||||
foreach ((array)$old['attendees'] as $attendee) {
|
||||
if ($attendee['ROLE'] == 'ORGANIZER' || !$attendee['email'] || in_array(strtolower($attendee['email']), $current)) {
|
||||
continue;
|
||||
}
|
||||
|
||||
$vevent = $old;
|
||||
$vevent['cancelled'] = $is_cancelled;
|
||||
$vevent['attendees'] = array($attendee);
|
||||
$vevent['comment'] = $comment;
|
||||
|
||||
if ($itip->send_itip_message($vevent, 'CANCEL', $attendee, 'eventcancelsubject', 'eventcancelmailbody'))
|
||||
$sent++;
|
||||
else
|
||||
$sent = -100;
|
||||
}
|
||||
|
||||
return $sent;
|
||||
}
|
||||
|
||||
/**
|
||||
* Compare two task objects and return differing properties
|
||||
*
|
||||
* @param array Event A
|
||||
* @param array Event B
|
||||
* @return array List of differing task properties
|
||||
*/
|
||||
public static function task_diff($a, $b)
|
||||
{
|
||||
$diff = array();
|
||||
$ignore = array('changed' => 1, 'attachments' => 1);
|
||||
|
||||
foreach (array_unique(array_merge(array_keys($a), array_keys($b))) as $key) {
|
||||
if (!$ignore[$key] && $a[$key] != $b[$key])
|
||||
$diff[] = $key;
|
||||
}
|
||||
|
||||
// only compare number of attachments
|
||||
if (count($a['attachments']) != count($b['attachments']))
|
||||
$diff[] = 'attachments';
|
||||
|
||||
return $diff;
|
||||
}
|
||||
|
||||
/**
|
||||
* Dispatcher for tasklist actions initiated by the client
|
||||
*/
|
||||
|
|
|
@ -56,6 +56,7 @@ class tasklist_ui
|
|||
|
||||
// copy config to client
|
||||
$this->rc->output->set_env('tasklist_settings', $this->load_settings());
|
||||
$this->rc->output->set_env('identities-selector', $this->identity_select(array('id' => 'edit-identities-list', 'aria-label' => $this->plugin->gettext('roleorganizer'))));
|
||||
|
||||
// initialize attendees autocompletion
|
||||
$this->rc->autocomplete_init();
|
||||
|
@ -91,6 +92,22 @@ class tasklist_ui
|
|||
return $settings;
|
||||
}
|
||||
|
||||
/**
|
||||
* Render a HTML select box for user identity selection
|
||||
*/
|
||||
function identity_select($attrib = array())
|
||||
{
|
||||
$attrib['name'] = 'identity';
|
||||
$select = new html_select($attrib);
|
||||
$identities = $this->rc->user->list_identities();
|
||||
|
||||
foreach ($identities as $ident) {
|
||||
$select->add(format_email_recipient($ident['email'], $ident['name']), $ident['identity_id']);
|
||||
}
|
||||
|
||||
return $select->show(null);
|
||||
}
|
||||
|
||||
/**
|
||||
* Register handler methods for the template engine
|
||||
*/
|
||||
|
@ -200,10 +217,11 @@ class tasklist_ui
|
|||
// enrich list properties with settings from the driver
|
||||
if (!$prop['virtual']) {
|
||||
unset($prop['user_id']);
|
||||
$prop['alarms'] = $this->plugin->driver->alarms;
|
||||
$prop['undelete'] = $this->plugin->driver->undelete;
|
||||
$prop['sortable'] = $this->plugin->driver->sortable;
|
||||
$prop['alarms'] = $this->plugin->driver->alarms;
|
||||
$prop['undelete'] = $this->plugin->driver->undelete;
|
||||
$prop['sortable'] = $this->plugin->driver->sortable;
|
||||
$prop['attachments'] = $this->plugin->driver->attachments;
|
||||
$prop['attendees'] = $this->plugin->driver->attendees;
|
||||
$jsenv[$id] = $prop;
|
||||
}
|
||||
|
||||
|
|
Loading…
Add table
Reference in a new issue