Branch 'dev/task-attendees' - plugins/libcalendaring plugins/tasklist
Thomas Brüderli
bruederli at kolabsys.com
Wed Jul 30 17:42:56 CEST 2014
plugins/libcalendaring/lib/libcalendaring_itip.php | 20 +-
plugins/libcalendaring/libcalendaring.php | 18 +
plugins/libcalendaring/libvcalendar.php | 2
plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php | 16 -
plugins/tasklist/localization/en_US.inc | 35 +--
plugins/tasklist/skins/larry/tasklist.css | 23 +-
plugins/tasklist/skins/larry/templates/mainview.html | 12 -
plugins/tasklist/skins/larry/templates/taskedit.html | 4
plugins/tasklist/tasklist.js | 147 +++++----------
plugins/tasklist/tasklist.php | 121 ++++++++++--
plugins/tasklist/tasklist_ui.php | 7
11 files changed, 243 insertions(+), 162 deletions(-)
New commits:
commit b3c5acd66a088248c5634bdb14e5faa3e2011cf4
Author: Thomas Bruederli <bruederli at kolabsys.com>
Date: Wed Jul 30 17:40:53 2014 +0200
- Fix task attendees and organizer setting and display
- Make basic iTip exchange for task assignments work
- Improve wording for task assignments
diff --git a/plugins/libcalendaring/lib/libcalendaring_itip.php b/plugins/libcalendaring/lib/libcalendaring_itip.php
index a6fca9c..ece0e48 100644
--- a/plugins/libcalendaring/lib/libcalendaring_itip.php
+++ b/plugins/libcalendaring/lib/libcalendaring_itip.php
@@ -213,6 +213,14 @@ class libcalendaring_itip
$event['attendees'] = $reply_attendees;
}
}
+ // set RSVP=TRUE for every attendee if not set
+ else if ($method == 'REQUEST') {
+ foreach ($event['attendees'] as $i => $attendee) {
+ if (!isset($attendee['rsvp'])) {
+ $event['attendees'][$i]['rsvp']= true;
+ }
+ }
+ }
// compose multipart message using PEAR:Mail_Mime
$message = new Mail_mime("\r\n");
@@ -532,15 +540,21 @@ class libcalendaring_itip
}
/**
- * Render event details in a table
+ * Render event/task details in a table
*/
function itip_object_details_table($event, $title)
{
$table = new html_table(array('cols' => 2, 'border' => 0, 'class' => 'calendar-eventdetails'));
$table->add('ititle', $title);
$table->add('title', Q($event['title']));
- $table->add('label', $this->plugin->gettext('date'), $this->domain);
- $table->add('date', Q($this->lib->event_date_text($event)));
+ if ($event['start'] && $event['end']) {
+ $table->add('label', $this->plugin->gettext('date'), $this->domain);
+ $table->add('date', Q($this->lib->event_date_text($event)));
+ }
+ else if ($event['due'] && $event['_type'] == 'task') {
+ $table->add('label', $this->plugin->gettext('date'), $this->domain);
+ $table->add('date', Q($this->lib->event_date_text($event)));
+ }
if ($event['location']) {
$table->add('label', $this->plugin->gettext('location'), $this->domain);
$table->add('location', Q($event['location']));
diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php
index 5a1a8b0..36fc287 100644
--- a/plugins/libcalendaring/libcalendaring.php
+++ b/plugins/libcalendaring/libcalendaring.php
@@ -247,11 +247,25 @@ class libcalendaring extends rcube_plugin
*/
public function event_date_text($event, $tzinfo = false)
{
- $fromto = '';
+ $fromto = '--';
+
+ // handle task objects
+ if ($event['_type'] == 'task' && is_object($event['due'])) {
+ $date_format = $event['due']->_dateonly ? self::to_php_date_format($this->rc->config->get('calendar_date_format', $this->defaults['calendar_date_format'])) : null;
+ $fromto = $this->rc->format_date($event['due'], $date_format, false);
+
+ // add timezone information
+ if ($fromto && $tzinfo && ($tzname = $this->timezone->getName())) {
+ $fromto .= ' (' . strtr($tzname, '_', ' ') . ')';
+ }
+
+ return $fromto;
+ }
// abort if no valid event dates are given
- if (!is_object($event['start']) || !is_a($event['start'], 'DateTime') || !is_object($event['end']) || !is_a($event['end'], 'DateTime'))
+ if (!is_object($event['start']) || !is_a($event['start'], 'DateTime') || !is_object($event['end']) || !is_a($event['end'], 'DateTime')) {
return $fromto;
+ }
$duration = $event['start']->diff($event['end'])->format('s');
diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php
index 855e074..a89cec2 100644
--- a/plugins/libcalendaring/libvcalendar.php
+++ b/plugins/libcalendaring/libvcalendar.php
@@ -567,7 +567,7 @@ class libvcalendar implements Iterator
}
// make organizer part of the attendees list for compatibility reasons
- if (!empty($event['organizer']) && is_array($event['attendees'])) {
+ if (!empty($event['organizer']) && is_array($event['attendees']) && $event['_type'] == 'event') {
array_unshift($event['attendees'], $event['organizer']);
}
diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
index 624bdd5..6884083 100644
--- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
@@ -774,6 +774,8 @@ class tasklist_kolab_driver extends tasklist_driver
'parent_id' => $record['parent_id'],
'recurrence' => $record['recurrence'],
'attendees' => $record['attendees'],
+ 'organizer' => $record['organizer'],
+ 'sequence' => $record['sequence'],
);
// convert from DateTime to internal date format
@@ -817,8 +819,8 @@ class tasklist_kolab_driver extends tasklist_driver
}
/**
- * Convert the given task record into a data structure that can be passed to kolab_storage backend for saving
- * (opposite of self::_to_rcube_event())
+ * Convert the given task record into a data structure that can be passed to kolab_storage backend for saving
+ * (opposite of self::_to_rcube_event())
*/
private function _from_rcube_task($task, $old = array())
{
@@ -826,14 +828,14 @@ class tasklist_kolab_driver extends tasklist_driver
$object['categories'] = (array)$task['tags'];
if (!empty($task['date'])) {
- $object['due'] = new DateTime($task['date'].' '.$task['time'], $this->plugin->timezone);
+ $object['due'] = rcube_utils::anytodatetime($task['date'].' '.$task['time'], $this->plugin->timezone);
if (empty($task['time']))
$object['due']->_dateonly = true;
unset($object['date']);
}
if (!empty($task['startdate'])) {
- $object['start'] = new DateTime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone);
+ $object['start'] = rcube_utils::anytodatetime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone);
if (empty($task['starttime']))
$object['start']->_dateonly = true;
unset($object['startdate']);
@@ -900,12 +902,6 @@ 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']);
diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc
index f43fbb9..b37faa8 100644
--- a/plugins/tasklist/localization/en_US.inc
+++ b/plugins/tasklist/localization/en_US.inc
@@ -33,6 +33,7 @@ $labels['status-needs-action'] = 'Needs action';
$labels['status-in-process'] = 'In process';
$labels['status-completed'] = 'Completed';
$labels['status-cancelled'] = 'Cancelled';
+$labels['assignedto'] = 'Assigned to';
$labels['all'] = 'All';
$labels['flagged'] = 'Flagged';
@@ -98,35 +99,35 @@ $labels['arialabeltaskselector'] = 'List mode';
$labels['arialabeltasklisting'] = 'Tasks listing';
// attendees
-$labels['attendee'] = 'Participant';
+$labels['attendee'] = 'Assignee';
$labels['role'] = 'Role';
$labels['availability'] = 'Avail.';
$labels['confirmstate'] = 'Status';
-$labels['addattendee'] = 'Add participant';
+$labels['addattendee'] = 'Add assignee';
$labels['roleorganizer'] = 'Organizer';
$labels['rolerequired'] = 'Required';
$labels['roleoptional'] = 'Optional';
$labels['rolechair'] = 'Chair';
-$labels['rolenonparticipant'] = 'Absent';
+$labels['rolenonparticipant'] = 'Observer';
$labels['sendinvitations'] = 'Send invitations';
-$labels['sendnotifications'] = 'Notify participants about modifications';
-$labels['sendcancellation'] = 'Notify participants about task cancellation';
-$labels['invitationsubject'] = 'You\'ve been invited to "$title"';
-$labels['invitationmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with all the task details which you can import to your tasks application.";
-$labels['invitationattendlinks'] = "In case your email client doesn't support iTip requests you can use the following link to either accept or decline this invitation:\n\$url";
-$labels['eventupdatesubject'] = '"$title" has been updated';
-$labels['eventupdatesubjectempty'] = 'A task that concerns you has been updated';
-$labels['eventupdatemailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nPlease find attached an iCalendar file with the updated task details which you can import to your tasks application.";
-$labels['eventcancelsubject'] = '"$title" has been canceled';
-$labels['eventcancelmailbody'] = "*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees\n\nThe task has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated task details.";
+$labels['sendnotifications'] = 'Notify assignees about modifications';
+$labels['sendcancellation'] = 'Notify assignees about task cancellation';
+$labels['invitationsubject'] = 'You\'ve been assigned to "$title"';
+$labels['invitationmailbody'] = "*\$title*\n\nDue: \$date\n\nAssignees: \$attendees\n\nPlease find attached an iCalendar file with all the task details which you can import to your tasks application.";
+$labels['itipupdatesubject'] = '"$title" has been updated';
+$labels['itipupdatesubjectempty'] = 'A task that concerns you has been updated';
+$labels['itipupdatemailbody'] = "*\$title*\n\nDue: \$date\n\nAssignees: \$attendees\n\nPlease find attached an iCalendar file with the updated task details which you can import to your tasks application.";
+$labels['itipcancelsubject'] = '"$title" has been canceled';
+$labels['itipcancelmailbody'] = "*\$title*\n\nDue: \$date\n\nAssignees: \$attendees\n\nThe task has been cancelled by \$organizer.\n\nPlease find attached an iCalendar file with the updated task details.";
+$labels['saveintasklist'] = 'save in ';
// invitation handling (overrides labels from libcalendaring)
$labels['itipobjectnotfound'] = 'The task referred by this message was not found in your tasks list.';
-$labels['itipmailbodyaccepted'] = "\$sender has accepted the assignment to the following task:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
-$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the assignment to the following task:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
-$labels['itipmailbodydeclined'] = "\$sender has declined the assignment to the following task:\n\n*\$title*\n\nWhen: \$date\n\nInvitees: \$attendees";
-$labels['itipmailbodycancel'] = "\$sender has rejected your assignment to the following task:\n\n*\$title*\n\nWhen: \$date";
+$labels['itipmailbodyaccepted'] = "\$sender has accepted the assignment to the following task:\n\n*\$title*\n\nDue: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodytentative'] = "\$sender has tentatively accepted the assignment to the following task:\n\n*\$title*\n\nDue: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodydeclined'] = "\$sender has declined the assignment to the following task:\n\n*\$title*\n\nDue: \$date\n\nInvitees: \$attendees";
+$labels['itipmailbodycancel'] = "\$sender has rejected your assignment to the following task:\n\n*\$title*\n\nDue: \$date";
$labels['itipdeclineevent'] = 'Do you want to decline your assignment to this task?';
$labels['declinedeleteconfirm'] = 'Do you also want to delete this declined task from your tasks list?';
diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css
index bcdf29a..1d891ed 100644
--- a/plugins/tasklist/skins/larry/tasklist.css
+++ b/plugins/tasklist/skins/larry/tasklist.css
@@ -855,6 +855,14 @@ a.morelink:hover {
margin-top: 0.5em;
}
+.edit-attendees-table tbody td {
+ padding: 4px 7px;
+}
+
+.edit-attendees-table tbody tr:last-child td {
+ border-bottom: 0;
+}
+
.edit-attendees-table th.role,
.edit-attendees-table td.role {
width: 9em;
@@ -864,18 +872,19 @@ a.morelink:hover {
.edit-attendees-table td.availability,
.edit-attendees-table th.confirmstate,
.edit-attendees-table td.confirmstate {
- width: 4em;
+ width: 6em;
}
.edit-attendees-table th.options,
.edit-attendees-table td.options {
- width: 16px;
+ width: 24px;
padding: 2px 4px;
+ text-align: right;
}
.edit-attendees-table th.sendmail,
.edit-attendees-table td.sendmail {
- width: 44px;
+ width: 48px;
padding: 2px;
}
@@ -955,7 +964,7 @@ a.morelink:hover {
div.form-section {
position: relative;
margin-top: 0.2em;
- margin-bottom: 0.8em;
+ margin-bottom: 0.5em;
}
.form-section label {
@@ -970,6 +979,10 @@ label.block {
margin-bottom: 0.3em;
}
+#task-description {
+ margin-bottom: 1em;
+}
+
#taskedit-completeness-slider {
display: inline-block;
margin-left: 2em;
@@ -1047,7 +1060,7 @@ label.block {
}
.task-attendees span.organizer {
- background-position: right -80px;
+ background-position: right 100px;
}
#all-task-attendees span.attendee {
diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html
index 1881c76..727d31f 100644
--- a/plugins/tasklist/skins/larry/templates/mainview.html
+++ b/plugins/tasklist/skins/larry/templates/mainview.html
@@ -156,12 +156,16 @@
<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 id="task-attendees" class="form-section task-attendees">
+ <label><roundcube:label name="tasklist.assignedto" /></label>
+ <span class="task-text"></span>
+ </div>
+ <div id="task-organizer" class="form-section task-attendees">
+ <label><roundcube:label name="tasklist.roleorganizer" /></label>
+ <span class="task-text"></span>
</div>
<!--
- <div class="form-section" id="task-partstat">
+ <div id="task-partstat" class="form-section">
<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>
diff --git a/plugins/tasklist/skins/larry/templates/taskedit.html b/plugins/tasklist/skins/larry/templates/taskedit.html
index 554028b..25c5796 100644
--- a/plugins/tasklist/skins/larry/templates/taskedit.html
+++ b/plugins/tasklist/skins/larry/templates/taskedit.html
@@ -81,6 +81,10 @@
</div>
<!-- attendees list (assignments) -->
<div id="taskedit-panel-attendees">
+ <div class="form-section" id="taskedit-organizer">
+ <label for="edit-identities-list"><roundcube:label name="tasklist.roleorganizer" /></label>
+ <roundcube:object name="plugin.identity_select" id="edit-identities-list" />
+ </div>
<h3 id="aria-label-attendeestable" class="voice"><roundcube:label name="tasklist.arialabeleventassignments" /></h3>
<roundcube:object name="plugin.attendees_list" id="edit-attendees-table" class="records-table edit-attendees-table" coltitle="attendee" aria-labelledby="aria-label-attendeestable" />
<roundcube:object name="plugin.attendees_form" id="edit-attendees-form" />
diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js
index 03aed93..1b10773 100644
--- a/plugins/tasklist/tasklist.js
+++ b/plugins/tasklist/tasklist.js
@@ -84,7 +84,6 @@ function rcube_tasklist_ui(settings)
var focused_subclass;
var task_attendees = [];
var attendees_list;
-// var resources_list;
var me = this;
// general datepicker settings
@@ -1349,7 +1348,7 @@ function rcube_tasklist_ui(settings)
};
// check if the current user is an attendee of this task
- var is_attendee = function(task, role, email)
+ var is_attendee = function(task, email, role)
{
var i, attendee, emails = email ? ';' + email.toLowerCase() : settings.identity.emails;
@@ -1366,7 +1365,10 @@ function rcube_tasklist_ui(settings)
// check if the current user is the organizer
var is_organizer = function(task, email)
{
- return is_attendee(task, 'ORGANIZER', email) || !task.id;
+ if (!email) email = task.organizer ? task.organizer.email : null;
+ if (email)
+ return settings.identity.emails.indexOf(';'+email) >= 0;
+ return true;
};
// add the given list of participants
@@ -1421,34 +1423,10 @@ function rcube_tasklist_ui(settings)
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 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 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 (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>';
-
- // 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>';
@@ -1463,18 +1441,15 @@ function rcube_tasklist_ui(settings)
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>' +
+ var html = '<td class="name">' + dispname + '</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>';
+ (data.cutype != 'RESOURCE' ? '<td class="sendmail">' + (readonly || !invbox ? '' : invbox) + '</td>' : '') +
+ '<td class="options">' + (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);
+ .appendTo(attendees_list);
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);
@@ -1483,15 +1458,6 @@ function rcube_tasklist_ui(settings)
$('p.attendees-commentbox')[enabled ? 'show' : 'hide']();
});
- // 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;
};
@@ -1499,15 +1465,8 @@ function rcube_tasklist_ui(settings)
// 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);
-
- if (rcmail.env.tasklist_resources && cutype == 'RESOURCE') {
- task_resources_dialog(mailto);
- }
- else {
- rcmail.redirect(rcmail.url('mail/compose', {_to: mailto}));
- }
+ var mailto = this.href.substr(7);
+ rcmail.command('compose', mailto);
return false;
};
@@ -1547,7 +1506,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();
+ $('#task-attendees, #task-organizer').hide();
var itags = get_inherited_tags(rec);
var taglist = $('#task-tags')[(rec.tags && rec.tags.length || itags.length ? 'show' : 'hide')]().children('.task-text').empty();
@@ -1587,6 +1546,7 @@ function rcube_tasklist_ui(settings)
// list task attendees
if (list.attendees && rec.attendees) {
+ console.log(rec.attendees)
/*
// sort resources to the end
rec.attendees.sort(function(a,b) {
@@ -1595,30 +1555,19 @@ function rcube_tasklist_ui(settings)
return (j - k);
});
*/
- var j, data, dispname, tooltip, organizer = false, rsvp = false, mystatus = null, line, morelink, html = '', overflow = '';
+ var j, data, rsvp = false, mystatus = null, line, morelink, html = '', overflow = '',
+ organizer = is_organizer(rec);
+
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'];
+ if (data.email && settings.identity.emails.indexOf(';'+data.email) >= 0) {
+ mystatus = data.status.toLowerCase();
+ if (data.status == 'NEEDS-ACTION' || data.status == 'TENTATIVE' || data.rsvp)
+ rsvp = mystatus;
+ }
- line = '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + dispname + '</span> ';
+ line = task_attendee_html(data);
if (morelink)
overflow += line;
@@ -1631,7 +1580,7 @@ function rcube_tasklist_ui(settings)
}
}
- if (html && (rec.attendees.length > 1 || !organizer)) {
+ if (html) {
$('#task-attendees').show()
.children('.task-text')
.html(html)
@@ -1667,6 +1616,10 @@ function rcube_tasklist_ui(settings)
$('#task-rsvp a.reply-comment-toggle').show();
$('#task-rsvp .itip-reply-comment textarea').hide().val('');
*/
+
+ if (rec.organizer && !organizer) {
+ $('#task-organizer').show().children('.task-text').html(task_attendee_html(rec.organizer));
+ }
}
// define dialog buttons
@@ -1697,7 +1650,7 @@ function rcube_tasklist_ui(settings)
closeOnEscape: true,
title: rcmail.gettext('taskdetails', 'tasklist'),
open: function() {
- $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
+ $dialog.parent().find('.ui-button:not(.ui-dialog-titlebar-close)').first().focus();
},
close: function() {
$dialog.dialog('destroy').appendTo(document.body);
@@ -1711,6 +1664,24 @@ function rcube_tasklist_ui(settings)
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
}
+ // render HTML code for displaying an attendee record
+ function task_attendee_html(data)
+ {
+ var 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['delegated-to'])
+ tooltip = rcmail.gettext('delegatedto', 'tasklist') + data['delegated-to'];
+ else if (data['delegated-from'])
+ tooltip = rcmail.gettext('delegatedfrom', 'tasklist') + data['delegated-from'];
+
+ return '<span class="attendee ' + String(data.role == 'ORGANIZER' ? 'organizer' : data.status).toLowerCase() + '" title="' + Q(tooltip) + '">' + dispname + '</span> ';
+ }
+
/**
* Opens the dialog to edit a task
*/
@@ -1786,18 +1757,17 @@ function rcube_tasklist_ui(settings)
task_attendees = [];
attendees_list = $('#edit-attendees-table > tbody').html('');
- //resources_list = $('#edit-resources-table > tbody').html('');
$('#edit-attendees-notify')[(notify.checked && allow_invitations ? 'show' : 'hide')]();
$('#edit-localchanges-warning')[(has_attendees(rec) && !(allow_invitations || (rec.owner && is_organizer(rec, rec.owner))) ? 'show' : 'hide')]();
- var load_attendees_tab = function()
- {
+ // attendees (aka assignees)
+ if (list.attendees) {
var j, data, reply_selected = 0;
if (rec.attendees) {
for (j=0; j < rec.attendees.length; j++) {
data = rec.attendees[j];
add_attendee(data, !allow_invitations);
- if (allow_invitations && data.role != 'ORGANIZER' && !data.noreply) {
+ if (allow_invitations && !data.noreply) {
reply_selected++;
}
}
@@ -1812,16 +1782,16 @@ function rcube_tasklist_ui(settings)
// select the correct organizer identity
var identity_id = 0;
$.each(settings.identities, function(i,v) {
- if (organizer && v == organizer.email) {
+ if (rec.organizer && v == rec.organizer.email) {
identity_id = i;
return false;
}
});
- $('#edit-identities-list').val(identity_id);
$('#edit-attendees-form')[(allow_invitations?'show':'hide')]();
-// $('#edit-attendee-schedule')[(tasklist.freebusy?'show':'hide')]();
- };
+ $('#edit-identities-list').val(identity_id);
+ $('#taskedit-organizer')[(organizer ? 'show' : 'hide')]();
+ }
// attachments
rcmail.enable_command('remove-attachment', list.editable);
@@ -1904,15 +1874,9 @@ function rcube_tasklist_ui(settings)
if (!data.tags.length)
data.tags = '';
- // read attendee roles
- $('select.edit-attendee-role').each(function(i, elem) {
- if (data.attendees[i]) {
- data.attendees[i].role = $(elem).val();
- }
- });
-
if (organizer) {
data._identity = $('#edit-identities-list option:selected').val();
+ delete data.organizer;
}
// don't submit attendees if only myself is added as organizer
@@ -1977,9 +1941,6 @@ function rcube_tasklist_ui(settings)
// set dialog size according to content
me.dialog_resize($dialog.get(0), $dialog.height(), 580);
-
- if (list.attendees)
- window.setTimeout(load_attendees_tab, 1);
}
/**
diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php
index 76ef251..0757c63 100644
--- a/plugins/tasklist/tasklist.php
+++ b/plugins/tasklist/tasklist.php
@@ -334,7 +334,7 @@ class tasklist extends rcube_plugin
$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)) {
+ if (!$oldrec || $action == 'delete' || self::task_diff($task, $oldrec)) {
$sent = $this->notify_attendees($task, $oldrec, $action, $rec['_comment']);
if ($sent > 0)
$this->rc->output->show_message('tasklist.itipsendsuccess', 'confirmation');
@@ -366,10 +366,7 @@ class tasklist extends rcube_plugin
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'));
-// }
+ $this->itip->set_rsvp_actions(array('accepted','declined'));
}
return $this->itip;
@@ -520,6 +517,11 @@ class tasklist extends rcube_plugin
$rec['attachments'] = $attachments;
+ // set organizer from identity selector
+ if (isset($rec['_identity']) && ($identity = $this->rc->user->get_identity($rec['_identity']))) {
+ $rec['organizer'] = array('name' => $identity['name'], 'email' => $identity['email']);
+ }
+
if (is_numeric($rec['id']) && $rec['id'] < 0)
unset($rec['id']);
@@ -646,7 +648,8 @@ class tasklist extends rcube_plugin
// compose multipart message using PEAR:Mail_Mime
$method = $action == 'delete' ? 'CANCEL' : 'REQUEST';
- $message = $itip->compose_itip_message($task, $method);
+ $object = $this->to_libcal($task);
+ $message = $itip->compose_itip_message($object, $method);
// list existing attendees from the $old task
$old_attendees = array();
@@ -671,11 +674,11 @@ class tasklist extends rcube_plugin
// 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'));
+ $bodytext = $is_cancelled ? 'itipcancelmailbody' : ($is_new ? 'invitationmailbody' : 'itipupdatemailbody');
+ $subject = $is_cancelled ? 'itipcancelsubject' : ($is_new ? 'invitationsubject' : ($task['title'] ? 'itipupdatesubject' : 'itipupdatesubjectempty'));
// finally send the message
- if ($itip->send_itip_message($task, $method, $attendee, $subject, $bodytext, $message))
+ if ($itip->send_itip_message($object, $method, $attendee, $subject, $bodytext, $message))
$sent++;
else
$sent = -100;
@@ -683,16 +686,16 @@ class tasklist extends rcube_plugin
// send CANCEL message to removed attendees
foreach ((array)$old['attendees'] as $attendee) {
- if ($attendee['ROLE'] == 'ORGANIZER' || !$attendee['email'] || in_array(strtolower($attendee['email']), $current)) {
+ if (!$attendee['email'] || in_array(strtolower($attendee['email']), $current)) {
continue;
}
- $vevent = $old;
- $vevent['cancelled'] = $is_cancelled;
- $vevent['attendees'] = array($attendee);
- $vevent['comment'] = $comment;
+ $vtodo = $this->to_libcal($old);
+ $vtodo['cancelled'] = $is_cancelled;
+ $vtodo['attendees'] = array($attendee);
+ $vtodo['comment'] = $comment;
- if ($itip->send_itip_message($vevent, 'CANCEL', $attendee, 'eventcancelsubject', 'eventcancelmailbody'))
+ if ($itip->send_itip_message($vtodo, 'CANCEL', $attendee, 'itipcancelsubject', 'itipcancelmailbody'))
$sent++;
else
$sent = -100;
@@ -1393,6 +1396,8 @@ class tasklist extends rcube_plugin
// successfully parsed events?
if (!empty($tasks) && ($task = $tasks[$index])) {
+ $task = $this->from_ical($task);
+
// store the message's sender address for comparisons
$task['_sender'] = preg_match('/([a-z0-9][a-z0-9\-\.\+\_]*@[^&@"\'.][^@&"\']*\\.([^\\x00-\\x40\\x5b-\\x60\\x7b-\\x7f]{2,}|xn--[a-z0-9]{2,}))/', $headers->from, $m) ? $m[1] : '';
$askt['_sender_utf'] = rcube_idn_to_utf8($task['_sender']);
@@ -1496,6 +1501,7 @@ class tasklist extends rcube_plugin
foreach ($tasks as $task) {
// save to tasklist
if ($list && $list['editable'] && $task['_type'] == 'task') {
+ $task = $this->from_ical($task);
$task['list'] = $list['id'];
if (!$this->driver->get_task($task['uid'])) {
@@ -1555,14 +1561,11 @@ class tasklist extends rcube_plugin
// update my attendee status according to submitted method
if (!empty($status)) {
- $organizer = null;
+ $organizer = $task['organizer'];
$emails = $this->lib->get_user_emails();
foreach ($task['attendees'] as $i => $attendee) {
- if ($attendee['role'] == 'ORGANIZER') {
- $organizer = $attendee;
- }
- else if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
+ if ($attendee['email'] && in_array(strtolower($attendee['email']), $emails)) {
$metadata['attendee'] = $attendee['email'];
$metadata['rsvp'] = $attendee['role'] != 'NON-PARTICIPANT';
$reply_sender = $attendee['email'];
@@ -1714,7 +1717,7 @@ class tasklist extends rcube_plugin
$itip = $this->load_itip();
$itip->set_sender_email($reply_sender);
- if ($itip->send_itip_message($task, 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
+ if ($itip->send_itip_message($this->to_libcal($task), 'REPLY', $organizer, 'itipsubject' . $status, 'itipmailbody' . $status))
$this->rc->output->command('display_message', $this->gettext(array('name' => 'sentresponseto', 'vars' => array('mailto' => $organizer['name'] ? $organizer['name'] : $organizer['email']))), 'confirmation');
else
$this->rc->output->command('display_message', $this->gettext('itipresponseerror'), 'error');
@@ -1729,7 +1732,7 @@ class tasklist extends rcube_plugin
/**
* Handler for calendar/itip-status requests
*/
- function task_itip_status()
+ public function task_itip_status()
{
$data = rcube_utils::get_input_value('data', rcube_utils::INPUT_POST, true);
@@ -1768,7 +1771,7 @@ class tasklist extends rcube_plugin
/**
* Handler for calendar/itip-remove requests
*/
- function task_itip_remove()
+ public function task_itip_remove()
{
$success = false;
$uid = rcube_utils::get_input_value('uid', rcube_utils::INPUT_POST);
@@ -1798,6 +1801,78 @@ class tasklist extends rcube_plugin
}
/**
+ * Map task properties for ical exprort using libcalendaring
+ */
+ public function to_libcal($task)
+ {
+ $object = $task;
+ $object['categories'] = (array)$task['tags'];
+
+ // convert to datetime objects
+ if (!empty($task['date'])) {
+ $object['due'] = rcube_utils::anytodatetime($task['date'].' '.$task['time'], $this->timezone);
+ if (empty($task['time']))
+ $object['due']->_dateonly = true;
+ unset($object['date']);
+ }
+
+ if (!empty($task['startdate'])) {
+ $object['start'] = rcube_utils::anytodatetime($task['startdate'].' '.$task['starttime'], $this->timezone);
+ if (empty($task['starttime']))
+ $object['start']->_dateonly = true;
+ unset($object['startdate']);
+ }
+
+ $object['complete'] = $task['complete'] * 100;
+ if ($task['complete'] == 1.0 && empty($task['complete'])) {
+ $object['status'] = 'COMPLETED';
+ }
+
+ if ($task['flagged']) {
+ $object['priority'] = 1;
+ }
+ else if (!$task['priority']) {
+ $object['priority'] = 0;
+ }
+
+ return $object;
+ }
+
+ /**
+ * Convert task properties from ical parser to the internal format
+ */
+ public function from_ical($vtodo)
+ {
+ $task = $vtodo;
+
+ $task['tags'] = array_filter((array)$vtodo['categories']);
+ $task['flagged'] = $vtodo['priority'] == 1;
+ $task['complete'] = floatval($vtodo['complete'] / 100);
+
+ // convert from DateTime to internal date format
+ if (is_a($vtodo['due'], 'DateTime')) {
+ $due = $this->lib->adjust_timezone($vtodo['due']);
+ $task['date'] = $due->format('Y-m-d');
+ if (!$vtodo['due']->_dateonly)
+ $task['time'] = $due->format('H:i');
+ }
+ // convert from DateTime to internal date format
+ if (is_a($vtodo['start'], 'DateTime')) {
+ $start = $this->lib->adjust_timezone($vtodo['start']);
+ $task['startdate'] = $start->format('Y-m-d');
+ if (!$vtodo['start']->_dateonly)
+ $task['starttime'] = $start->format('H:i');
+ }
+ if (is_a($vtodo['dtstamp'], 'DateTime')) {
+ $task['changed'] = $vtodo['dtstamp'];
+ }
+
+ unset($task['categories'], $task['due'], $task['start'], $task['dtstamp']);
+
+ return $task;
+ }
+
+ /**
* Handler for user_delete plugin hook
*/
public function user_delete($args)
diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php
index 5fc0a20..21b322e 100644
--- a/plugins/tasklist/tasklist_ui.php
+++ b/plugins/tasklist/tasklist_ui.php
@@ -56,7 +56,6 @@ 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();
@@ -71,7 +70,7 @@ class tasklist_ui
{
$settings = array();
- //$settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', $this->defaults['calendar_allow_invite_shared']);
+ $settings['invite_shared'] = (int)$this->rc->config->get('calendar_allow_invite_shared', 0);
// get user identity to create default attendee
foreach ($this->rc->user->list_identities() as $rec) {
@@ -128,6 +127,7 @@ class tasklist_ui
$this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
$this->plugin->register_handler('plugin.attendees_list', array($this, 'attendees_list'));
$this->plugin->register_handler('plugin.attendees_form', array($this, 'attendees_form'));
+ $this->plugin->register_handler('plugin.identity_select', array($this, 'identity_select'));
$this->plugin->register_handler('plugin.edit_attendees_notify', array($this, 'edit_attendees_notify'));
$this->plugin->include_script('jquery.tagedit.js');
@@ -438,9 +438,8 @@ class tasklist_ui
$invite = new html_checkbox(array('value' => 1, 'id' => 'edit-attendees-invite'));
$table = new html_table(array('cols' => 4 + intval($invitations), 'border' => 0, 'cellpadding' => 0, 'class' => 'rectable'));
- $table->add_header('role', $this->plugin->gettext('role'));
+// $table->add_header('role', $this->plugin->gettext('role'));
$table->add_header('name', $this->plugin->gettext($attrib['coltitle'] ?: 'attendee'));
-// $table->add_header('availability', $this->plugin->gettext('availability'));
$table->add_header('confirmstate', $this->plugin->gettext('confirmstate'));
if ($invitations) {
$table->add_header(array('class' => 'sendmail', 'title' => $this->plugin->gettext('sendinvitations')),
More information about the commits
mailing list