Projects
Kolab:3.4
roundcubemail-plugins-kolab
Log In
Username
Password
Overview
Repositories
Revisions
Requests
Users
Attributes
Meta
Expand all
Collapse all
Changes of Revision 40
View file
roundcubemail-plugins-kolab.spec
Changed
@@ -24,7 +24,7 @@ Name: roundcubemail-plugins-kolab Version: 3.2 -Release: 0.1.dev20140214.git351f7d%{?dist} +Release: 0.2.dev20140214.git351f7d%{?dist} Summary: Kolab Groupware plugins for Roundcube Webmail Group: Applications/Internet @@ -35,6 +35,9 @@ Source0: http://git.kolab.org/%{name}/snapshot/%{name}-%{version}.tar.gz Source1: kolab_logo.png +Patch0: roundcubemail-plugins-kolab-3.2-2863-kolab-storage-cache.patch +Patch1: roundcubemail-plugins-kolab-3.2-2867-mysql-initial.patch + BuildRoot: %(mktemp -ud %{_tmppath}/%{name}-%{version}-%{release}-XXXXXX) BuildArch: noarch @@ -50,6 +53,8 @@ %prep %setup -q +%patch0 -p1 +%patch1 -p1 %build @@ -210,6 +215,10 @@ %attr(0770,root,%{httpd_group}) %{roundcube_lib}/plugins/odfviewer %changelog +* Tue Feb 25 2014 Daniel Hoffend <dh@dotlan.net> - 3.2-0.2.git +- Apply patch for #2867 oudated mysql initial +- Apply patch for #2863 kolab_storage_cache::save() + * Fri Feb 14 2014 Jeroen van Meeuwen <vanmeeuwen@kolabsys.com> - 3.2-0.1.git - Upstream snapshot with birthday calendar for Kolab 3.2
View file
roundcubemail-plugins-kolab-3.2-2863-kolab-storage-cache.patch
Added
@@ -0,0 +1,13 @@ +diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php +index 925f4fe..9c1368f 100644 +--- a/plugins/libkolab/lib/kolab_storage_cache.php ++++ b/plugins/libkolab/lib/kolab_storage_cache.php +@@ -318,7 +318,7 @@ class kolab_storage_cache + * @param mixed Hash array with object properties to save or false to delete the cache entry + * @param int Optional old message UID (for update) + */ +- public function save($msguid, $object, $olduid) ++ public function save($msguid, $object, $olduid = null) + { + // write to cache + if ($this->ready) {
View file
roundcubemail-plugins-kolab-3.2-2867-mysql-initial.patch
Added
@@ -0,0 +1,21 @@ +diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql +index 40b8631..89b7244 100644 +--- a/plugins/libkolab/SQL/mysql.initial.sql ++++ b/plugins/libkolab/SQL/mysql.initial.sql +@@ -34,6 +34,10 @@ CREATE TABLE `kolab_cache_contact` ( + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, ++ `name` VARCHAR(255) NOT NULL, ++ `firstname` VARCHAR(255) NOT NULL, ++ `surname` VARCHAR(255) NOT NULL, ++ `email` VARCHAR(255) NOT NULL, + CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), +@@ -172,4 +176,4 @@ CREATE TABLE `kolab_cache_freebusy` ( + ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + + +-INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013121100'); ++INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2014021000');
View file
debian.changelog
Changed
@@ -1,3 +1,9 @@ +roundcubemail-plugins-kolab (1:3.2~dev20140214-0~kolab3) unstable; urgency=low + + * added patches to fix the outdated mysql-inital file and fix a kolab_storage_cache::save() bug + + -- Daniel Hoffend <dh@dotlan.net> Tue, 25 Feb 2014 11:40:12 +0200 + roundcubemail-plugins-kolab (1:3.2~dev20140214-0~kolab2) unstable; urgency=low * added bin/updatedb.sh command to the postinst section.
View file
debian.tar.gz/debian.series
Deleted
@@ -1,5 +0,0 @@ -roundcubemail-plugins-kolab-3.1.6-task-enhancements.patch -p1 -roundcubemail-plugins-kolab-3.1.6-2353-ics-import-efficiency.patch -p1 -roundcubemail-plugins-kolab-3.1.6-2463-all-day-events-are-off-one-day.patch -p1 -roundcubemail-plugins-kolab-3.1.6-fix-replacement-of-alarm-values.patch -p1 -
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-2353-ics-import-efficiency.patch
Deleted
@@ -1,744 +0,0 @@ -diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php -index 685745d..e8e5ab2 100644 ---- a/plugins/calendar/calendar.php -+++ b/plugins/calendar/calendar.php -@@ -1003,7 +1003,7 @@ class calendar extends rcube_plugin - rcube_upload_progress(); - } - -- $calendar = get_input_value('calendar', RCUBE_INPUT_GPC); -+ @set_time_limit(0); - - // process uploaded file if there is no error - $err = $_FILES['_data']['error']; -@@ -1011,22 +1011,23 @@ class calendar extends rcube_plugin - if (!$err && $_FILES['_data']['tmp_name']) { - $calendar = get_input_value('calendar', RCUBE_INPUT_GPC); - $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0; -- $count = $errors = 0; -- -- try { -- $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name'], 'UTF-8', true); -- } -- catch (Exception $e) { -- $errors = 1; -- $msg = $e->getMessage(); -- $events = array(); -- } -+ $user_email = $this->rc->user->get_username(); -+ -+ $ical = $this->get_ical(); -+ $errors = !$ical->fopen($_FILES['_data']['tmp_name']); -+ $count = $i = 0; -+ foreach ($ical as $event) { -+ // keep the browser connection alive on long import jobs -+ if (++$i > 100 && $i % 100 == 0) { -+ echo "<!-- -->"; -+ ob_flush(); -+ } - -- foreach ($events as $event) { - // TODO: correctly handle recurring events which start before $rangestart - if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart))) - continue; - -+ $event['_owner'] = $user_email; - $event['calendar'] = $calendar; - if ($this->driver->new_event($event)) { - $count++; -diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js -index d5a4308..3ef1cb1 100644 ---- a/plugins/calendar/calendar_ui.js -+++ b/plugins/calendar/calendar_ui.js -@@ -1988,10 +1988,15 @@ function rcube_calendar_ui(settings) - rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error'); - }); - -- // display upload indicator -+ // display upload indicator (with extended timeout) -+ var timeout = rcmail.env.request_timeout; -+ rcmail.env.request_timeout = 600; - me.import_succeeded = null; - me.saving_lock = rcmail.set_busy(true, 'uploading'); - $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable'); -+ -+ // restore settings -+ rcmail.env.request_timeout = timeout; - } - }; - -diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php -index 91c8d0c..bd42f56 100644 ---- a/plugins/calendar/drivers/kolab/kolab_calendar.php -+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php -@@ -317,7 +317,7 @@ class kolab_calendar - } - else { - $event['id'] = $event['uid']; -- $this->events[$event['uid']] = $this->_to_rcube_event($object); -+ $this->events = array($event['uid'] => $this->_to_rcube_event($object)); - } - - return $saved; -diff --git a/plugins/libcalendaring/libvcalendar.php b/plugins/libcalendaring/libvcalendar.php -index 5e12d50..aa6dc4a 100644 ---- a/plugins/libcalendaring/libvcalendar.php -+++ b/plugins/libcalendaring/libvcalendar.php -@@ -37,13 +37,17 @@ if (!class_exists('\Sabre\VObject\Reader')) { - * and place the lib files in this plugin's lib directory - * - */ --class libvcalendar -+class libvcalendar implements Iterator - { - private $timezone; - private $attach_uri = null; - private $prodid = '-//Roundcube//Roundcube libcalendaring//Sabre//Sabre VObject//EN'; - private $type_component_map = array('event' => 'VEVENT', 'task' => 'VTODO'); - private $attendee_keymap = array('name' => 'CN', 'status' => 'PARTSTAT', 'role' => 'ROLE', 'cutype' => 'CUTYPE', 'rsvp' => 'RSVP'); -+ private $iteratorkey = 0; -+ private $charset; -+ private $forward_exceptions; -+ private $fp; - - public $method; - public $agent = ''; -@@ -98,6 +102,13 @@ class libvcalendar - { - $this->method = ''; - $this->objects = array(); -+ $this->freebusy = array(); -+ $this->iteratorkey = 0; -+ -+ if ($this->fp) { -+ fclose($this->fp); -+ $this->fp = null; -+ } - } - - /** -@@ -108,25 +119,36 @@ class libvcalendar - * @param boolean True if parsing exceptions should be forwarded to the caller - * @return array List of events extracted from the input - */ -- public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false) -+ public function import($vcal, $charset = 'UTF-8', $forward_exceptions = false, $memcheck = true) - { - // TODO: convert charset to UTF-8 if other - - try { -+ // estimate the memory usage and try to avoid fatal errors when allowed memory gets exhausted -+ if ($memcheck) { -+ $count = substr_count($vcal, 'BEGIN:VEVENT'); -+ $expected_memory = $count * 70*1024; // assume ~ 70K per event (empirically determined) -+ -+ if (!rcube_utils::mem_check($expected_memory)) { -+ throw new Exception("iCal file too big"); -+ } -+ } -+ - $vobject = VObject\Reader::read($vcal, VObject\Reader::OPTION_FORGIVING | VObject\Reader::OPTION_IGNORE_INVALID_LINES); - if ($vobject) - return $this->import_from_vobject($vobject); - } - catch (Exception $e) { -- rcube::raise_error(array( -- 'code' => 600, 'type' => 'php', -- 'file' => __FILE__, 'line' => __LINE__, -- 'message' => "iCal data parse error: " . $e->getMessage()), -- true, false); -- - if ($forward_exceptions) { - throw $e; - } -+ else { -+ rcube::raise_error(array( -+ 'code' => 600, 'type' => 'php', -+ 'file' => __FILE__, 'line' => __LINE__, -+ 'message' => "iCal data parse error: " . $e->getMessage()), -+ true, false); -+ } - } - - return array(); -@@ -142,17 +164,118 @@ class libvcalendar - */ - public function import_from_file($filepath, $charset = 'UTF-8', $forward_exceptions = false) - { -- $this->objects = array(); -- $fp = fopen($filepath, 'r'); -+ if ($this->fopen($filepath, $charset, $forward_exceptions)) { -+ while ($this->_parse_next(false)) { -+ // nop -+ } -+ -+ fclose($this->fp); -+ $this->fp = null; -+ } -+ -+ return $this->objects; -+ } -+ -+ /** -+ * Open a file to read iCalendar events sequentially -+ * -+ * @param string File path to read from -+ * @param string Input charset (from envelope) -+ * @param boolean True if parsing exceptions should be forwarded to the caller -+ * @return boolean True if file contents are considered valid -+ */ -+ public function fopen($filepath, $charset = 'UTF-8', $forward_exceptions = false) -+ { -+ $this->reset(); -+ -+ // just to be sure... -+ @ini_set('auto_detect_line_endings', true); -+ -+ $this->charset = $charset; -+ $this->forward_exceptions = $forward_exceptions; -+ $this->fp = fopen($filepath, 'r'); - - // check file content first -- $begin = fread($fp, 1024); -+ $begin = fread($this->fp, 1024); - if (!preg_match('/BEGIN:VCALENDAR/i', $begin)) { -- return $this->objects; -+ return false; -+ } -+ -+ // read vcalendar header (with timezone defintion) -+ $this->vhead = ''; -+ fseek($this->fp, 0); -+ while (($line = fgets($this->fp, 512)) !== false) { -+ if (preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $line)) -+ break; -+ $this->vhead .= $line; -+ } -+ fseek($this->fp, -strlen($line), SEEK_CUR); -+ -+ return $this->_parse_next(); -+ } -+ -+ /** -+ * Parse the next event/todo/freebusy object from the input file -+ */ -+ private function _parse_next($reset = true) -+ { -+ if ($reset) { -+ $this->iteratorkey = 0; -+ $this->objects = array(); -+ $this->freebusy = array(); -+ } -+ -+ $next = $this->_next_component(); -+ $buffer = $next; -+ -+ // load the next component(s) too, as they could contain recurrence exceptions -+ while (preg_match('/(RRULE|RECURRENCE-ID)[:;]/i', $next)) { -+ $next = $this->_next_component(); -+ $buffer .= $next; -+ } -+ -+ // parse the vevent block surrounded with the vcalendar heading -+ if (strlen($buffer) && preg_match('/BEGIN:(VEVENT|VTODO|VFREEBUSY)/i', $buffer)) { -+ try { -+ $this->import($this->vhead . $buffer . "END:VCALENDAR", $this->charset, true, false); -+ } -+ catch (Exception $e) { -+ if ($this->forward_exceptions) { -+ throw new VObject\ParseException($e->getMessage() . " in\n" . $buffer); -+ } -+ else { -+ // write the failing section to error log -+ rcube::raise_error(array( -+ 'code' => 600, 'type' => 'php', -+ 'file' => __FILE__, 'line' => __LINE__, -+ 'message' => $e->getMessage() . " in\n" . $buffer), -+ true, false); -+ } -+ -+ // advance to next -+ return $this->_parse_next($reset); -+ } -+ -+ return count($this->objects) > 0; - } -- fclose($fp); - -- return $this->import(file_get_contents($filepath), $charset, $forward_exceptions); -+ return false; -+ } -+ -+ /** -+ * Helper method to read the next calendar component from the file -+ */ -+ private function _next_component() -+ { -+ $buffer = ''; -+ while (($line = fgets($this->fp, 1024)) !== false) { -+ $buffer .= $line; -+ if (preg_match('/END:(VEVENT|VTODO|VFREEBUSY)/i', $line)) { -+ break; -+ } -+ } -+ -+ return $buffer; - } - - /** -@@ -163,7 +286,7 @@ class libvcalendar - */ - public function import_from_vobject($vobject) - { -- $this->objects = $this->freebusy = $seen = array(); -+ $seen = array(); - - if ($vobject->name == 'VCALENDAR') { - $this->method = strval($vobject->METHOD); -@@ -847,6 +970,41 @@ class libvcalendar - } - } - -+ -+ /*** Implement PHP 5 Iterator interface to make foreach work ***/ -+ -+ function current() -+ { -+ return $this->objects[$this->iteratorkey]; -+ } -+ -+ function key() -+ { -+ return $this->iteratorkey; -+ } -+ -+ function next() -+ { -+ $this->iteratorkey++; -+ -+ // read next chunk if we're reading from a file -+ if (!$this->objects[$this->iteratorkey] && $this->fp) { -+ $this->_parse_next(true); -+ } -+ -+ return $this->valid(); -+ } -+ -+ function rewind() -+ { -+ $this->iteratorkey = 0; -+ } -+ -+ function valid() -+ { -+ return !empty($this->objects[$this->iteratorkey]); -+ } -+ - } - - -diff --git a/plugins/libcalendaring/tests/libvcalendar.php b/plugins/libcalendaring/tests/libvcalendar.php -index 92a22a3..28be822 100644 ---- a/plugins/libcalendaring/tests/libvcalendar.php -+++ b/plugins/libcalendaring/tests/libvcalendar.php -@@ -167,6 +167,22 @@ class libvcalendar_test extends PHPUnit_Framework_TestCase - } - - /** -+ * @depends test_import_from_file -+ */ -+ function test_attachment() -+ { -+ $ical = new libvcalendar(); -+ -+ $events = $ical->import_from_file(__DIR__ . '/resources/attachment.ics', 'UTF-8'); -+ $event = $events[0]; -+ -+ $this->assertEquals(2, count($events)); -+ $this->assertEquals(1, count($event['attachments'])); -+ $this->assertEquals('image/png', $event['attachments'][0]['mimetype']); -+ $this->assertEquals('500px-Opensource.svg.png', $event['attachments'][0]['name']); -+ } -+ -+ /** - * @depends test_import - */ - function test_freebusy() -diff --git a/plugins/libcalendaring/tests/resources/attachment.ics b/plugins/libcalendaring/tests/resources/attachment.ics -new file mode 100644 -index 0000000..827cc76 ---- /dev/null -+++ b/plugins/libcalendaring/tests/resources/attachment.ics -@@ -0,0 +1,344 @@ -+BEGIN:VCALENDAR -+VERSION:2.0 -+PRODID:-//Roundcube//Roundcube libcalendaring 1.0-git//Sabre//Sabre VObject -+ 2.1.3//EN -+CALSCALE:GREGORIAN -+BEGIN:VEVENT -+UID:93B331265CF47061F888BC0A3F0FBD7F-FCBB6C4091F28CA0 -+DTSTAMP;VALUE=DATE-TIME:20131017T084408Z -+CREATED;VALUE=DATE-TIME:20131017T083610Z -+LAST-MODIFIED;VALUE=DATE-TIME:20131017T083610Z -+DTSTART;VALUE=DATE-TIME;TZID=Europe/Zurich:20131017T110000 -+DTEND;VALUE=DATE-TIME;TZID=Europe/Zurich:20131017T120000 -+SUMMARY:Event with attachment -+LOCATION:Test lab -+TRANSP:OPAQUE -+CLASS:PUBLIC -+ORGANIZER;CN=Bruederli:mailto:thomas.bruederli@example.org -+ATTACH;VALUE=BINARY;ENCODING=BASE64;FMTTYPE=image/png;X-LABEL=500px-Opensou -+ rce.svg.png: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 -+END:VEVENT -+ -+BEGIN:VEVENT -+CREATED:20130718T073555Z -+UID:B968B885-08FB-40E5-B89E-6DA05F26AA79 -+URL;VALUE=URI:http://en.wikipedia.org/wiki/Swiss_National_Day -+DTEND;VALUE=DATE:20130802 -+TRANSP:TRANSPARENT -+SUMMARY:Swiss National Day -+DTSTART;VALUE=DATE:20130801 -+DTSTAMP:20130718T074538Z -+SEQUENCE:2 -+DESCRIPTION:German: Schweizer Bundesfeier\nFrench: FĂȘte nationale Suisse -+ \nItalian: Festa nazionale svizzera\nRomansh: Fiasta naziunala Svizra -+END:VEVENT -+ -+END:VCALENDAR -diff --git a/plugins/libkolab/lib/kolab_format_xcal.php b/plugins/libkolab/lib/kolab_format_xcal.php -index 284a068..500dfa2 100644 ---- a/plugins/libkolab/lib/kolab_format_xcal.php -+++ b/plugins/libkolab/lib/kolab_format_xcal.php -@@ -373,7 +373,7 @@ abstract class kolab_format_xcal extends kolab_format - if ($object['alarms']) { - list($offset, $type) = explode(":", $object['alarms']); - -- if ($type == 'EMAIL') { // email alarms implicitly go to event owner -+ if ($type == 'EMAIL' && !empty($object['_owner'])) { // email alarms implicitly go to event owner - $recipients = new vectorcontactref; - $recipients->push(new ContactReference(ContactReference::EmailReference, $object['_owner'])); - $alarm = new Alarm($object['title'], strval($object['description']), $recipients); -diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php -index a4fd34c..54b4e7a 100644 ---- a/plugins/libkolab/lib/kolab_storage_cache.php -+++ b/plugins/libkolab/lib/kolab_storage_cache.php -@@ -318,8 +318,8 @@ class kolab_storage_cache - } - - // keep a copy in memory for fast access -- $this->objects[$msguid] = $object; -- $this->uid2msg[$object['uid']] = $msguid; -+ $this->objects = array($msguid => $object); -+ $this->uid2msg = array($object['uid'] => $msguid); - } - -
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-2463-all-day-events-are-off-one-day.patch
Deleted
@@ -1,51 +0,0 @@ -commit 5924783b18b5c423e58b30730bfdee00ca6c85b0 -Author: Thomas Bruederli <bruederli@kolabsys.com> -Date: Fri Nov 1 11:23:42 2013 +0100 - - Don't set timezone on allday dates (#2463) - -diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php -index e8e5ab2..f08798e 100644 ---- a/plugins/calendar/calendar.php -+++ b/plugins/calendar/calendar.php -@@ -1217,7 +1217,7 @@ class calendar extends rcube_plugin - if ($event['recurrence']) { - $event['recurrence_text'] = $this->_recurrence_text($event['recurrence']); - if ($event['recurrence']['UNTIL']) -- $event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'])->format('c'); -+ $event['recurrence']['UNTIL'] = $this->lib->adjust_timezone($event['recurrence']['UNTIL'], $event['allday'])->format('c'); - unset($event['recurrence']['EXCEPTIONS']); - } - -@@ -1248,8 +1248,8 @@ class calendar extends rcube_plugin - - return array( - '_id' => $event['calendar'] . ':' . $event['id'], // unique identifier for fullcalendar -- 'start' => $this->lib->adjust_timezone($event['start'])->format('c'), -- 'end' => $this->lib->adjust_timezone($event['end'])->format('c'), -+ 'start' => $this->lib->adjust_timezone($event['start'], $event['allday'])->format('c'), -+ 'end' => $this->lib->adjust_timezone($event['end'], $event['allday'])->format('c'), - // 'changed' might be empty for event recurrences (Bug #2185) - 'changed' => $event['changed'] ? $this->lib->adjust_timezone($event['changed'])->format('c') : null, - 'title' => strval($event['title']), -diff --git a/plugins/libcalendaring/libcalendaring.php b/plugins/libcalendaring/libcalendaring.php -index 47cf67e..4cd5782 100644 ---- a/plugins/libcalendaring/libcalendaring.php -+++ b/plugins/libcalendaring/libcalendaring.php -@@ -120,14 +120,14 @@ class libcalendaring extends rcube_plugin - * @param mixed Any kind of a date representation (DateTime object, string or unix timestamp) - * @return object DateTime object in user's timezone - */ -- public function adjust_timezone($dt) -+ public function adjust_timezone($dt, $dateonly = false) - { - if (is_numeric($dt)) - $dt = new DateTime('@'.$dt); - else if (is_string($dt)) - $dt = new DateTime($dt); - -- if ($dt instanceof DateTime) { -+ if ($dt instanceof DateTime && !($dt->_dateonly || $dateonly)) { - $dt->setTimezone($this->timezone); - } -
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-fix-replacement-of-alarm-values.patch
Deleted
@@ -1,19 +0,0 @@ -commit 2784e1a2dd79de0a13fe4cf75ce1d6ce8733d6a3 -Author: Thomas Bruederli <bruederli@kolabsys.com> -Date: Fri Nov 1 14:47:46 2013 +0100 - - Fix replacement of alarm values - -diff --git a/plugins/calendar/drivers/kolab/kolab_calendar.php b/plugins/calendar/drivers/kolab/kolab_calendar.php -index bd42f56..9eb70bd 100644 ---- a/plugins/calendar/drivers/kolab/kolab_calendar.php -+++ b/plugins/calendar/drivers/kolab/kolab_calendar.php -@@ -581,7 +581,7 @@ class kolab_calendar - - // The web client only supports DISPLAY type of alarms - if (!empty($record['alarms'])) -- $record['alarms'] = preg_replace('/:[A-Z]+$/', 'DISPLAY', $record['alarms']); -+ $record['alarms'] = preg_replace('/:[A-Z]+$/', ':DISPLAY', $record['alarms']); - - // remove empty recurrence array - if (empty($record['recurrence']))
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.1.6-task-enhancements.patch
Deleted
@@ -1,1472 +0,0 @@ -diff --git a/plugins/calendar/calendar.php b/plugins/calendar/calendar.php -index f038caf..685745d 100644 ---- a/plugins/calendar/calendar.php -+++ b/plugins/calendar/calendar.php -@@ -126,6 +126,7 @@ class calendar extends rcube_plugin - $this->register_action('mailtoevent', array($this, 'mail_message2event')); - $this->register_action('inlineui', array($this, 'get_inline_ui')); - $this->register_action('check-recent', array($this, 'check_recent')); -+ $this->add_hook('refresh', array($this, 'refresh')); - - // remove undo information... - if ($undo = $_SESSION['calendar_event_undo']) { -@@ -162,6 +163,8 @@ class calendar extends rcube_plugin - 'innerclass' => 'icon calendar', - ))), - 'messagemenu'); -+ -+ $this->api->output->add_label('calendar.createfrommail'); - } - } - -@@ -919,6 +922,38 @@ class calendar extends rcube_plugin - } - - /** -+ * Handler for keep-alive requests -+ * This will check for updated data in active calendars and sync them to the client -+ */ -+ public function refresh($attr) -+ { -+ // refresh the entire calendar every 10th time to also sync deleted events -+ $refetch = rand(0,10) == 10; -+ -+ foreach ($this->driver->list_calendars(true) as $cal) { -+ if ($refetch) { -+ $this->rc->output->command('plugin.refresh_calendar', -+ array('source' => $cal['id'], 'refetch' => true)); -+ } -+ else { -+ $events = $this->driver->load_events( -+ get_input_value('start', RCUBE_INPUT_GET), -+ get_input_value('end', RCUBE_INPUT_GET), -+ get_input_value('q', RCUBE_INPUT_GET), -+ $cal['id'], -+ 1, -+ $attr['last'] -+ ); -+ -+ foreach ($events as $event) { -+ $this->rc->output->command('plugin.refresh_calendar', -+ array('source' => $cal['id'], 'update' => $this->_client_event($event))); -+ } -+ } -+ } -+ } -+ -+ /** - * Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests. - * This will check for pending notifications and pass them to the client - */ -@@ -975,10 +1010,18 @@ class calendar extends rcube_plugin - - if (!$err && $_FILES['_data']['tmp_name']) { - $calendar = get_input_value('calendar', RCUBE_INPUT_GPC); -- $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name']); -- -- $count = $errors = 0; - $rangestart = $_REQUEST['_range'] ? date_create("now -" . intval($_REQUEST['_range']) . " months") : 0; -+ $count = $errors = 0; -+ -+ try { -+ $events = $this->get_ical()->import_from_file($_FILES['_data']['tmp_name'], 'UTF-8', true); -+ } -+ catch (Exception $e) { -+ $errors = 1; -+ $msg = $e->getMessage(); -+ $events = array(); -+ } -+ - foreach ($events as $event) { - // TODO: correctly handle recurring events which start before $rangestart - if ($event['end'] < $rangestart && (!$event['recurrence'] || ($event['recurrence']['until'] && $event['recurrence']['until'] < $rangestart))) -@@ -1000,8 +1043,9 @@ class calendar extends rcube_plugin - $this->rc->output->command('display_message', $this->gettext('importnone'), 'notice'); - $this->rc->output->command('plugin.import_success', array('source' => $calendar)); - } -- else -- $this->rc->output->command('display_message', $this->gettext('importerror'), 'error'); -+ else { -+ $this->rc->output->command('plugin.import_error', array('message' => $this->gettext('importerror') . ($msg ? ': ' . $msg : ''))); -+ } - } - else { - if ($err == UPLOAD_ERR_INI_SIZE || $err == UPLOAD_ERR_FORM_SIZE) { -@@ -1012,7 +1056,7 @@ class calendar extends rcube_plugin - $msg = rcube_label('fileuploaderror'); - } - -- $this->rc->output->command('display_message', $msg, 'error'); -+ $this->rc->output->command('plugin.import_error', array('message' => $msg)); - $this->rc->output->command('plugin.unlock_saving', false); - } - -diff --git a/plugins/calendar/calendar_base.js b/plugins/calendar/calendar_base.js -index c60c89a..33fe9e4 100644 ---- a/plugins/calendar/calendar_base.js -+++ b/plugins/calendar/calendar_base.js -@@ -6,7 +6,7 @@ - * @author Thomas Bruederli <bruederli@kolabsys.com> - * - * Copyright (C) 2010, Lazlo Westerhof <hello@lazlo.me> -- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> -+ * Copyright (C) 2013, 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 -@@ -36,10 +36,9 @@ function rcube_calendar(settings) - var me = this; - - // create new event from current mail message -- this.create_from_mail = function() -+ this.create_from_mail = function(uid) - { -- var uid; -- if ((uid = rcmail.get_single_uid())) { -+ if (uid || (uid = rcmail.get_single_uid())) { - // load calendar UI (scripts and edit dialog template) - if (!this.ui_loaded) { - $.when( -@@ -53,7 +52,7 @@ function rcube_calendar(settings) - - me.ui_loaded = true; - me.ui = new rcube_calendar_ui(me.settings); -- me.create_from_mail(); // start over -+ me.create_from_mail(uid); // start over - }); - return; - } -@@ -156,9 +155,6 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { - - // register create-from-mail command to message_commands array - if (rcmail.env.task == 'mail') { -- // place link above 'view source' -- $('#messagemenu a.calendarlink').parent().insertBefore($('#messagemenu a.sourcelink').parent()); -- - rcmail.register_command('calendar-create-from-mail', function() { cal.create_from_mail() }); - rcmail.addEventListener('plugin.mail2event_dialog', function(p){ cal.mail2event_dialog(p) }); - rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.ui && cal.ui.unlock_saving(); }); -@@ -169,6 +165,15 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { - } - else - rcmail.enable_command('calendar-create-from-mail', true); -+ -+ // add contextmenu item -+ if (window.rcm_contextmenu_register_command) { -+ rcm_contextmenu_register_command( -+ 'calendar-create-from-mail', -+ function(cmd,el){ cal.create_from_mail() }, -+ 'calendar.createfrommail', -+ 'moveto'); -+ } - } - } - -diff --git a/plugins/calendar/calendar_ui.js b/plugins/calendar/calendar_ui.js -index 986113f..d5a4308 100644 ---- a/plugins/calendar/calendar_ui.js -+++ b/plugins/calendar/calendar_ui.js -@@ -1981,10 +1981,17 @@ function rcube_calendar_ui(settings) - if (form && form.elements._data.value) { - rcmail.async_upload_form(form, 'import_events', function(e) { - rcmail.set_busy(false, null, me.saving_lock); -+ $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); -+ -+ // display error message if no sophisticated response from server arrived (e.g. iframe load error) -+ if (me.import_succeeded === null) -+ rcmail.display_message(rcmail.get_label('importerror', 'calendar'), 'error'); - }); - - // display upload indicator -+ me.import_succeeded = null; - me.saving_lock = rcmail.set_busy(true, 'uploading'); -+ $('.ui-dialog-buttonpane button', $dialog.parent()).button('disable'); - } - }; - -@@ -1999,6 +2006,7 @@ function rcube_calendar_ui(settings) - closeOnEscape: false, - title: rcmail.gettext('importevents', 'calendar'), - close: function() { -+ $('.ui-dialog-buttonpane button', $dialog.parent()).button('enable'); - $dialog.dialog("destroy").hide(); - }, - buttons: buttons, -@@ -2010,6 +2018,7 @@ function rcube_calendar_ui(settings) - // callback from server if import succeeded - this.import_success = function(p) - { -+ this.import_succeeded = true; - $("#eventsimport:ui-dialog").dialog('close'); - rcmail.set_busy(false, null, me.saving_lock); - rcmail.gui_objects.importform.reset(); -@@ -2018,6 +2027,13 @@ function rcube_calendar_ui(settings) - this.refresh(p); - }; - -+ // callback from server to report errors on import -+ this.import_error = function(p) -+ { -+ this.import_succeeded = false; -+ rcmail.display_message(p.message || rcmail.get_label('importerror', 'calendar'), 'error'); -+ } -+ - // show URL of the given calendar in a dialog box - this.showurl = function(calendar) - { -@@ -2087,6 +2103,20 @@ function rcube_calendar_ui(settings) - fc.fullCalendar('removeEvents', function(e){ return e.temp; }); - }; - -+ // modify query parameters for refresh requests -+ this.before_refresh = function(query) -+ { -+ var view = fc.fullCalendar('getView'); -+ -+ query.start = date2unixtime(view.visStart); -+ query.end = date2unixtime(view.visEnd); -+ -+ if (this.search_query) -+ query.q = this.search_query; -+ -+ return query; -+ }; -+ - - /*** event searching ***/ - -@@ -2780,6 +2810,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { - rcmail.addEventListener('plugin.unlock_saving', function(p){ cal.unlock_saving(); }); - rcmail.addEventListener('plugin.refresh_calendar', function(p){ cal.refresh(p); }); - rcmail.addEventListener('plugin.import_success', function(p){ cal.import_success(p); }); -+ rcmail.addEventListener('plugin.import_error', function(p){ cal.import_error(p); }); -+ rcmail.addEventListener('requestrefresh', function(q){ return cal.before_refresh(q); }); - - // let's go - var cal = new rcube_calendar_ui($.extend(rcmail.env.calendar_settings, rcmail.env.libcal_settings)); -diff --git a/plugins/calendar/drivers/calendar_driver.php b/plugins/calendar/drivers/calendar_driver.php -index 52de901..c09d8b9 100644 ---- a/plugins/calendar/drivers/calendar_driver.php -+++ b/plugins/calendar/drivers/calendar_driver.php -@@ -236,9 +236,11 @@ abstract class calendar_driver - * @param integer Event's new end (unix timestamp) - * @param string Search query (optional) - * @param mixed List of calendar IDs to load events from (either as array or comma-separated string) -+ * @param boolean Include virtual/recurring events (optional) -+ * @param integer Only list events modified since this time (unix timestamp) - * @return array A list of event objects (see header of this file for struct of an event) - */ -- abstract function load_events($start, $end, $query = null, $calendars = null); -+ abstract function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null); - - /** - * Get a list of pending alarms to be displayed to the user -diff --git a/plugins/calendar/drivers/database/database_driver.php b/plugins/calendar/drivers/database/database_driver.php -index 8cd363c..a2cb903 100644 ---- a/plugins/calendar/drivers/database/database_driver.php -+++ b/plugins/calendar/drivers/database/database_driver.php -@@ -724,7 +724,7 @@ class database_driver extends calendar_driver - * - * @see calendar_driver::load_events() - */ -- public function load_events($start, $end, $query = null, $calendars = null) -+ public function load_events($start, $end, $query = null, $calendars = null, $virtual = 1, $modifiedsince = null) - { - if (empty($calendars)) - $calendars = array_keys($this->calendars); -@@ -742,6 +742,12 @@ class database_driver extends calendar_driver - $sql_add = 'AND (' . join(' OR ', $sql_query) . ')'; - } - -+ if (!$virtual) -+ $sql_arr .= ' AND e.recurrence_id = 0'; -+ -+ if ($modifiedsince) -+ $sql_add .= ' AND e.changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $modifiedsince)); -+ - $events = array(); - if (!empty($calendar_ids)) { - $result = $this->rc->db->query(sprintf( -diff --git a/plugins/calendar/drivers/kolab/kolab_driver.php b/plugins/calendar/drivers/kolab/kolab_driver.php -index b8171b7..0722d23 100644 ---- a/plugins/calendar/drivers/kolab/kolab_driver.php -+++ b/plugins/calendar/drivers/kolab/kolab_driver.php -@@ -733,20 +733,25 @@ class kolab_driver extends calendar_driver - * @param integer Event's new end (unix timestamp) - * @param string Search query (optional) - * @param mixed List of calendar IDs to load events from (either as array or comma-separated string) -- * @param boolean Strip virtual events (optional) -+ * @param boolean Include virtual events (optional) -+ * @param integer Only list events modified since this time (unix timestamp) - * @return array A list of event records - */ -- public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1) -+ public function load_events($start, $end, $search = null, $calendars = null, $virtual = 1, $modifiedsince = null) - { - if ($calendars && is_string($calendars)) - $calendars = explode(',', $calendars); - -+ $query = array(); -+ if ($modifiedsince) -+ $query[] = array('changed', '>=', $modifiedsince); -+ - $events = $categories = array(); - foreach (array_keys($this->calendars) as $cid) { - if ($calendars && !in_array($cid, $calendars)) - continue; - -- $events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual)); -+ $events = array_merge($events, $this->calendars[$cid]->list_events($start, $end, $search, $virtual, $query)); - $categories += $this->calendars[$cid]->categories; - } - -diff --git a/plugins/tasklist/drivers/database/tasklist_database_driver.php b/plugins/tasklist/drivers/database/tasklist_database_driver.php -index 8ad776a..3c5ad38 100644 ---- a/plugins/tasklist/drivers/database/tasklist_database_driver.php -+++ b/plugins/tasklist/drivers/database/tasklist_database_driver.php -@@ -286,7 +286,7 @@ class tasklist_database_driver extends tasklist_driver - - if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE) - $sql_add .= ' AND complete=1'; -- else // don't show complete tasks by default -+ else if (empty($filter['since'])) // don't show complete tasks by default - $sql_add .= ' AND complete<1'; - - if ($filter['mask'] & tasklist::FILTER_MASK_FLAGGED) -@@ -301,6 +301,10 @@ class tasklist_database_driver extends tasklist_driver - $sql_add = 'AND (' . join(' OR ', $sql_query) . ')'; - } - -+ if ($filter['since'] && is_numeric($filter['since'])) { -+ $sql_add .= ' AND changed >= ' . $this->rc->db->quote(date('Y-m-d H:i:s', $filter['since'])); -+ } -+ - $tasks = array(); - if (!empty($list_ids)) { - $result = $this->rc->db->query(sprintf( -diff --git a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php -index c630a41..fd3e5c3 100644 ---- a/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php -+++ b/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php -@@ -371,7 +371,7 @@ class tasklist_kolab_driver extends tasklist_driver - $query = array(); - if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE) - $query[] = array('tags','~','x-complete'); -- else -+ else if (empty($filter['since'])) - $query[] = array('tags','!~','x-complete'); - - // full text search (only works with cache enabled) -@@ -382,6 +382,10 @@ class tasklist_kolab_driver extends tasklist_driver - } - } - -+ if ($filter['since']) { -+ $query[] = array('changed', '>=', $filter['since']); -+ } -+ - foreach ($lists as $list_id) { - $folder = $this->folders[$list_id]; - foreach ((array)$folder->select($query) as $record) { -diff --git a/plugins/tasklist/localization/en_US.inc b/plugins/tasklist/localization/en_US.inc -index 57d6c3c..ba331be 100644 ---- a/plugins/tasklist/localization/en_US.inc -+++ b/plugins/tasklist/localization/en_US.inc -@@ -51,6 +51,8 @@ $labels['listactions'] = 'List options...'; - $labels['listname'] = 'Name'; - $labels['showalarms'] = 'Show alarms'; - $labels['import'] = 'Import'; -+$labels['viewoptions'] = 'View options'; -+$labels['focusview'] = 'View only this list'; - - // date words - $labels['on'] = 'on'; -diff --git a/plugins/tasklist/skins/larry/sprites.png b/plugins/tasklist/skins/larry/sprites.png -index 5224f6f..5c6b9fd 100644 -Binary files a/plugins/tasklist/skins/larry/sprites.png and b/plugins/tasklist/skins/larry/sprites.png differ -diff --git a/plugins/tasklist/skins/larry/tasklist.css b/plugins/tasklist/skins/larry/tasklist.css -index 173704d..7a1df99 100644 ---- a/plugins/tasklist/skins/larry/tasklist.css -+++ b/plugins/tasklist/skins/larry/tasklist.css -@@ -66,7 +66,7 @@ body.attachmentwin #topnav .topright { - } - - #taskselector { -- margin: -4px 0 0; -+ margin: -4px 40px 0 0; - padding: 0; - } - -@@ -180,16 +180,46 @@ body.attachmentwin #topnav .topright { - #tagslist li { - display: inline-block; - color: #004458; -- margin-right: 0.5em; -+ padding-right: 0.2em; -+ margin-right: 0.3em; - margin-bottom: 0.4em; - min-width: 1.2em; - cursor: pointer; - } - -+#tagslist li.inactive { -+ color: #89b3be; -+ padding-right: 0.6em; -+ font-size: 80%; -+/* display: none; */ -+} -+ -+#tagslist li .count { -+ position: relative; -+ top: -1px; -+ margin-left: 5px; -+ padding: 0.15em 0.5em; -+ font-size: 80%; -+ font-weight: bold; -+ color: #59838e; -+ background: #c7e3ef; -+ box-shadow: inset 0 1px 1px 0 #b0ccd7; -+ -o-box-shadow: inset 0 1px 1px 0 #b0ccd7; -+ -webkit-box-shadow: inset 0 1px 1px 0 #b0ccd7; -+ -moz-box-shadow: inset 0 1px 1px 0 #b0ccd7; -+ border-color: #b0ccd7; -+ border-radius: 8px; -+} -+ -+.tag-draghelper .tag .count, -+#tagslist li.inactive .count { -+ display: none; -+} -+ - #tasklists li { - margin: 0; - height: 20px; -- padding: 6px 8px 2px; -+ padding: 6px 8px 2px 6px; - display: block; - position: relative; - white-space: nowrap; -@@ -206,10 +236,13 @@ body.attachmentwin #topnav .topright { - - #tasklists li span.listname { - display: block; -+ position: absolute; -+ top: 7px; -+ left: 26px; -+ right: 26px; - cursor: default; - padding-bottom: 2px; - padding-right: 30px; -- margin-right: 20px; - color: #004458; - overflow: hidden; - text-overflow: ellipsis; -@@ -218,7 +251,20 @@ body.attachmentwin #topnav .topright { - } - - #tasklists li span.handle { -- display: none; -+ display: inline-block; -+ width: 16px; -+ height: 16px; -+ margin-right: 4px; -+ background: url(sprites.png) -200px 0 no-repeat; -+ cursor: pointer; -+} -+ -+#tasklists li:hover span.handle { -+ background-position: -20px -101px; -+} -+ -+#tasklists li.focusview span.handle { -+ background-position: -2px -101px; - } - - #tasklists li.selected span.listname { -@@ -249,6 +295,11 @@ body.attachmentwin #topnav .topright { - color: #aaa; - } - -+#tasklists li.virtual span.handle { -+ background: none; -+ cursor: default; -+} -+ - #tasklists li input { - position: absolute; - top: 5px; -@@ -336,6 +387,27 @@ body.attachmentwin #topnav .topright { - background: -ms-linear-gradient(top, #eee 0%, #dfdfdf 100%); - background: linear-gradient(top, #eee 0%, #dfdfdf 100%); - border-bottom: 1px solid #ccc; -+ position: relative; -+} -+ -+#tasksview .buttonbar .buttonbar-right { -+ position: absolute; -+ top: 6px; -+ right: 8px; -+} -+ -+.buttonbar-right .listmenu { -+ display: inline-block; -+ cursor: pointer; -+} -+ -+.buttonbar-right .listmenu .inner { -+ display: inline-block; -+ height: 18px; -+ width: 20px; -+ padding: 0; -+ background: url(sprites.png) 0 -237px no-repeat; -+ text-indent: -5000px; - } - - #thelist { -@@ -456,6 +528,7 @@ body.attachmentwin #topnav .topright { - text-align: right; - } - -+.tag-draghelper .tag, - .taskhead .tags .tag { - font-size: 85%; - background: #d9ecf4; -@@ -465,6 +538,11 @@ body.attachmentwin #topnav .topright { - margin-right: 3px; - } - -+.tag-draghelper li.tag { -+ list-style: none; -+ font-size: 100%; -+} -+ - .taskhead .date { - position: absolute; - top: 4px; -@@ -547,9 +625,22 @@ body.attachmentwin #topnav .topright { - border-top: 1px solid #219de6; - } - --ul.toolbarmenu li span.add { -+ul.toolbarmenu li span.add, -+ul.toolbarmenu li span.expand, -+ul.toolbarmenu li span.collapse { - background-image: url(sprites.png); -- background-position: 0 -100px; -+} -+ -+ul.toolbarmenu li span.add { -+ background-position: 0 -302px; -+} -+ -+ul.toolbarmenu li span.expand { -+ background-position: 0 -258px; -+} -+ -+ul.toolbarmenu li span.collapse { -+ background-position: 0 -280px; - } - - ul.toolbarmenu li span.delete { -@@ -806,6 +897,12 @@ label.block { - /* cursor: pointer; */ - } - -+.form-section span.tag-element.inherit { -+ color: #666; -+ background: #f2f2f2; -+ border-color: #ddd; -+} -+ - .tagedit-list li.tagedit-listelement-old a.tagedit-close, - .tagedit-list li.tagedit-listelement-old a.tagedit-break, - .tagedit-list li.tagedit-listelement-old a.tagedit-delete, -diff --git a/plugins/tasklist/skins/larry/templates/mainview.html b/plugins/tasklist/skins/larry/templates/mainview.html -index 9196d02..52463e0 100644 ---- a/plugins/tasklist/skins/larry/templates/mainview.html -+++ b/plugins/tasklist/skins/larry/templates/mainview.html -@@ -59,6 +59,10 @@ - <li class="nodate"><a href="#nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li> - <li class="complete"><a href="#complete"><roundcube:label name="tasklist.complete" /><span class="count"></span></a></li> - </ul> -+ -+ <div class="buttonbar-right"> -+ <roundcube:button name="taskviewmenulink" id="taskviewmenulink" type="link" title="tasklist.viewoptions" class="listmenu viewoptions" onclick="UI.show_popup('taskviewmenu');return false" innerClass="inner" content="⚙" /> -+ </div> - </div> - - <div class="scroller"> -@@ -92,6 +96,13 @@ - </ul> - </div> - -+<div id="taskviewmenu" class="popupmenu"> -+ <ul class="toolbarmenu"> -+ <li><roundcube:button command="expand-all" label="expand-all" class="icon" classAct="icon active" innerclass="icon expand" /></li> -+ <li><roundcube:button command="collapse-all" label="collapse-all" class="icon" classAct="icon active" innerclass="icon collapse" /></li> -+ </ul> -+</div> -+ - <div id="taskshow"> - <div class="form-section" id="task-parent-title"></div> - <div class="form-section"> -@@ -146,6 +157,8 @@ $(document).ready(function(e){ - UI.init(); - new rcube_splitter({ id:'taskviewsplitter', p1:'#sidebar', p2:'#mainview-right', - orientation:'v', relative:true, start:240, min:180, size:16, offset:2 }).init(); -+ new rcube_splitter({ id:'taskviewsplitterv', p1:'#tagsbox', p2:'#tasklistsbox', -+ orientation:'h', relative:true, start:242, min:120, size:16, offset:6 }).init(); - }); - - </script> -diff --git a/plugins/tasklist/tasklist.js b/plugins/tasklist/tasklist.js -index d76c9d1..25ab68d 100644 ---- a/plugins/tasklist/tasklist.js -+++ b/plugins/tasklist/tasklist.js -@@ -54,6 +54,7 @@ function rcube_tasklist_ui(settings) - var filtermask = FILTER_MASK_ALL; - var loadstate = { filter:-1, lists:'', search:null }; - var idcount = 0; -+ var focusview; - var saving_lock; - var ui_loading; - var taskcounts = {}; -@@ -64,6 +65,8 @@ function rcube_tasklist_ui(settings) - var search_request; - var search_query; - var completeness_slider; -+ var task_draghelper; -+ var tag_draghelper; - var me = this; - - // general datepicker settings -@@ -92,6 +95,7 @@ function rcube_tasklist_ui(settings) - this.add_childtask = add_childtask; - this.quicksearch = quicksearch; - this.reset_search = reset_search; -+ this.expand_collapse = expand_collapse; - this.list_remove = list_remove; - this.list_edit_dialog = list_edit_dialog; - this.unlock_saving = unlock_saving; -@@ -116,22 +120,30 @@ function rcube_tasklist_ui(settings) - init_tasklist_li(li, id); - } - -- if (me.tasklists[id].editable && !me.selected_list) { -+ if (me.tasklists[id].editable && (!me.selected_list || (me.tasklists[id].active && !me.tasklists[me.selected_list].active))) { - me.selected_list = id; -- rcmail.enable_command('addtask', true); -- $(li).click(); - } - } - -+ if (me.selected_list) { -+ rcmail.enable_command('addtask', true); -+ $(rcmail.get_folder_li(me.selected_list, 'rcmlitasklist')).click(); -+ } -+ - // register server callbacks - rcmail.addEventListener('plugin.data_ready', data_ready); -- rcmail.addEventListener('plugin.refresh_task', update_taskitem); -+ rcmail.addEventListener('plugin.update_task', update_taskitem); -+ rcmail.addEventListener('plugin.refresh_tasks', function(p) { update_taskitem(p, true); }); - rcmail.addEventListener('plugin.update_counts', update_counts); - rcmail.addEventListener('plugin.insert_tasklist', insert_list); - rcmail.addEventListener('plugin.update_tasklist', update_list); - rcmail.addEventListener('plugin.destroy_tasklist', destroy_list); -- rcmail.addEventListener('plugin.reload_data', function(){ list_tasks(null); }); - rcmail.addEventListener('plugin.unlock_saving', unlock_saving); -+ rcmail.addEventListener('requestrefresh', before_refresh); -+ rcmail.addEventListener('plugin.reload_data', function(){ -+ list_tasks(null, true); -+ setTimeout(fetch_counts, 200); -+ }); - - // start loading tasks - fetch_counts(); -@@ -335,7 +347,8 @@ function rcube_tasklist_ui(settings) - $(input).datepicker('widget').find('button.ui-datepicker-close') - .html(rcmail.gettext('nodate','tasklist')) - .attr('onclick', '') -- .click(function(e){ -+ .unbind('click') -+ .bind('click', function(e){ - $(input).datepicker('setDate', null).datepicker('hide'); - }); - }, 1); -@@ -393,7 +406,7 @@ function rcube_tasklist_ui(settings) - /** - * List tasks matching the given selector - */ -- function list_tasks(sel) -+ function list_tasks(sel, force) - { - if (rcmail.busy) - return; -@@ -405,7 +418,7 @@ function rcube_tasklist_ui(settings) - - var active = active_lists(), - basefilter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL, -- reload = active.join(',') != loadstate.lists || basefilter != loadstate.filter || loadstate.search != search_query; -+ reload = force || active.join(',') != loadstate.lists || basefilter != loadstate.filter || loadstate.search != search_query; - - if (active.length && reload) { - ui_loading = rcmail.set_busy(true, 'loading'); -@@ -439,6 +452,19 @@ function rcube_tasklist_ui(settings) - } - - /** -+ * Modify query parameters for refresh requests -+ */ -+ function before_refresh(query) -+ { -+ query.filter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL; -+ query.lists = active_lists().join(','); -+ if (search_query) -+ query.q = search_query; -+ -+ return query; -+ } -+ -+ /** - * Callback if task data from server is ready - */ - function data_ready(response) -@@ -459,8 +485,9 @@ function rcube_tasklist_ui(settings) - listdata[listdata[id].parent_id].children.push(id); - } - -- render_tasklist(); - append_tags(response.tags || []); -+ render_tasklist(); -+ - rcmail.set_busy(false, 'loading', ui_loading); - } - -@@ -473,6 +500,7 @@ function rcube_tasklist_ui(settings) - var id, rec, - count = 0, - cache = {}, -+ activetags = {}, - msgbox = $('#listmessagebox').hide(), - list = $(rcmail.gui_objects.resultlist).html(''); - -@@ -482,10 +510,19 @@ function rcube_tasklist_ui(settings) - if (match_filter(rec, cache)) { - render_task(rec); - count++; -+ -+ // keep a list of tags from all visible tasks -+ for (var t, j=0; rec.tags && j < rec.tags.length; j++) { -+ t = rec.tags[j]; -+ if (typeof activetags[t] == 'undefined') -+ activetags[t] = 0; -+ activetags[t]++; -+ } - } - } - - fix_tree_toggles(); -+ update_tagcloud(activetags); - - if (!count) - msgbox.html(rcmail.gettext('notasksfound','tasklist')).show(); -@@ -506,6 +543,30 @@ function rcube_tasklist_ui(settings) - } - - /** -+ * Expand/collapse all task items with childs -+ */ -+ function expand_collapse(expand) -+ { -+ var collapsed = !expand; -+ -+ $('.taskitem .childtasks')[(collapsed ? 'hide' : 'show')](); -+ $('.taskitem .childtoggle') -+ .removeClass(collapsed ? 'expanded' : 'collapsed') -+ .addClass(collapsed ? 'collapsed' : 'expanded') -+ .html(collapsed ? '▶' : '▼'); -+ -+ // store new toggle collapse states -+ var ids = []; -+ for (var id in listdata) { -+ if (listdata[id].children && listdata[id].children.length) -+ ids.push(id); -+ } -+ if (ids.length) { -+ rcmail.http_post('tasks/task', { action:'collapse', t:{ id:ids.join(',') }, collapsed:collapsed?1:0 }); -+ } -+ } -+ -+ /** - * - */ - function append_tags(taglist) -@@ -520,8 +581,19 @@ function rcube_tasklist_ui(settings) - - // append new tags to tag cloud - $.each(newtags, function(i, tag){ -- $('<li>').attr('rel', tag).data('value', tag).html(Q(tag)).appendTo(rcmail.gui_objects.tagslist); -- }); -+ $('<li>').attr('rel', tag).data('value', tag) -+ .html(Q(tag) + '<span class="count"></span>') -+ .appendTo(rcmail.gui_objects.tagslist) -+ .draggable({ -+ addClasses: false, -+ revert: 'invalid', -+ revertDuration: 300, -+ helper: tag_draggable_helper, -+ start: tag_draggable_start, -+ appendTo: 'body', -+ cursor: 'pointer' -+ }); -+ }); - - // re-sort tags list - $(rcmail.gui_objects.tagslist).children('li').sortElements(function(a,b){ -@@ -530,6 +602,90 @@ function rcube_tasklist_ui(settings) - } - - /** -+ * Display the given counts to each tag and set those inactive which don't -+ * have any matching tasks in the current view. -+ */ -+ function update_tagcloud(counts) -+ { -+ // compute counts first by iterating over all visible task items -+ if (typeof counts == 'undefined') { -+ counts = {}; -+ $('li.taskitem', rcmail.gui_objects.resultlist).each(function(i,li){ -+ var t, id = $(li).attr('rel'), -+ rec = listdata[id]; -+ for (var j=0; rec && rec.tags && j < rec.tags.length; j++) { -+ t = rec.tags[j]; -+ if (typeof counts[t] == 'undefined') -+ counts[t] = 0; -+ counts[t]++; -+ } -+ }); -+ } -+ -+ $(rcmail.gui_objects.tagslist).children('li').each(function(i,li){ -+ var elem = $(li), tag = elem.attr('rel'), -+ count = counts[tag] || 0; -+ -+ elem.children('.count').html(count+''); -+ if (count == 0) elem.addClass('inactive'); -+ else elem.removeClass('inactive'); -+ }); -+ } -+ -+ /* Helper functions for drag & drop functionality of tags */ -+ -+ function tag_draggable_helper() -+ { -+ if (!tag_draghelper) -+ tag_draghelper = $('<div class="tag-draghelper"></div>'); -+ else -+ tag_draghelper.html(''); -+ -+ $(this).clone().addClass('tag').appendTo(tag_draghelper); -+ return tag_draghelper; -+ } -+ -+ function tag_draggable_start(event, ui) -+ { -+ $('.taskhead').droppable({ -+ hoverClass: 'droptarget', -+ accept: tag_droppable_accept, -+ drop: tag_draggable_dropped, -+ addClasses: false -+ }); -+ } -+ -+ function tag_droppable_accept(draggable) -+ { -+ if (rcmail.busy) -+ return false; -+ -+ var tag = draggable.data('value'), -+ drop_id = $(this).data('id'), -+ drop_rec = listdata[drop_id]; -+ -+ // target already has this tag assigned -+ if (!drop_rec || (drop_rec.tags && $.inArray(tag, drop_rec.tags) >= 0)) { -+ return false; -+ } -+ -+ return true; -+ } -+ -+ function tag_draggable_dropped(event, ui) -+ { -+ var drop_id = $(this).data('id'), -+ tag = ui.draggable.data('value'), -+ rec = listdata[drop_id]; -+ -+ if (rec && rec.id) { -+ if (!rec.tags) rec.tags = []; -+ rec.tags.push(tag); -+ save_task(rec, 'edit'); -+ } -+ } -+ -+ /** - * - */ - function update_counts(counts) -@@ -553,11 +709,11 @@ function rcube_tasklist_ui(settings) - /** - * Callback from server to update a single task item - */ -- function update_taskitem(rec) -+ function update_taskitem(rec, filter) - { - // handle a list of task records - if ($.isArray(rec)) { -- $.each(rec, function(i,r){ update_taskitem(r); }); -+ $.each(rec, function(i,r){ update_taskitem(r, filter); }); - return; - } - -@@ -597,12 +753,16 @@ function rcube_tasklist_ui(settings) - } - } - -- if (list.active) -- render_task(rec, oldid); -- else -+ if (list.active || rec.tempid) { -+ if (!filter || match_filter(rec, {})) -+ render_task(rec, oldid); -+ } -+ else { - $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).remove(); -+ } - - append_tags(rec.tags || []); -+ update_tagcloud(); - fix_tree_toggles(); - } - -@@ -655,10 +815,10 @@ function rcube_tasklist_ui(settings) - revert: 'invalid', - addClasses: false, - cursorAt: { left:-10, top:12 }, -- helper: draggable_helper, -+ helper: task_draggable_helper, - appendTo: 'body', -- start: draggable_start, -- stop: draggable_stop, -+ start: task_draggable_start, -+ stop: task_draggable_stop, - revertDuration: 300 - }); - -@@ -703,7 +863,7 @@ function rcube_tasklist_ui(settings) - */ - function resort_task(rec, li, animated) - { -- var dir = 0, index, slice, next_li, next_id, next_rec; -+ var dir = 0, index, slice, cmp, next_li, next_id, next_rec, insert_after, past_myself; - - // animated moving - var insert_animated = function(li, before, after) { -@@ -729,33 +889,36 @@ function rcube_tasklist_ui(settings) - } - - // find the right place to insert the task item -- li.siblings().each(function(i, elem){ -+ li.parent().children('.taskitem').each(function(i, elem){ - next_li = $(elem); - next_id = next_li.attr('rel'); - next_rec = listdata[next_id]; - - if (next_id == rec.id) { -- next_li = null; -+ past_myself = true; - return 1; // continue - } - -- if (next_rec && task_cmp(rec, next_rec) > 0) { -+ cmp = next_rec ? task_cmp(rec, next_rec) : 0; -+ -+ if (cmp > 0 || (cmp == 0 && !past_myself)) { -+ insert_after = next_li; - return 1; // continue; - } -- else if (next_rec && next_li && task_cmp(rec, next_rec) < 0) { -+ else if (next_li && cmp < 0) { - if (animated) insert_animated(li, next_li); - else li.insertBefore(next_li); -- next_li = null; -- return false; -+ index = $.inArray(next_id, listindex); -+ return false; // break - } - }); - -- index = $.inArray(next_id, listindex); -+ if (insert_after) { -+ if (animated) insert_animated(li, null, insert_after); -+ else li.insertAfter(insert_after); - -- if (next_li) { -- if (animated) insert_animated(li, null, next_li); -- else li.insertAfter(next_li); -- index++; -+ next_id = insert_after.attr('rel'); -+ index = $.inArray(next_id, listindex); - } - - // insert into list index -@@ -799,20 +962,20 @@ function rcube_tasklist_ui(settings) - - /* Helper functions for drag & drop functionality */ - -- function draggable_helper() -+ function task_draggable_helper() - { -- if (!draghelper) -- draghelper = $('<div class="taskitem-draghelper">✔</div>'); -+ if (!task_draghelper) -+ task_draghelper = $('<div class="taskitem-draghelper">✔</div>'); - -- return draghelper; -+ return task_draghelper; - } - -- function draggable_start(event, ui) -+ function task_draggable_start(event, ui) - { - $('.taskhead, #rootdroppable, #'+rcmail.gui_objects.folderlist.id+' li').droppable({ - hoverClass: 'droptarget', -- accept: droppable_accept, -- drop: draggable_dropped, -+ accept: task_droppable_accept, -+ drop: task_draggable_dropped, - addClasses: false - }); - -@@ -820,13 +983,13 @@ function rcube_tasklist_ui(settings) - $('#rootdroppable').show(); - } - -- function draggable_stop(event, ui) -+ function task_draggable_stop(event, ui) - { - $(this).parent().removeClass('dragging'); - $('#rootdroppable').hide(); - } - -- function droppable_accept(draggable) -+ function task_droppable_accept(draggable) - { - if (rcmail.busy) - return false; -@@ -858,7 +1021,7 @@ function rcube_tasklist_ui(settings) - return true; - } - -- function draggable_dropped(event, ui) -+ function task_draggable_dropped(event, ui) - { - var drop_id = $(this).data('id'), - task_id = ui.draggable.data('id'), -@@ -921,10 +1084,23 @@ function rcube_tasklist_ui(settings) - $('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%'); - $('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : '')); - -- var taglist = $('#task-tags')[(rec.tags && rec.tags.length ? 'show' : 'hide')]().children('.task-text').empty(); -+ var itags = get_inherited_tags(rec); -+ var taglist = $('#task-tags')[(rec.tags && rec.tags.length || itags.length ? 'show' : 'hide')]().children('.task-text').empty(); - if (rec.tags && rec.tags.length) { - $.each(rec.tags, function(i,val){ -- $('<span>').addClass('tag-element').html(Q(val)).data('value', val).appendTo(taglist); -+ $('<span>').addClass('tag-element').html(Q(val)).appendTo(taglist); -+ }); -+ } -+ -+ // append inherited tags -+ if (itags.length) { -+ $.each(itags, function(i,val){ -+ if (!rec.tags || $.inArray(val, rec.tags) < 0) -+ $('<span>').addClass('tag-element inherit').html(Q(val)).appendTo(taglist); -+ }); -+ // re-sort tags list -+ $(taglist).children().sortElements(function(a,b){ -+ return $.text([a]).toLowerCase() > $.text([b]).toLowerCase() ? 1 : -1; - }); - } - -@@ -1012,7 +1188,7 @@ function rcube_tasklist_ui(settings) - var recstarttime = $('#taskedit-starttime').val(rec.starttime || ''); - var complete = $('#taskedit-completeness').val((rec.complete || 0) * 100); - completeness_slider.slider('value', complete.val()); -- var tasklist = $('#taskedit-tasklist').val(rec.list || 0).prop('disabled', rec.parent_id ? true : false); -+ var tasklist = $('#taskedit-tasklist').val(rec.list || me.selected_list).prop('disabled', rec.parent_id ? true : false); - - // tag-edit line - var tagline = $(rcmail.gui_objects.edittagline).empty(); -@@ -1102,10 +1278,16 @@ function rcube_tasklist_ui(settings) - } - } - -+ // collect tags - $('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem){ - if (elem.value) - me.selected_task.tags.push(elem.value); - }); -+ // including the "pending" one in the text box -+ var newtag = $('#tagedit-input').val(); -+ if (newtag != '') { -+ me.selected_task.tags.push(newtag); -+ } - - // serialize alarm settings - var alarm = $('#taskedit select.edit-alarm-type').val(); -@@ -1124,7 +1306,7 @@ function rcube_tasklist_ui(settings) - } - - // task assigned to a new list -- if (me.selected_task.list && me.selected_task.list != rec.list) { -+ if (me.selected_task.list && listdata[id] && me.selected_task.list != listdata[id].list) { - me.selected_task._fromlist = rec.list; - } - -@@ -1135,6 +1317,9 @@ function rcube_tasklist_ui(settings) - if (!me.selected_task.list && list.id) - me.selected_task.list = list.id; - -+ if (!me.selected_task.tags.length) -+ me.selected_task.tags = ''; -+ - if (save_task(me.selected_task, action)) - $dialog.dialog('close'); - }; -@@ -1367,12 +1552,17 @@ function rcube_tasklist_ui(settings) - return cache[rec.id]; - } - -- var match = !filtermask || (filtermask & rec.mask) > 0 -+ var match = !filtermask || (filtermask & rec.mask) > 0; -+ -+ // in focusview mode, only tasks from the selected list are allowed -+ if (focusview && rec.list != focusview) -+ match = false; - - if (match && tagsfilter.length) { - match = rec.tags && rec.tags.length; -+ var alltags = get_inherited_tags(rec).concat(rec.tags || []); - for (var i=0; match && i < tagsfilter.length; i++) { -- if ($.inArray(tagsfilter[i], rec.tags) < 0) -+ if ($.inArray(tagsfilter[i], alltags) < 0) - match = false; - } - } -@@ -1402,6 +1592,23 @@ function rcube_tasklist_ui(settings) - /** - * - */ -+ function get_inherited_tags(rec) -+ { -+ var parent_id, itags = []; -+ -+ if ((parent_id = rec.parent_id)) { -+ while (parent_id && listdata[parent_id]) { -+ itags = itags.concat(listdata[parent_id].tags || []); -+ parent_id = listdata[parent_id].parent_id; -+ } -+ } -+ -+ return $.unqiqueStrings(itags); -+ } -+ -+ /** -+ * -+ */ - function list_edit_dialog(id) - { - var list = me.tasklists[id], -@@ -1671,6 +1878,11 @@ function rcube_tasklist_ui(settings) - if (!this.checked) remove_tasks(id); - else list_tasks(null); - rcmail.http_post('tasklist', { action:'subscribe', l:{ id:id, active:me.tasklists[id].active?1:0 } }); -+ -+ // disable focusview -+ if (!this.checked && focusview == id) { -+ set_focusview(null); -+ } - } - }).data('id', id).get(0).checked = me.tasklists[id].active || false; - -@@ -1679,6 +1891,15 @@ function rcube_tasklist_ui(settings) - rcmail.select_folder(id, 'rcmlitasklist'); - rcmail.enable_command('list-edit', 'list-remove', 'list-import', me.tasklists[id].editable); - me.selected_list = id; -+ -+ // click on handle icon toggles focusview -+ if (e.target.className == 'handle') { -+ set_focusview(focusview == id ? null : id) -+ } -+ // disable focusview when selecting another list -+ else if (focusview && id != focusview) { -+ set_focusview(null); -+ } - }) - .dblclick(function(e){ - list_edit_dialog($(this).data('id')); -@@ -1688,6 +1909,31 @@ function rcube_tasklist_ui(settings) - .addClass(me.tasklists[id].editable ? null : 'readonly'); - } - -+ /** -+ * Enable/disable focusview mode for the given list -+ */ -+ function set_focusview(id) -+ { -+ if (focusview && focusview != id) -+ $(rcmail.get_folder_li(focusview, 'rcmlitasklist')).removeClass('focusview'); -+ -+ focusview = id; -+ -+ // activate list if necessary -+ if (focusview && !me.tasklists[id].active) { -+ $('input', rcmail.get_folder_li(id, 'rcmlitasklist')).get(0).checked = true; -+ me.tasklists[id].active = true; -+ fetch_counts(); -+ } -+ -+ // update list -+ list_tasks(null); -+ -+ if (focusview) { -+ $(rcmail.get_folder_li(focusview, 'rcmlitasklist')).addClass('focusview'); -+ } -+ } -+ - - // init dialog by default - init_taskedit(); -@@ -1714,6 +1960,22 @@ jQuery.fn.sortElements = (function(){ - }; - })(); - -+// equivalent to $.unique() but working on arrays of strings -+jQuery.unqiqueStrings = (function() { -+ return function(arr) { -+ var hash = {}, out = []; -+ -+ for (var i = 0; i < arr.length; i++) { -+ hash[arr[i]] = 0; -+ } -+ for (var val in hash) { -+ out.push(val); -+ } -+ -+ return out; -+ }; -+})(); -+ - - /* tasklist plugin UI initialization */ - var rctasks; -@@ -1731,6 +1993,8 @@ window.rcmail && rcmail.addEventListener('init', function(evt) { - - rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true); - rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true); -+ rcmail.register_command('expand-all', function(){ rctasks.expand_collapse(true); }, true); -+ rcmail.register_command('collapse-all', function(){ rctasks.expand_collapse(false); }, true); - - rctasks.init(); - }); -diff --git a/plugins/tasklist/tasklist.php b/plugins/tasklist/tasklist.php -index 6ebddc4..e77bccc 100644 ---- a/plugins/tasklist/tasklist.php -+++ b/plugins/tasklist/tasklist.php -@@ -90,6 +90,7 @@ class tasklist extends rcube_plugin - $this->register_action('mail2task', array($this, 'mail_message2task')); - $this->register_action('get-attachment', array($this, 'attachment_get')); - $this->register_action('upload', array($this, 'attachment_upload')); -+ $this->add_hook('refresh', array($this, 'refresh')); - - $this->collapsed_tasks = array_filter(explode(',', $this->rc->config->get('tasklist_collapsed_tasks', ''))); - } -@@ -112,6 +113,8 @@ class tasklist extends rcube_plugin - 'innerclass' => 'icon taskadd', - ))), - 'messagemenu'); -+ -+ $this->api->output->add_label('tasklist.createfrommail'); - } - } - -@@ -247,13 +250,15 @@ class tasklist extends rcube_plugin - break; - - case 'collapse': -- if (intval(get_input_value('collapsed', RCUBE_INPUT_GPC))) { -- $this->collapsed_tasks[] = $rec['id']; -- } -- else { -- $i = array_search($rec['id'], $this->collapsed_tasks); -- if ($i !== false) -- unset($this->collapsed_tasks[$i]); -+ foreach (explode(',', $rec['id']) as $rec_id) { -+ if (intval(get_input_value('collapsed', RCUBE_INPUT_GPC))) { -+ $this->collapsed_tasks[] = $rec_id; -+ } -+ else { -+ $i = array_search($rec_id, $this->collapsed_tasks); -+ if ($i !== false) -+ unset($this->collapsed_tasks[$i]); -+ } - } - - $this->rc->user->save_prefs(array('tasklist_collapsed_tasks' => join(',', array_unique($this->collapsed_tasks)))); -@@ -278,7 +283,7 @@ class tasklist extends rcube_plugin - foreach ($refresh as $i => $r) - $this->encode_task($refresh[$i]); - } -- $this->rc->output->command('plugin.refresh_task', $refresh); -+ $this->rc->output->command('plugin.update_task', $refresh); - } - } - -@@ -540,8 +545,17 @@ class tasklist extends rcube_plugin - - } - */ -+ $data = $this->tasks_data($this->driver->list_tasks($filter, $lists), $f, $tags); -+ $this->rc->output->command('plugin.data_ready', array('filter' => $f, 'lists' => $lists, 'search' => $search, 'data' => $data, 'tags' => array_values(array_unique($tags)))); -+ } -+ -+ /** -+ * Prepare and sort the given task records to be sent to the client -+ */ -+ private function tasks_data($records, $f, &$tags) -+ { - $data = $tags = $this->task_tree = $this->task_titles = array(); -- foreach ($this->driver->list_tasks($filter, $lists) as $rec) { -+ foreach ($records as $rec) { - if ($rec['parent_id']) { - $this->task_tree[$rec['id']] = $rec['parent_id']; - } -@@ -558,7 +572,7 @@ class tasklist extends rcube_plugin - array_walk($data, array($this, 'task_walk_tree')); - usort($data, array($this, 'task_sort_cmp')); - -- $this->rc->output->command('plugin.data_ready', array('filter' => $f, 'lists' => $lists, 'search' => $search, 'data' => $data, 'tags' => array_values(array_unique($tags)))); -+ return $data; - } - - /** -@@ -605,6 +619,10 @@ class tasklist extends rcube_plugin - $rec['attachments'][$k]['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']); - } - -+ if (!is_array($rec['tags'])) -+ $rec['tags'] = (array)$rec['tags']; -+ sort($rec['tags'], SORT_LOCALE_STRING); -+ - if (in_array($rec['id'], $this->collapsed_tasks)) - $rec['collapsed'] = true; - -@@ -720,6 +738,34 @@ class tasklist extends rcube_plugin - exit; - } - -+ /** -+ * Handler for keep-alive requests -+ * This will check for updated data in active lists and sync them to the client -+ */ -+ public function refresh($attr) -+ { -+ // refresh the entire list every 10th time to also sync deleted items -+ if (rand(0,10) == 10) { -+ $this->rc->output->command('plugin.reload_data'); -+ return; -+ } -+ -+ $filter = array( -+ 'since' => $attr['last'], -+ 'search' => get_input_value('q', RCUBE_INPUT_GPC), -+ 'mask' => intval(get_input_value('filter', RCUBE_INPUT_GPC)) & self::FILTER_MASK_COMPLETE, -+ ); -+ $lists = get_input_value('lists', RCUBE_INPUT_GPC);; -+ -+ $updates = $this->driver->list_tasks($filter, $lists); -+ if (!empty($updates)) { -+ $this->rc->output->command('plugin.refresh_tasks', $this->tasks_data($updates, 255, $tags), true); -+ -+ // update counts -+ $counts = $this->driver->count_tasks($lists); -+ $this->rc->output->command('plugin.update_counts', $counts); -+ } -+ } - - /** - * Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests. -diff --git a/plugins/tasklist/tasklist_base.js b/plugins/tasklist/tasklist_base.js -index e3a889c..f804c34 100644 ---- a/plugins/tasklist/tasklist_base.js -+++ b/plugins/tasklist/tasklist_base.js -@@ -4,7 +4,7 @@ - * @version @package_version@ - * @author Thomas Bruederli <bruederli@kolabsys.com> - * -- * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com> -+ * Copyright (C) 2013, 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 -@@ -37,10 +37,9 @@ function rcube_tasklist(settings) - /** - * Open a new task dialog prefilled with contents from the currently selected mail message - */ -- function create_from_mail() -+ function create_from_mail(uid) - { -- var uid; -- if ((uid = rcmail.get_single_uid())) { -+ if (uid || (uid = rcmail.get_single_uid())) { - // load calendar UI (scripts and edit dialog template) - if (!ui_loaded) { - $.when( -@@ -53,7 +52,7 @@ function rcube_tasklist(settings) - - ui_loaded = true; - me.ui = new rcube_tasklist_ui(settings); -- create_from_mail(); // start over -+ create_from_mail(uid); // start over - }); - return; - } -@@ -90,5 +89,14 @@ window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', fu - rcmail.env.message_commands.push('tasklist-create-from-mail'); - else - rcmail.enable_command('tasklist-create-from-mail', true); -+ -+ // add contextmenu item -+ if (window.rcm_contextmenu_register_command) { -+ rcm_contextmenu_register_command( -+ 'tasklist-create-from-mail', -+ function(cmd,el){ tasks.create_from_mail() }, -+ 'tasklist.createfrommail', -+ 'moveto'); -+ } - }); - -diff --git a/plugins/tasklist/tasklist_ui.php b/plugins/tasklist/tasklist_ui.php -index dab9b12..21faba3 100644 ---- a/plugins/tasklist/tasklist_ui.php -+++ b/plugins/tasklist/tasklist_ui.php -@@ -117,7 +117,7 @@ class tasklist_ui - - $li .= html::tag('li', array('id' => 'rcmlitasklist' . $html_id, 'class' => $class), - ($prop['virtual'] ? '' : html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active']))) . -- html::span('handle', ' ') . -+ html::span(array('class' => 'handle', 'title' => $this->plugin->gettext('focusview')), ' ') . - html::span(array('class' => 'listname', 'title' => $title), $prop['listname'])); - } -
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.2-2863-kolab-storage-cache.patch
Added
@@ -0,0 +1,13 @@ +diff --git a/plugins/libkolab/lib/kolab_storage_cache.php b/plugins/libkolab/lib/kolab_storage_cache.php +index 925f4fe..9c1368f 100644 +--- a/plugins/libkolab/lib/kolab_storage_cache.php ++++ b/plugins/libkolab/lib/kolab_storage_cache.php +@@ -318,7 +318,7 @@ class kolab_storage_cache + * @param mixed Hash array with object properties to save or false to delete the cache entry + * @param int Optional old message UID (for update) + */ +- public function save($msguid, $object, $olduid) ++ public function save($msguid, $object, $olduid = null) + { + // write to cache + if ($this->ready) {
View file
debian.tar.gz/patches/roundcubemail-plugins-kolab-3.2-2867-mysql-initial.patch
Added
@@ -0,0 +1,21 @@ +diff --git a/plugins/libkolab/SQL/mysql.initial.sql b/plugins/libkolab/SQL/mysql.initial.sql +index 40b8631..89b7244 100644 +--- a/plugins/libkolab/SQL/mysql.initial.sql ++++ b/plugins/libkolab/SQL/mysql.initial.sql +@@ -34,6 +34,10 @@ CREATE TABLE `kolab_cache_contact` ( + `tags` VARCHAR(255) NOT NULL, + `words` TEXT NOT NULL, + `type` VARCHAR(32) CHARACTER SET ascii NOT NULL, ++ `name` VARCHAR(255) NOT NULL, ++ `firstname` VARCHAR(255) NOT NULL, ++ `surname` VARCHAR(255) NOT NULL, ++ `email` VARCHAR(255) NOT NULL, + CONSTRAINT `fk_kolab_cache_contact_folder` FOREIGN KEY (`folder_id`) + REFERENCES `kolab_folders`(`folder_id`) ON DELETE CASCADE ON UPDATE CASCADE, + PRIMARY KEY(`folder_id`,`msguid`), +@@ -172,4 +176,4 @@ CREATE TABLE `kolab_cache_freebusy` ( + ) /*!40000 ENGINE=INNODB */ /*!40101 CHARACTER SET utf8 COLLATE utf8_general_ci */; + + +-INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2013121100'); ++INSERT INTO `system` (`name`, `value`) VALUES ('libkolab-version', '2014021000');
View file
debian.tar.gz/patches/series
Added
@@ -0,0 +1,2 @@ +roundcubemail-plugins-kolab-3.2-2863-kolab-storage-cache.patch +roundcubemail-plugins-kolab-3.2-2867-mysql-initial.patch
View file
roundcubemail-plugins-kolab.dsc
Changed
@@ -2,7 +2,7 @@ Source: roundcubemail-plugins-kolab Binary: roundcubemail-plugins-kolab Architecture: all -Version: 1:3.2~dev20140214-0~kolab2 +Version: 1:3.2~dev20140214-0~kolab3 Maintainer: Christoph Wickert <wickert@kolabsys.com> Uploaders: Jeroen van Meeuwen (Kolab Systems) <vanmeeuwen@kolabsys.com>, Paul Klos <kolab@klos2day.nl> Standards-Version: 3.9.3
Locations
Projects
Search
Status Monitor
Help
Open Build Service
OBS Manuals
API Documentation
OBS Portal
Reporting a Bug
Contact
Mailing List
Forums
Chat (IRC)
Twitter
Open Build Service (OBS)
is an
openSUSE project
.