/[smecontribs]/rpms/roundcube/contribs8/roundcube-0.9.5-tasklist.patch
ViewVC logotype

Annotation of /rpms/roundcube/contribs8/roundcube-0.9.5-tasklist.patch

Parent Directory Parent Directory | Revision Log Revision Log | View Revision Graph Revision Graph


Revision 1.1 - (hide annotations) (download)
Mon Nov 25 18:50:20 2013 UTC (10 years, 11 months ago) by unnilennium
Branch: MAIN
CVS Tags: roundcube-0_9_5-2_el5_sme, HEAD
* Sun Nov 24 2013 stephane de labrusse <stephdl@de-labrusse.fr> 0.9.5.2
- Add Tasklisk from kolab plugin

1 unnilennium 1.1 diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/config.inc.php.dist roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/config.inc.php.dist
2     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/config.inc.php.dist 1970-01-01 01:00:00.000000000 +0100
3     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/config.inc.php.dist 2013-11-24 00:38:51.000000000 +0100
4     @@ -0,0 +1,4 @@
5     +<?php
6     +
7     +$rcmail_config['tasklist_driver'] = 'kolab';
8     +
9     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/database/SQL/mysql.sql roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/database/SQL/mysql.sql
10     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/database/SQL/mysql.sql 1970-01-01 01:00:00.000000000 +0100
11     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/database/SQL/mysql.sql 2013-11-24 00:38:51.000000000 +0100
12     @@ -0,0 +1,49 @@
13     +/**
14     + * Roundcube Tasklist plugin database
15     + *
16     + * @version @package_version@
17     + * @author Thomas Bruederli
18     + * @licence GNU AGPL
19     + * @copyright (C) 2012, Kolab Systems AG
20     + */
21     +
22     +CREATE TABLE `tasklists` (
23     + `tasklist_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
24     + `user_id` int(10) unsigned NOT NULL,
25     + `name` varchar(255) NOT NULL,
26     + `color` varchar(8) NOT NULL,
27     + `showalarms` tinyint(2) unsigned NOT NULL DEFAULT '0',
28     + PRIMARY KEY (`tasklist_id`),
29     + KEY `user_id` (`user_id`),
30     + CONSTRAINT `fk_tasklist_user_id` FOREIGN KEY (`user_id`)
31     + REFERENCES `users`(`user_id`) ON DELETE CASCADE ON UPDATE CASCADE
32     +) /*!40000 ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci */;
33     +
34     +CREATE TABLE `tasks` (
35     + `task_id` int(10) unsigned NOT NULL AUTO_INCREMENT,
36     + `tasklist_id` int(10) unsigned NOT NULL,
37     + `parent_id` int(10) unsigned DEFAULT NULL,
38     + `uid` varchar(255) NOT NULL,
39     + `created` datetime NOT NULL,
40     + `changed` datetime NOT NULL,
41     + `del` tinyint(1) unsigned NOT NULL DEFAULT '0',
42     + `title` varchar(255) NOT NULL,
43     + `description` text,
44     + `tags` text,
45     + `date` varchar(10) DEFAULT NULL,
46     + `time` varchar(5) DEFAULT NULL,
47     + `startdate` varchar(10) DEFAULT NULL,
48     + `starttime` varchar(5) DEFAULT NULL,
49     + `flagged` tinyint(4) NOT NULL DEFAULT '0',
50     + `complete` float NOT NULL DEFAULT '0',
51     + `alarms` varchar(255) DEFAULT NULL,
52     + `recurrence` varchar(255) DEFAULT NULL,
53     + `organizer` varchar(255) DEFAULT NULL,
54     + `attendees` text,
55     + `notify` datetime DEFAULT NULL,
56     + PRIMARY KEY (`task_id`),
57     + KEY `tasklisting` (`tasklist_id`,`del`,`date`),
58     + KEY `uid` (`uid`),
59     + CONSTRAINT `fk_tasks_tasklist_id` FOREIGN KEY (`tasklist_id`)
60     + REFERENCES `tasklists`(`tasklist_id`) ON DELETE CASCADE ON UPDATE CASCADE
61     +) /*!40000 ENGINE=InnoDB CHARACTER SET utf8 COLLATE utf8_general_ci */;
62     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/database/tasklist_database_driver.php roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/database/tasklist_database_driver.php
63     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/database/tasklist_database_driver.php 1970-01-01 01:00:00.000000000 +0100
64     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/database/tasklist_database_driver.php 2013-11-24 00:38:51.000000000 +0100
65     @@ -0,0 +1,666 @@
66     +<?php
67     +
68     +/**
69     + * Database driver for the Tasklist plugin
70     + *
71     + * @version @package_version@
72     + * @author Thomas Bruederli <bruederli@kolabsys.com>
73     + *
74     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
75     + *
76     + * This program is free software: you can redistribute it and/or modify
77     + * it under the terms of the GNU Affero General Public License as
78     + * published by the Free Software Foundation, either version 3 of the
79     + * License, or (at your option) any later version.
80     + *
81     + * This program is distributed in the hope that it will be useful,
82     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
83     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
84     + * GNU Affero General Public License for more details.
85     + *
86     + * You should have received a copy of the GNU Affero General Public License
87     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
88     + */
89     +
90     +class tasklist_database_driver extends tasklist_driver
91     +{
92     + public $undelete = true; // yes, we can
93     + public $sortable = false;
94     + public $alarm_types = array('DISPLAY','EMAIL');
95     +
96     + private $rc;
97     + private $plugin;
98     + private $lists = array();
99     + private $list_ids = '';
100     +
101     + private $db_tasks = 'tasks';
102     + private $db_lists = 'tasklists';
103     + private $sequence_tasks = 'task_ids';
104     + private $sequence_lists = 'tasklist_ids';
105     +
106     +
107     + /**
108     + * Default constructor
109     + */
110     + public function __construct($plugin)
111     + {
112     + $this->rc = $plugin->rc;
113     + $this->plugin = $plugin;
114     +
115     + // read database config
116     + $this->db_lists = $this->rc->config->get('db_table_lists', $this->db_lists);
117     + $this->db_tasks = $this->rc->config->get('db_table_tasks', $this->db_tasks);
118     + $this->sequence_lists = $this->rc->config->get('db_sequence_lists', $this->sequence_lists);
119     + $this->sequence_tasks = $this->rc->config->get('db_sequence_tasks', $this->sequence_tasks);
120     +
121     + $this->_read_lists();
122     + }
123     +
124     + /**
125     + * Read available calendars for the current user and store them internally
126     + */
127     + private function _read_lists()
128     + {
129     + $hidden = array_filter(explode(',', $this->rc->config->get('hidden_tasklists', '')));
130     +
131     + if (!empty($this->rc->user->ID)) {
132     + $list_ids = array();
133     + $result = $this->rc->db->query(
134     + "SELECT *, tasklist_id AS id FROM " . $this->db_lists . "
135     + WHERE user_id=?
136     + ORDER BY CASE WHEN name='INBOX' THEN 0 ELSE 1 END, name",
137     + $this->rc->user->ID
138     + );
139     +
140     + while ($result && ($arr = $this->rc->db->fetch_assoc($result))) {
141     + $arr['showalarms'] = intval($arr['showalarms']);
142     + $arr['active'] = !in_array($arr['id'], $hidden);
143     + $arr['name'] = html::quote($arr['name']);
144     + $arr['editable'] = true;
145     + $this->lists[$arr['id']] = $arr;
146     + $list_ids[] = $this->rc->db->quote($arr['id']);
147     + }
148     + $this->list_ids = join(',', $list_ids);
149     + }
150     + }
151     +
152     + /**
153     + * Get a list of available tasks lists from this source
154     + */
155     + public function get_lists()
156     + {
157     + // attempt to create a default list for this user
158     + if (empty($this->lists)) {
159     + if ($this->create_list(array('name' => 'Default', 'color' => '000000')))
160     + $this->_read_lists();
161     + }
162     +
163     + return $this->lists;
164     + }
165     +
166     + /**
167     + * Create a new list assigned to the current user
168     + *
169     + * @param array Hash array with list properties
170     + * @return mixed ID of the new list on success, False on error
171     + * @see tasklist_driver::create_list()
172     + */
173     + public function create_list($prop)
174     + {
175     + $result = $this->rc->db->query(
176     + "INSERT INTO " . $this->db_lists . "
177     + (user_id, name, color, showalarms)
178     + VALUES (?, ?, ?, ?)",
179     + $this->rc->user->ID,
180     + $prop['name'],
181     + $prop['color'],
182     + $prop['showalarms']?1:0
183     + );
184     +
185     + if ($result)
186     + return $this->rc->db->insert_id($this->sequence_lists);
187     +
188     + return false;
189     + }
190     +
191     + /**
192     + * Update properties of an existing tasklist
193     + *
194     + * @param array Hash array with list properties
195     + * @return boolean True on success, Fales on failure
196     + * @see tasklist_driver::edit_list()
197     + */
198     + public function edit_list($prop)
199     + {
200     + $query = $this->rc->db->query(
201     + "UPDATE " . $this->db_lists . "
202     + SET name=?, color=?, showalarms=?
203     + WHERE tasklist_id=?
204     + AND user_id=?",
205     + $prop['name'],
206     + $prop['color'],
207     + $prop['showalarms']?1:0,
208     + $prop['id'],
209     + $this->rc->user->ID
210     + );
211     +
212     + return $this->rc->db->affected_rows($query);
213     + }
214     +
215     + /**
216     + * Set active/subscribed state of a list
217     + *
218     + * @param array Hash array with list properties
219     + * @return boolean True on success, Fales on failure
220     + * @see tasklist_driver::subscribe_list()
221     + */
222     + public function subscribe_list($prop)
223     + {
224     + $hidden = array_flip(explode(',', $this->rc->config->get('hidden_tasklists', '')));
225     +
226     + if ($prop['active'])
227     + unset($hidden[$prop['id']]);
228     + else
229     + $hidden[$prop['id']] = 1;
230     +
231     + return $this->rc->user->save_prefs(array('hidden_tasklists' => join(',', array_keys($hidden))));
232     + }
233     +
234     + /**
235     + * Delete the given list with all its contents
236     + *
237     + * @param array Hash array with list properties
238     + * @return boolean True on success, Fales on failure
239     + * @see tasklist_driver::remove_list()
240     + */
241     + public function remove_list($prop)
242     + {
243     + $list_id = $prop['id'];
244     +
245     + if ($this->lists[$list_id]) {
246     + // delete all tasks linked with this list
247     + $this->rc->db->query(
248     + "DELETE FROM " . $this->db_tasks . "
249     + WHERE tasklist_id=?",
250     + $list_id
251     + );
252     +
253     + // delete list record
254     + $query = $this->rc->db->query(
255     + "DELETE FROM " . $this->db_lists . "
256     + WHERE tasklist_id=?
257     + AND user_id=?",
258     + $list_id,
259     + $this->rc->user->ID
260     + );
261     +
262     + return $this->rc->db->affected_rows($query);
263     + }
264     +
265     + return false;
266     + }
267     +
268     + /**
269     + * Get number of tasks matching the given filter
270     + *
271     + * @param array List of lists to count tasks of
272     + * @return array Hash array with counts grouped by status (all|flagged|today|tomorrow|overdue|nodate)
273     + * @see tasklist_driver::count_tasks()
274     + */
275     + function count_tasks($lists = null)
276     + {
277     + if (empty($lists))
278     + $lists = array_keys($this->lists);
279     + else if (is_string($lists))
280     + $lists = explode(',', $lists);
281     +
282     + // only allow to select from lists of this user
283     + $list_ids = array_map(array($this->rc->db, 'quote'), array_intersect($lists, array_keys($this->lists)));
284     +
285     + $today_date = new DateTime('now', $this->plugin->timezone);
286     + $today = $today_date->format('Y-m-d');
287     + $tomorrow_date = new DateTime('now + 1 day', $this->plugin->timezone);
288     + $tomorrow = $tomorrow_date->format('Y-m-d');
289     +
290     + $result = $this->rc->db->query(sprintf(
291     + "SELECT task_id, flagged, date FROM " . $this->db_tasks . "
292     + WHERE tasklist_id IN (%s)
293     + AND del=0 AND complete<1",
294     + join(',', $list_ids)
295     + ));
296     +
297     + $counts = array('all' => 0, 'flagged' => 0, 'today' => 0, 'tomorrow' => 0, 'overdue' => 0, 'nodate' => 0);
298     + while ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
299     + $counts['all']++;
300     + if ($rec['flagged'])
301     + $counts['flagged']++;
302     + if (empty($rec['date']))
303     + $counts['nodate']++;
304     + else if ($rec['date'] == $today)
305     + $counts['today']++;
306     + else if ($rec['date'] == $tomorrow)
307     + $counts['tomorrow']++;
308     + else if ($rec['date'] < $today)
309     + $counts['overdue']++;
310     + }
311     +
312     + return $counts;
313     + }
314     +
315     + /**
316     + * Get all taks records matching the given filter
317     + *
318     + * @param array Hash array wiht filter criterias
319     + * @param array List of lists to get tasks from
320     + * @return array List of tasks records matchin the criteria
321     + * @see tasklist_driver::list_tasks()
322     + */
323     + function list_tasks($filter, $lists = null)
324     + {
325     + if (empty($lists))
326     + $lists = array_keys($this->lists);
327     + else if (is_string($lists))
328     + $lists = explode(',', $lists);
329     +
330     + // only allow to select from lists of this user
331     + $list_ids = array_map(array($this->rc->db, 'quote'), array_intersect($lists, array_keys($this->lists)));
332     + $sql_add = '';
333     +
334     + // add filter criteria
335     + if ($filter['from'] || ($filter['mask'] & tasklist::FILTER_MASK_TODAY)) {
336     + $sql_add .= ' AND (date IS NULL OR date >= ?)';
337     + $datefrom = $filter['from'];
338     + }
339     + if ($filter['to']) {
340     + if ($filter['mask'] & tasklist::FILTER_MASK_OVERDUE)
341     + $sql_add .= ' AND (date IS NOT NULL AND date <= ' . $this->rc->db->quote($filter['to']) . ')';
342     + else
343     + $sql_add .= ' AND (date IS NULL OR date <= ' . $this->rc->db->quote($filter['to']) . ')';
344     + }
345     +
346     + // special case 'today': also show all events with date before today
347     + if ($filter['mask'] & tasklist::FILTER_MASK_TODAY) {
348     + $datefrom = date('Y-m-d', 0);
349     + }
350     +
351     + if ($filter['mask'] & tasklist::FILTER_MASK_NODATE)
352     + $sql_add = ' AND date IS NULL';
353     +
354     + if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE)
355     + $sql_add .= ' AND complete=1';
356     + else // don't show complete tasks by default
357     + $sql_add .= ' AND complete<1';
358     +
359     + if ($filter['mask'] & tasklist::FILTER_MASK_FLAGGED)
360     + $sql_add .= ' AND flagged=1';
361     +
362     + // compose (slow) SQL query for searching
363     + // FIXME: improve searching using a dedicated col and normalized values
364     + if ($filter['search']) {
365     + $sql_query = array();
366     + foreach (array('title','description','organizer','attendees') as $col)
367     + $sql_query[] = $this->rc->db->ilike($col, '%'.$filter['search'].'%');
368     + $sql_add = 'AND (' . join(' OR ', $sql_query) . ')';
369     + }
370     +
371     + $tasks = array();
372     + if (!empty($list_ids)) {
373     + $result = $this->rc->db->query(sprintf(
374     + "SELECT * FROM " . $this->db_tasks . "
375     + WHERE tasklist_id IN (%s)
376     + AND del=0
377     + %s
378     + ORDER BY parent_id, task_id ASC",
379     + join(',', $list_ids),
380     + $sql_add
381     + ),
382     + $datefrom
383     + );
384     +
385     + while ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
386     + $tasks[] = $this->_read_postprocess($rec);
387     + }
388     + }
389     +
390     + return $tasks;
391     + }
392     +
393     + /**
394     + * Return data of a specific task
395     + *
396     + * @param mixed Hash array with task properties or task UID
397     + * @return array Hash array with task properties or false if not found
398     + */
399     + public function get_task($prop)
400     + {
401     + if (is_string($prop))
402     + $prop['uid'] = $prop;
403     +
404     + $query_col = $prop['id'] ? 'task_id' : 'uid';
405     +
406     + $result = $this->rc->db->query(sprintf(
407     + "SELECT * FROM " . $this->db_tasks . "
408     + WHERE tasklist_id IN (%s)
409     + AND %s=?
410     + AND del=0",
411     + $this->list_ids,
412     + $query_col
413     + ),
414     + $prop['id'] ? $prop['id'] : $prop['uid']
415     + );
416     +
417     + if ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
418     + return $this->_read_postprocess($rec);
419     + }
420     +
421     + return false;
422     + }
423     +
424     + /**
425     + * Get all decendents of the given task record
426     + *
427     + * @param mixed Hash array with task properties or task UID
428     + * @param boolean True if all childrens children should be fetched
429     + * @return array List of all child task IDs
430     + */
431     + public function get_childs($prop, $recursive = false)
432     + {
433     + // resolve UID first
434     + if (is_string($prop)) {
435     + $result = $this->rc->db->query(sprintf(
436     + "SELECT task_id AS id, tasklist_id AS list FROM " . $this->db_tasks . "
437     + WHERE tasklist_id IN (%s)
438     + AND uid=?",
439     + $this->list_ids
440     + ),
441     + $prop);
442     + $prop = $this->rc->db->fetch_assoc($result);
443     + }
444     +
445     + $childs = array();
446     + $task_ids = array($prop['id']);
447     +
448     + // query for childs (recursively)
449     + while (!empty($task_ids)) {
450     + $result = $this->rc->db->query(sprintf(
451     + "SELECT task_id AS id FROM " . $this->db_tasks . "
452     + WHERE tasklist_id IN (%s)
453     + AND parent_id IN (%s)
454     + AND del=0",
455     + $this->list_ids,
456     + join(',', array_map(array($this->rc->db, 'quote'), $task_ids))
457     + ));
458     +
459     + $task_ids = array();
460     + while ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
461     + $childs[] = $rec['id'];
462     + $task_ids[] = $rec['id'];
463     + }
464     +
465     + if (!$recursive)
466     + break;
467     + }
468     +
469     + return $childs;
470     + }
471     +
472     + /**
473     + * Get a list of pending alarms to be displayed to the user
474     + *
475     + * @param integer Current time (unix timestamp)
476     + * @param mixed List of list IDs to show alarms for (either as array or comma-separated string)
477     + * @return array A list of alarms, each encoded as hash array with task properties
478     + * @see tasklist_driver::pending_alarms()
479     + */
480     + public function pending_alarms($time, $lists = null)
481     + {
482     + if (empty($lists))
483     + $lists = array_keys($this->lists);
484     + else if (is_string($lists))
485     + $lists = explode(',', $lists);
486     +
487     + // only allow to select from calendars with activated alarms
488     + $list_ids = array();
489     + foreach ($lists as $lid) {
490     + if ($this->lists[$lid] && $this->lists[$lid]['showalarms'])
491     + $list_ids[] = $lid;
492     + }
493     + $list_ids = array_map(array($this->rc->db, 'quote'), $list_ids);
494     +
495     + $alarms = array();
496     + if (!empty($list_ids)) {
497     + $result = $this->rc->db->query(sprintf(
498     + "SELECT * FROM " . $this->db_tasks . "
499     + WHERE tasklist_id IN (%s)
500     + AND notify <= %s AND complete < 1",
501     + join(',', $list_ids),
502     + $this->rc->db->fromunixtime($time)
503     + ));
504     +
505     + while ($result && ($rec = $this->rc->db->fetch_assoc($result)))
506     + $alarms[] = $this->_read_postprocess($rec);
507     + }
508     +
509     + return $alarms;
510     + }
511     +
512     + /**
513     + * Feedback after showing/sending an alarm notification
514     + *
515     + * @see tasklist_driver::dismiss_alarm()
516     + */
517     + public function dismiss_alarm($task_id, $snooze = 0)
518     + {
519     + // set new notifyat time or unset if not snoozed
520     + $notify_at = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
521     +
522     + $query = $this->rc->db->query(sprintf(
523     + "UPDATE " . $this->db_tasks . "
524     + SET changed=%s, notify=?
525     + WHERE task_id=?
526     + AND tasklist_id IN (" . $this->list_ids . ")",
527     + $this->rc->db->now()),
528     + $notify_at,
529     + $task_id
530     + );
531     +
532     + return $this->rc->db->affected_rows($query);
533     + }
534     +
535     + /**
536     + * Map some internal database values to match the generic "API"
537     + */
538     + private function _read_postprocess($rec)
539     + {
540     + $rec['id'] = $rec['task_id'];
541     + $rec['list'] = $rec['tasklist_id'];
542     + $rec['changed'] = new DateTime($rec['changed']);
543     + $rec['tags'] = array_filter(explode(',', $rec['tags']));
544     +
545     + if (!$rec['parent_id'])
546     + unset($rec['parent_id']);
547     +
548     + unset($rec['task_id'], $rec['tasklist_id'], $rec['created']);
549     + return $rec;
550     + }
551     +
552     + /**
553     + * Add a single task to the database
554     + *
555     + * @param array Hash array with task properties (see header of this file)
556     + * @return mixed New event ID on success, False on error
557     + * @see tasklist_driver::create_task()
558     + */
559     + public function create_task($prop)
560     + {
561     + // check list permissions
562     + $list_id = $prop['list'] ? $prop['list'] : reset(array_keys($this->lists));
563     + if (!$this->lists[$list_id] || $this->lists[$list_id]['readonly'])
564     + return false;
565     +
566     + foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms') as $col) {
567     + if (empty($prop[$col]))
568     + $prop[$col] = null;
569     + }
570     +
571     + $notify_at = $this->_get_notification($prop);
572     + $result = $this->rc->db->query(sprintf(
573     + "INSERT INTO " . $this->db_tasks . "
574     + (tasklist_id, uid, parent_id, created, changed, title, date, time, startdate, starttime, description, tags, alarms, notify)
575     + VALUES (?, ?, ?, %s, %s, ?, ?, ?, ?, ?, ?, ?, ?, ?)",
576     + $this->rc->db->now(),
577     + $this->rc->db->now()
578     + ),
579     + $list_id,
580     + $prop['uid'],
581     + $prop['parent_id'],
582     + $prop['title'],
583     + $prop['date'],
584     + $prop['time'],
585     + $prop['startdate'],
586     + $prop['starttime'],
587     + strval($prop['description']),
588     + join(',', (array)$prop['tags']),
589     + $prop['alarms'],
590     + $notify_at
591     + );
592     +
593     + if ($result)
594     + return $this->rc->db->insert_id($this->sequence_tasks);
595     +
596     + return false;
597     + }
598     +
599     + /**
600     + * Update an task entry with the given data
601     + *
602     + * @param array Hash array with task properties
603     + * @return boolean True on success, False on error
604     + * @see tasklist_driver::edit_task()
605     + */
606     + public function edit_task($prop)
607     + {
608     + $sql_set = array();
609     + foreach (array('title', 'description', 'flagged', 'complete') as $col) {
610     + if (isset($prop[$col]))
611     + $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . $this->rc->db->quote($prop[$col]);
612     + }
613     + foreach (array('parent_id', 'date', 'time', 'startdate', 'starttime', 'alarms') as $col) {
614     + if (isset($prop[$col]))
615     + $sql_set[] = $this->rc->db->quote_identifier($col) . '=' . (empty($prop[$col]) ? 'NULL' : $this->rc->db->quote($prop[$col]));
616     + }
617     + if (isset($prop['tags']))
618     + $sql_set[] = $this->rc->db->quote_identifier('tags') . '=' . $this->rc->db->quote(join(',', (array)$prop['tags']));
619     +
620     + if (isset($prop['date']) || isset($prop['time']) || isset($prop['alarms'])) {
621     + $notify_at = $this->_get_notification($prop);
622     + $sql_set[] = $this->rc->db->quote_identifier('notify') . '=' . (empty($notify_at) ? 'NULL' : $this->rc->db->quote($notify_at));
623     + }
624     +
625     + // moved from another list
626     + if ($prop['_fromlist'] && ($newlist = $prop['list'])) {
627     + $sql_set[] = 'tasklist_id=' . $this->rc->db->quote($newlist);
628     + }
629     +
630     + $query = $this->rc->db->query(sprintf(
631     + "UPDATE " . $this->db_tasks . "
632     + SET changed=%s %s
633     + WHERE task_id=?
634     + AND tasklist_id IN (%s)",
635     + $this->rc->db->now(),
636     + ($sql_set ? ', ' . join(', ', $sql_set) : ''),
637     + $this->list_ids
638     + ),
639     + $prop['id']
640     + );
641     +
642     + return $this->rc->db->affected_rows($query);
643     + }
644     +
645     + /**
646     + * Move a single task to another list
647     + *
648     + * @param array Hash array with task properties:
649     + * @return boolean True on success, False on error
650     + * @see tasklist_driver::move_task()
651     + */
652     + public function move_task($prop)
653     + {
654     + return $this->edit_task($prop);
655     + }
656     +
657     + /**
658     + * Remove a single task from the database
659     + *
660     + * @param array Hash array with task properties
661     + * @param boolean Remove record irreversible
662     + * @return boolean True on success, False on error
663     + * @see tasklist_driver::delete_task()
664     + */
665     + public function delete_task($prop, $force = true)
666     + {
667     + $task_id = $prop['id'];
668     +
669     + if ($task_id && $force) {
670     + $query = $this->rc->db->query(
671     + "DELETE FROM " . $this->db_tasks . "
672     + WHERE task_id=?
673     + AND tasklist_id IN (" . $this->list_ids . ")",
674     + $task_id
675     + );
676     + }
677     + else if ($task_id) {
678     + $query = $this->rc->db->query(sprintf(
679     + "UPDATE " . $this->db_tasks . "
680     + SET changed=%s, del=1
681     + WHERE task_id=?
682     + AND tasklist_id IN (%s)",
683     + $this->rc->db->now(),
684     + $this->list_ids
685     + ),
686     + $task_id
687     + );
688     + }
689     +
690     + return $this->rc->db->affected_rows($query);
691     + }
692     +
693     + /**
694     + * Restores a single deleted task (if supported)
695     + *
696     + * @param array Hash array with task properties
697     + * @return boolean True on success, False on error
698     + * @see tasklist_driver::undelete_task()
699     + */
700     + public function undelete_task($prop)
701     + {
702     + $query = $this->rc->db->query(sprintf(
703     + "UPDATE " . $this->db_tasks . "
704     + SET changed=%s, del=0
705     + WHERE task_id=?
706     + AND tasklist_id IN (%s)",
707     + $this->rc->db->now(),
708     + $this->list_ids
709     + ),
710     + $prop['id']
711     + );
712     +
713     + return $this->rc->db->affected_rows($query);
714     + }
715     +
716     + /**
717     + * Compute absolute time to notify the user
718     + */
719     + private function _get_notification($task)
720     + {
721     + if ($task['alarms'] && $task['complete'] < 1 || strpos($task['alarms'], '@') !== false) {
722     + $alarm = libcalendaring::get_next_alarm($task, 'task');
723     +
724     + if ($alarm['time'] && $alarm['action'] == 'DISPLAY')
725     + return date('Y-m-d H:i:s', $alarm['time']);
726     + }
727     +
728     + return null;
729     + }
730     +
731     +}
732     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php
733     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php 1970-01-01 01:00:00.000000000 +0100
734     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/kolab/tasklist_kolab_driver.php 2013-11-24 00:38:51.000000000 +0100
735     @@ -0,0 +1,853 @@
736     +<?php
737     +
738     +/**
739     + * Kolab Groupware driver for the Tasklist plugin
740     + *
741     + * @version @package_version@
742     + * @author Thomas Bruederli <bruederli@kolabsys.com>
743     + *
744     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
745     + *
746     + * This program is free software: you can redistribute it and/or modify
747     + * it under the terms of the GNU Affero General Public License as
748     + * published by the Free Software Foundation, either version 3 of the
749     + * License, or (at your option) any later version.
750     + *
751     + * This program is distributed in the hope that it will be useful,
752     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
753     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
754     + * GNU Affero General Public License for more details.
755     + *
756     + * You should have received a copy of the GNU Affero General Public License
757     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
758     + */
759     +
760     +class tasklist_kolab_driver extends tasklist_driver
761     +{
762     + // features supported by the backend
763     + public $alarms = false;
764     + public $attachments = true;
765     + public $undelete = false; // task undelete action
766     + public $alarm_types = array('DISPLAY');
767     +
768     + private $rc;
769     + private $plugin;
770     + private $lists;
771     + private $folders = array();
772     + private $tasks = array();
773     +
774     +
775     + /**
776     + * Default constructor
777     + */
778     + public function __construct($plugin)
779     + {
780     + $this->rc = $plugin->rc;
781     + $this->plugin = $plugin;
782     +
783     + $this->_read_lists();
784     +
785     + if (kolab_storage::$version == '2.0') {
786     + $this->alarm_absolute = false;
787     + }
788     + }
789     +
790     + /**
791     + * Read available calendars for the current user and store them internally
792     + */
793     + private function _read_lists($force = false)
794     + {
795     + // already read sources
796     + if (isset($this->lists) && !$force)
797     + return $this->lists;
798     +
799     + // get all folders that have type "task"
800     + $this->folders = kolab_storage::get_folders('task');
801     + $this->lists = array();
802     +
803     + // convert to UTF8 and sort
804     + $names = array();
805     + $default_folder = null;
806     + foreach ($this->folders as $folder) {
807     + $names[$folder->name] = rcube_charset::convert($folder->name, 'UTF7-IMAP');
808     + $this->folders[$folder->name] = $folder;
809     + if ($folder->default)
810     + $default_folder = $folder->name;
811     + }
812     +
813     + asort($names, SORT_LOCALE_STRING);
814     +
815     + // put default folder (aka INBOX) on top of the list
816     + if ($default_folder) {
817     + $default_name = $names[$default_folder];
818     + unset($names[$default_folder]);
819     + $names = array_merge(array($default_folder => $default_name), $names);
820     + }
821     +
822     + $delim = $this->rc->get_storage()->get_hierarchy_delimiter();
823     + $listnames = array();
824     +
825     + $prefs = $this->rc->config->get('kolab_tasklists', array());
826     +
827     + foreach ($names as $utf7name => $name) {
828     + $folder = $this->folders[$utf7name];
829     +
830     + $path_imap = explode($delim, $name);
831     + $editname = array_pop($path_imap); // pop off raw name part
832     + $path_imap = join($delim, $path_imap);
833     +
834     + $name = kolab_storage::folder_displayname(kolab_storage::object_name($utf7name), $listnames);
835     +
836     + if ($folder->get_namespace() == 'personal') {
837     + $readonly = false;
838     + $alarms = true;
839     + }
840     + else {
841     + $alarms = false;
842     + $readonly = true;
843     + if (($rights = $folder->get_myrights()) && !PEAR::isError($rights)) {
844     + if (strpos($rights, 'i') !== false)
845     + $readonly = false;
846     + }
847     + }
848     +
849     + $list_id = kolab_storage::folder_id($utf7name);
850     + $tasklist = array(
851     + 'id' => $list_id,
852     + 'name' => $name,
853     + 'editname' => $editname,
854     + 'color' => $folder->get_color('0000CC'),
855     + 'showalarms' => isset($prefs[$list_id]['showalarms']) ? $prefs[$list_id]['showalarms'] : $alarms,
856     + 'editable' => !$readonly,
857     + 'active' => $folder->is_active(),
858     + 'parentfolder' => $path_imap,
859     + 'default' => $folder->default,
860     + 'class_name' => trim($folder->get_namespace() . ($folder->default ? ' default' : '')),
861     + );
862     + $this->lists[$tasklist['id']] = $tasklist;
863     + $this->folders[$tasklist['id']] = $folder;
864     + }
865     + }
866     +
867     + /**
868     + * Get a list of available task lists from this source
869     + */
870     + public function get_lists()
871     + {
872     + // attempt to create a default list for this user
873     + if (empty($this->lists)) {
874     + if ($this->create_list(array('name' => 'Tasks', 'color' => '0000CC', 'default' => true)))
875     + $this->_read_lists(true);
876     + }
877     +
878     + return $this->lists;
879     + }
880     +
881     + /**
882     + * Create a new list assigned to the current user
883     + *
884     + * @param array Hash array with list properties
885     + * name: List name
886     + * color: The color of the list
887     + * showalarms: True if alarms are enabled
888     + * @return mixed ID of the new list on success, False on error
889     + */
890     + public function create_list($prop)
891     + {
892     + $prop['type'] = 'task' . ($prop['default'] ? '.default' : '');
893     + $prop['active'] = true; // activate folder by default
894     + $folder = kolab_storage::folder_update($prop);
895     +
896     + if ($folder === false) {
897     + $this->last_error = kolab_storage::$last_error;
898     + return false;
899     + }
900     +
901     + // create ID
902     + $id = kolab_storage::folder_id($folder);
903     +
904     + $prefs['kolab_tasklists'] = $this->rc->config->get('kolab_tasklists', array());
905     +
906     + if (isset($prop['showalarms']))
907     + $prefs['kolab_tasklists'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
908     +
909     + if ($prefs['kolab_tasklists'][$id])
910     + $this->rc->user->save_prefs($prefs);
911     +
912     + return $id;
913     + }
914     +
915     + /**
916     + * Update properties of an existing tasklist
917     + *
918     + * @param array Hash array with list properties
919     + * id: List Identifier
920     + * name: List name
921     + * color: The color of the list
922     + * showalarms: True if alarms are enabled (if supported)
923     + * @return boolean True on success, Fales on failure
924     + */
925     + public function edit_list($prop)
926     + {
927     + if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
928     + $prop['oldname'] = $folder->name;
929     + $prop['type'] = 'task';
930     + $newfolder = kolab_storage::folder_update($prop);
931     +
932     + if ($newfolder === false) {
933     + $this->last_error = kolab_storage::$last_error;
934     + return false;
935     + }
936     +
937     + // create ID
938     + $id = kolab_storage::folder_id($newfolder);
939     +
940     + // fallback to local prefs
941     + $prefs['kolab_tasklists'] = $this->rc->config->get('kolab_tasklists', array());
942     + unset($prefs['kolab_tasklists'][$prop['id']]);
943     +
944     + if (isset($prop['showalarms']))
945     + $prefs['kolab_tasklists'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
946     +
947     + if ($prefs['kolab_tasklists'][$id])
948     + $this->rc->user->save_prefs($prefs);
949     +
950     + return $id;
951     + }
952     +
953     + return false;
954     + }
955     +
956     + /**
957     + * Set active/subscribed state of a list
958     + *
959     + * @param array Hash array with list properties
960     + * id: List Identifier
961     + * active: True if list is active, false if not
962     + * @return boolean True on success, Fales on failure
963     + */
964     + public function subscribe_list($prop)
965     + {
966     + if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
967     + return $folder->activate($prop['active']);
968     + }
969     + return false;
970     + }
971     +
972     + /**
973     + * Delete the given list with all its contents
974     + *
975     + * @param array Hash array with list properties
976     + * id: list Identifier
977     + * @return boolean True on success, Fales on failure
978     + */
979     + public function remove_list($prop)
980     + {
981     + if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
982     + if (kolab_storage::folder_delete($folder->name))
983     + return true;
984     + else
985     + $this->last_error = kolab_storage::$last_error;
986     + }
987     +
988     + return false;
989     + }
990     +
991     + /**
992     + * Get number of tasks matching the given filter
993     + *
994     + * @param array List of lists to count tasks of
995     + * @return array Hash array with counts grouped by status (all|flagged|completed|today|tomorrow|nodate)
996     + */
997     + public function count_tasks($lists = null)
998     + {
999     + if (empty($lists))
1000     + $lists = array_keys($this->lists);
1001     + else if (is_string($lists))
1002     + $lists = explode(',', $lists);
1003     +
1004     + $today_date = new DateTime('now', $this->plugin->timezone);
1005     + $today = $today_date->format('Y-m-d');
1006     + $tomorrow_date = new DateTime('now + 1 day', $this->plugin->timezone);
1007     + $tomorrow = $tomorrow_date->format('Y-m-d');
1008     +
1009     + $counts = array('all' => 0, 'flagged' => 0, 'today' => 0, 'tomorrow' => 0, 'overdue' => 0, 'nodate' => 0);
1010     + foreach ($lists as $list_id) {
1011     + $folder = $this->folders[$list_id];
1012     + foreach ((array)$folder->select(array(array('tags','!~','x-complete'))) as $record) {
1013     + $rec = $this->_to_rcube_task($record);
1014     +
1015     + if ($rec['complete'] >= 1.0) // don't count complete tasks
1016     + continue;
1017     +
1018     + $counts['all']++;
1019     + if ($rec['flagged'])
1020     + $counts['flagged']++;
1021     + if (empty($rec['date']))
1022     + $counts['nodate']++;
1023     + else if ($rec['date'] == $today)
1024     + $counts['today']++;
1025     + else if ($rec['date'] == $tomorrow)
1026     + $counts['tomorrow']++;
1027     + else if ($rec['date'] < $today)
1028     + $counts['overdue']++;
1029     + }
1030     + }
1031     +
1032     + return $counts;
1033     + }
1034     +
1035     + /**
1036     + * Get all taks records matching the given filter
1037     + *
1038     + * @param array Hash array with filter criterias:
1039     + * - mask: Bitmask representing the filter selection (check against tasklist::FILTER_MASK_* constants)
1040     + * - from: Date range start as string (Y-m-d)
1041     + * - to: Date range end as string (Y-m-d)
1042     + * - search: Search query string
1043     + * @param array List of lists to get tasks from
1044     + * @return array List of tasks records matchin the criteria
1045     + */
1046     + public function list_tasks($filter, $lists = null)
1047     + {
1048     + if (empty($lists))
1049     + $lists = array_keys($this->lists);
1050     + else if (is_string($lists))
1051     + $lists = explode(',', $lists);
1052     +
1053     + $results = array();
1054     +
1055     + // query Kolab storage
1056     + $query = array();
1057     + if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE)
1058     + $query[] = array('tags','~','x-complete');
1059     + else
1060     + $query[] = array('tags','!~','x-complete');
1061     +
1062     + // full text search (only works with cache enabled)
1063     + if ($filter['search']) {
1064     + $search = mb_strtolower($filter['search']);
1065     + foreach (rcube_utils::normalize_string($search, true) as $word) {
1066     + $query[] = array('words', '~', $word);
1067     + }
1068     + }
1069     +
1070     + foreach ($lists as $list_id) {
1071     + $folder = $this->folders[$list_id];
1072     + foreach ((array)$folder->select($query) as $record) {
1073     + $task = $this->_to_rcube_task($record);
1074     + $task['list'] = $list_id;
1075     +
1076     + // TODO: post-filter tasks returned from storage
1077     +
1078     + $results[] = $task;
1079     + }
1080     + }
1081     +
1082     + return $results;
1083     + }
1084     +
1085     + /**
1086     + * Return data of a specific task
1087     + *
1088     + * @param mixed Hash array with task properties or task UID
1089     + * @return array Hash array with task properties or false if not found
1090     + */
1091     + public function get_task($prop)
1092     + {
1093     + $id = is_array($prop) ? ($prop['uid'] ?: $prop['id']) : $prop;
1094     + $list_id = is_array($prop) ? $prop['list'] : null;
1095     + $folders = $list_id ? array($list_id => $this->folders[$list_id]) : $this->folders;
1096     +
1097     + // find task in the available folders
1098     + foreach ($folders as $list_id => $folder) {
1099     + if (is_numeric($list_id))
1100     + continue;
1101     + if (!$this->tasks[$id] && ($object = $folder->get_object($id))) {
1102     + $this->tasks[$id] = $this->_to_rcube_task($object);
1103     + $this->tasks[$id]['list'] = $list_id;
1104     + break;
1105     + }
1106     + }
1107     +
1108     + return $this->tasks[$id];
1109     + }
1110     +
1111     + /**
1112     + * Get all decendents of the given task record
1113     + *
1114     + * @param mixed Hash array with task properties or task UID
1115     + * @param boolean True if all childrens children should be fetched
1116     + * @return array List of all child task IDs
1117     + */
1118     + public function get_childs($prop, $recursive = false)
1119     + {
1120     + if (is_string($prop)) {
1121     + $task = $this->get_task($prop);
1122     + $prop = array('id' => $task['id'], 'list' => $task['list']);
1123     + }
1124     +
1125     + $childs = array();
1126     + $list_id = $prop['list'];
1127     + $task_ids = array($prop['id']);
1128     + $folder = $this->folders[$list_id];
1129     +
1130     + // query for childs (recursively)
1131     + while ($folder && !empty($task_ids)) {
1132     + $query_ids = array();
1133     + foreach ($task_ids as $task_id) {
1134     + $query = array(array('tags','=','x-parent:' . $task_id));
1135     + foreach ((array)$folder->select($query) as $record) {
1136     + // don't rely on kolab_storage_folder filtering
1137     + if ($record['parent_id'] == $task_id) {
1138     + $childs[] = $record['uid'];
1139     + $query_ids[] = $record['uid'];
1140     + }
1141     + }
1142     + }
1143     +
1144     + if (!$recursive)
1145     + break;
1146     +
1147     + $task_ids = $query_ids;
1148     + }
1149     +
1150     + return $childs;
1151     + }
1152     +
1153     + /**
1154     + * Get a list of pending alarms to be displayed to the user
1155     + *
1156     + * @param integer Current time (unix timestamp)
1157     + * @param mixed List of list IDs to show alarms for (either as array or comma-separated string)
1158     + * @return array A list of alarms, each encoded as hash array with task properties
1159     + * @see tasklist_driver::pending_alarms()
1160     + */
1161     + public function pending_alarms($time, $lists = null)
1162     + {
1163     + $interval = 300;
1164     + $time -= $time % 60;
1165     +
1166     + $slot = $time;
1167     + $slot -= $slot % $interval;
1168     +
1169     + $last = $time - max(60, $this->rc->config->get('refresh_interval', 0));
1170     + $last -= $last % $interval;
1171     +
1172     + // only check for alerts once in 5 minutes
1173     + if ($last == $slot)
1174     + return array();
1175     +
1176     + if ($lists && is_string($lists))
1177     + $lists = explode(',', $lists);
1178     +
1179     + $time = $slot + $interval;
1180     +
1181     + $tasks = array();
1182     + $query = array(array('tags', '=', 'x-has-alarms'), array('tags', '!=', 'x-complete'));
1183     + foreach ($this->lists as $lid => $list) {
1184     + // skip lists with alarms disabled
1185     + if (!$list['showalarms'] || ($lists && !in_array($lid, $lists)))
1186     + continue;
1187     +
1188     + $folder = $this->folders[$lid];
1189     + foreach ((array)$folder->select($query) as $record) {
1190     + if (!$record['alarms']) // don't trust query :-)
1191     + continue;
1192     +
1193     + $task = $this->_to_rcube_task($record);
1194     +
1195     + // add to list if alarm is set
1196     + $alarm = libcalendaring::get_next_alarm($task, 'task');
1197     + if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
1198     + $id = $task['id'];
1199     + $tasks[$id] = $task;
1200     + $tasks[$id]['notifyat'] = $alarm['time'];
1201     + }
1202     + }
1203     + }
1204     +
1205     + // get alarm information stored in local database
1206     + if (!empty($tasks)) {
1207     + $task_ids = array_map(array($this->rc->db, 'quote'), array_keys($tasks));
1208     + $result = $this->rc->db->query(sprintf(
1209     + "SELECT * FROM kolab_alarms
1210     + WHERE event_id IN (%s) AND user_id=?",
1211     + join(',', $task_ids),
1212     + $this->rc->db->now()
1213     + ),
1214     + $this->rc->user->ID
1215     + );
1216     +
1217     + while ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
1218     + $dbdata[$rec['event_id']] = $rec;
1219     + }
1220     + }
1221     +
1222     + $alarms = array();
1223     + foreach ($tasks as $id => $task) {
1224     + // skip dismissed
1225     + if ($dbdata[$id]['dismissed'])
1226     + continue;
1227     +
1228     + // snooze function may have shifted alarm time
1229     + $notifyat = $dbdata[$id]['notifyat'] ? strtotime($dbdata[$id]['notifyat']) : $task['notifyat'];
1230     + if ($notifyat <= $time)
1231     + $alarms[] = $task;
1232     + }
1233     +
1234     + return $alarms;
1235     + }
1236     +
1237     + /**
1238     + * (User) feedback after showing an alarm notification
1239     + * This should mark the alarm as 'shown' or snooze it for the given amount of time
1240     + *
1241     + * @param string Task identifier
1242     + * @param integer Suspend the alarm for this number of seconds
1243     + */
1244     + public function dismiss_alarm($id, $snooze = 0)
1245     + {
1246     + // delete old alarm entry
1247     + $this->rc->db->query(
1248     + "DELETE FROM kolab_alarms
1249     + WHERE event_id=? AND user_id=?",
1250     + $id,
1251     + $this->rc->user->ID
1252     + );
1253     +
1254     + // set new notifyat time or unset if not snoozed
1255     + $notifyat = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
1256     +
1257     + $query = $this->rc->db->query(
1258     + "INSERT INTO kolab_alarms
1259     + (event_id, user_id, dismissed, notifyat)
1260     + VALUES(?, ?, ?, ?)",
1261     + $id,
1262     + $this->rc->user->ID,
1263     + $snooze > 0 ? 0 : 1,
1264     + $notifyat
1265     + );
1266     +
1267     + return $this->rc->db->affected_rows($query);
1268     + }
1269     +
1270     + /**
1271     + * Convert from Kolab_Format to internal representation
1272     + */
1273     + private function _to_rcube_task($record)
1274     + {
1275     + $task = array(
1276     + 'id' => $record['uid'],
1277     + 'uid' => $record['uid'],
1278     + 'title' => $record['title'],
1279     +# 'location' => $record['location'],
1280     + 'description' => $record['description'],
1281     + 'tags' => (array)$record['categories'],
1282     + 'flagged' => $record['priority'] == 1,
1283     + 'complete' => $record['status'] == 'COMPLETED' ? 1 : floatval($record['complete'] / 100),
1284     + 'parent_id' => $record['parent_id'],
1285     + );
1286     +
1287     + // convert from DateTime to internal date format
1288     + if (is_a($record['due'], 'DateTime')) {
1289     + $task['date'] = $record['due']->format('Y-m-d');
1290     + if (!$record['due']->_dateonly)
1291     + $task['time'] = $record['due']->format('H:i');
1292     + }
1293     + // convert from DateTime to internal date format
1294     + if (is_a($record['start'], 'DateTime')) {
1295     + $task['startdate'] = $record['start']->format('Y-m-d');
1296     + if (!$record['start']->_dateonly)
1297     + $task['starttime'] = $record['start']->format('H:i');
1298     + }
1299     + if (is_a($record['dtstamp'], 'DateTime')) {
1300     + $task['changed'] = $record['dtstamp'];
1301     + }
1302     +
1303     + if ($record['alarms']) {
1304     + $task['alarms'] = $record['alarms'];
1305     + }
1306     +
1307     + if (!empty($record['_attachments'])) {
1308     + foreach ($record['_attachments'] as $key => $attachment) {
1309     + if ($attachment !== false) {
1310     + if (!$attachment['name'])
1311     + $attachment['name'] = $key;
1312     + $attachments[] = $attachment;
1313     + }
1314     + }
1315     +
1316     + $task['attachments'] = $attachments;
1317     + }
1318     +
1319     + return $task;
1320     + }
1321     +
1322     + /**
1323     + * Convert the given task record into a data structure that can be passed to kolab_storage backend for saving
1324     + * (opposite of self::_to_rcube_event())
1325     + */
1326     + private function _from_rcube_task($task, $old = array())
1327     + {
1328     + $object = $task;
1329     + $object['categories'] = (array)$task['tags'];
1330     +
1331     + if (!empty($task['date'])) {
1332     + $object['due'] = new DateTime($task['date'].' '.$task['time'], $this->plugin->timezone);
1333     + if (empty($task['time']))
1334     + $object['due']->_dateonly = true;
1335     + unset($object['date']);
1336     + }
1337     +
1338     + if (!empty($task['startdate'])) {
1339     + $object['start'] = new DateTime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone);
1340     + if (empty($task['starttime']))
1341     + $object['start']->_dateonly = true;
1342     + unset($object['startdate']);
1343     + }
1344     +
1345     + $object['complete'] = $task['complete'] * 100;
1346     + if ($task['complete'] == 1.0)
1347     + $object['status'] = 'COMPLETED';
1348     +
1349     + if ($task['flagged'])
1350     + $object['priority'] = 1;
1351     + else
1352     + $object['priority'] = $old['priority'] > 1 ? $old['priority'] : 0;
1353     +
1354     + // copy meta data (starting with _) from old object
1355     + foreach ((array)$old as $key => $val) {
1356     + if (!isset($object[$key]) && $key[0] == '_')
1357     + $object[$key] = $val;
1358     + }
1359     +
1360     + // delete existing attachment(s)
1361     + if (!empty($task['deleted_attachments'])) {
1362     + foreach ($task['deleted_attachments'] as $attachment) {
1363     + if (is_array($object['_attachments'])) {
1364     + foreach ($object['_attachments'] as $idx => $att) {
1365     + if ($att['id'] == $attachment)
1366     + $object['_attachments'][$idx] = false;
1367     + }
1368     + }
1369     + }
1370     + unset($task['deleted_attachments']);
1371     + }
1372     +
1373     + // in kolab_storage attachments are indexed by content-id
1374     + if (is_array($task['attachments'])) {
1375     + foreach ($task['attachments'] as $idx => $attachment) {
1376     + $key = null;
1377     + // Roundcube ID has nothing to do with the storage ID, remove it
1378     + if ($attachment['content']) {
1379     + unset($attachment['id']);
1380     + }
1381     + else {
1382     + foreach ((array)$old['_attachments'] as $cid => $oldatt) {
1383     + if ($oldatt && $attachment['id'] == $oldatt['id'])
1384     + $key = $cid;
1385     + }
1386     + }
1387     +
1388     + // replace existing entry
1389     + if ($key) {
1390     + $object['_attachments'][$key] = $attachment;
1391     + }
1392     + // append as new attachment
1393     + else {
1394     + $object['_attachments'][] = $attachment;
1395     + }
1396     + }
1397     +
1398     + unset($object['attachments']);
1399     + }
1400     +
1401     + unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags']);
1402     + return $object;
1403     + }
1404     +
1405     + /**
1406     + * Add a single task to the database
1407     + *
1408     + * @param array Hash array with task properties (see header of tasklist_driver.php)
1409     + * @return mixed New task ID on success, False on error
1410     + */
1411     + public function create_task($task)
1412     + {
1413     + return $this->edit_task($task);
1414     + }
1415     +
1416     + /**
1417     + * Update an task entry with the given data
1418     + *
1419     + * @param array Hash array with task properties (see header of tasklist_driver.php)
1420     + * @return boolean True on success, False on error
1421     + */
1422     + public function edit_task($task)
1423     + {
1424     + $list_id = $task['list'];
1425     + if (!$list_id || !($folder = $this->folders[$list_id]))
1426     + return false;
1427     +
1428     + // moved from another folder
1429     + if ($task['_fromlist'] && ($fromfolder = $this->folders[$task['_fromlist']])) {
1430     + if (!$fromfolder->move($task['id'], $folder->name))
1431     + return false;
1432     +
1433     + unset($task['_fromlist']);
1434     + }
1435     +
1436     + // load previous version of this task to merge
1437     + if ($task['id']) {
1438     + $old = $folder->get_object($task['id']);
1439     + if (!$old || PEAR::isError($old))
1440     + return false;
1441     +
1442     + // merge existing properties if the update isn't complete
1443     + if (!isset($task['title']) || !isset($task['complete']))
1444     + $task += $this->_to_rcube_task($old);
1445     + }
1446     +
1447     + // generate new task object from RC input
1448     + $object = $this->_from_rcube_task($task, $old);
1449     + $saved = $folder->save($object, 'task', $task['id']);
1450     +
1451     + if (!$saved) {
1452     + raise_error(array(
1453     + 'code' => 600, 'type' => 'php',
1454     + 'file' => __FILE__, 'line' => __LINE__,
1455     + 'message' => "Error saving task object to Kolab server"),
1456     + true, false);
1457     + $saved = false;
1458     + }
1459     + else {
1460     + $task = $this->_to_rcube_task($object);
1461     + $task['list'] = $list_id;
1462     + $this->tasks[$task['id']] = $task;
1463     + }
1464     +
1465     + return $saved;
1466     + }
1467     +
1468     + /**
1469     + * Move a single task to another list
1470     + *
1471     + * @param array Hash array with task properties:
1472     + * @return boolean True on success, False on error
1473     + * @see tasklist_driver::move_task()
1474     + */
1475     + public function move_task($task)
1476     + {
1477     + $list_id = $task['list'];
1478     + if (!$list_id || !($folder = $this->folders[$list_id]))
1479     + return false;
1480     +
1481     + // execute move command
1482     + if ($task['_fromlist'] && ($fromfolder = $this->folders[$task['_fromlist']])) {
1483     + return $fromfolder->move($task['id'], $folder->name);
1484     + }
1485     +
1486     + return false;
1487     + }
1488     +
1489     + /**
1490     + * Remove a single task from the database
1491     + *
1492     + * @param array Hash array with task properties:
1493     + * id: Task identifier
1494     + * @param boolean Remove record irreversible (mark as deleted otherwise, if supported by the backend)
1495     + * @return boolean True on success, False on error
1496     + */
1497     + public function delete_task($task, $force = true)
1498     + {
1499     + $list_id = $task['list'];
1500     + if (!$list_id || !($folder = $this->folders[$list_id]))
1501     + return false;
1502     +
1503     + return $folder->delete($task['id']);
1504     + }
1505     +
1506     + /**
1507     + * Restores a single deleted task (if supported)
1508     + *
1509     + * @param array Hash array with task properties:
1510     + * id: Task identifier
1511     + * @return boolean True on success, False on error
1512     + */
1513     + public function undelete_task($prop)
1514     + {
1515     + // TODO: implement this
1516     + return false;
1517     + }
1518     +
1519     +
1520     + /**
1521     + * Get attachment properties
1522     + *
1523     + * @param string $id Attachment identifier
1524     + * @param array $task Hash array with event properties:
1525     + * id: Task identifier
1526     + * list: List identifier
1527     + *
1528     + * @return array Hash array with attachment properties:
1529     + * id: Attachment identifier
1530     + * name: Attachment name
1531     + * mimetype: MIME content type of the attachment
1532     + * size: Attachment size
1533     + */
1534     + public function get_attachment($id, $task)
1535     + {
1536     + $task['uid'] = $task['id'];
1537     + $task = $this->get_task($task);
1538     +
1539     + if ($task && !empty($task['attachments'])) {
1540     + foreach ($task['attachments'] as $att) {
1541     + if ($att['id'] == $id)
1542     + return $att;
1543     + }
1544     + }
1545     +
1546     + return null;
1547     + }
1548     +
1549     + /**
1550     + * Get attachment body
1551     + *
1552     + * @param string $id Attachment identifier
1553     + * @param array $task Hash array with event properties:
1554     + * id: Task identifier
1555     + * list: List identifier
1556     + *
1557     + * @return string Attachment body
1558     + */
1559     + public function get_attachment_body($id, $task)
1560     + {
1561     + if ($storage = $this->folders[$task['list']]) {
1562     + return $storage->get_attachment($task['id'], $id);
1563     + }
1564     +
1565     + return false;
1566     + }
1567     +
1568     + /**
1569     + *
1570     + */
1571     + public function tasklist_edit_form($fieldprop)
1572     + {
1573     + $select = kolab_storage::folder_selector('task', array('name' => 'parent', 'id' => 'taskedit-parentfolder'), null);
1574     + $fieldprop['parent'] = array(
1575     + 'id' => 'taskedit-parentfolder',
1576     + 'label' => $this->plugin->gettext('parentfolder'),
1577     + 'value' => $select->show(''),
1578     + );
1579     +
1580     + $formfields = array();
1581     + foreach (array('name','parent','showalarms') as $f) {
1582     + $formfields[$f] = $fieldprop[$f];
1583     + }
1584     +
1585     + return parent::tasklist_edit_form($formfields);
1586     + }
1587     +
1588     +}
1589     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/tasklist_driver.php roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/tasklist_driver.php
1590     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/drivers/tasklist_driver.php 1970-01-01 01:00:00.000000000 +0100
1591     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/drivers/tasklist_driver.php 2013-11-24 00:38:51.000000000 +0100
1592     @@ -0,0 +1,278 @@
1593     +<?php
1594     +
1595     +/**
1596     + * Driver interface for the Tasklist plugin
1597     + *
1598     + * @version @package_version@
1599     + * @author Thomas Bruederli <bruederli@kolabsys.com>
1600     + *
1601     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
1602     + *
1603     + * This program is free software: you can redistribute it and/or modify
1604     + * it under the terms of the GNU Affero General Public License as
1605     + * published by the Free Software Foundation, either version 3 of the
1606     + * License, or (at your option) any later version.
1607     + *
1608     + * This program is distributed in the hope that it will be useful,
1609     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
1610     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
1611     + * GNU Affero General Public License for more details.
1612     + *
1613     + * You should have received a copy of the GNU Affero General Public License
1614     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
1615     + */
1616     +
1617     + /**
1618     + * Struct of an internal task object how it is passed from/to the driver classes:
1619     + *
1620     + * $task = array(
1621     + * 'id' => 'Task ID used for editing', // must be unique for the current user
1622     + * 'parent_id' => 'ID of parent task', // null if top-level task
1623     + * 'uid' => 'Unique identifier of this task',
1624     + * 'list' => 'Task list identifier to add the task to or where the task is stored',
1625     + * 'changed' => <DateTime>, // Last modification date/time of the record
1626     + * 'title' => 'Event title/summary',
1627     + * 'description' => 'Event description',
1628     + * 'tags' => array(), // List of tags for this task
1629     + * 'date' => 'Due date', // as string of format YYYY-MM-DD or null if no date is set
1630     + * 'time' => 'Due time', // as string of format hh::ii or null if no due time is set
1631     + * 'startdate' => 'Start date' // Delay start of the task until that date
1632     + * 'starttime' => 'Start time' // ...and time
1633     + * 'categories' => 'Task category',
1634     + * 'flagged' => 'Boolean value whether this record is flagged',
1635     + * 'complete' => 'Float value representing the completeness state (range 0..1)',
1636     + * 'sensitivity' => 0|1|2, // Event sensitivity (0=public, 1=private, 2=confidential)
1637     + * 'alarms' => '-15M:DISPLAY', // Reminder settings inspired by valarm definition (e.g. display alert 15 minutes before due time)
1638     + * '_fromlist' => 'List identifier where the task was stored before',
1639     + * );
1640     + */
1641     +
1642     +/**
1643     + * Driver interface for the Tasklist plugin
1644     + */
1645     +abstract class tasklist_driver
1646     +{
1647     + // features supported by the backend
1648     + public $alarms = false;
1649     + public $attachments = false;
1650     + public $undelete = false; // task undelete action
1651     + public $sortable = false;
1652     + public $alarm_types = array('DISPLAY');
1653     + public $alarm_absolute = true;
1654     + public $last_error;
1655     +
1656     + /**
1657     + * Get a list of available task lists from this source
1658     + */
1659     + abstract function get_lists();
1660     +
1661     + /**
1662     + * Create a new list assigned to the current user
1663     + *
1664     + * @param array Hash array with list properties
1665     + * name: List name
1666     + * color: The color of the list
1667     + * showalarms: True if alarms are enabled
1668     + * @return mixed ID of the new list on success, False on error
1669     + */
1670     + abstract function create_list($prop);
1671     +
1672     + /**
1673     + * Update properties of an existing tasklist
1674     + *
1675     + * @param array Hash array with list properties
1676     + * id: List Identifier
1677     + * name: List name
1678     + * color: The color of the list
1679     + * showalarms: True if alarms are enabled (if supported)
1680     + * @return boolean True on success, Fales on failure
1681     + */
1682     + abstract function edit_list($prop);
1683     +
1684     + /**
1685     + * Set active/subscribed state of a list
1686     + *
1687     + * @param array Hash array with list properties
1688     + * id: List Identifier
1689     + * active: True if list is active, false if not
1690     + * @return boolean True on success, Fales on failure
1691     + */
1692     + abstract function subscribe_list($prop);
1693     +
1694     + /**
1695     + * Delete the given list with all its contents
1696     + *
1697     + * @param array Hash array with list properties
1698     + * id: list Identifier
1699     + * @return boolean True on success, Fales on failure
1700     + */
1701     + abstract function remove_list($prop);
1702     +
1703     + /**
1704     + * Get number of tasks matching the given filter
1705     + *
1706     + * @param array List of lists to count tasks of
1707     + * @return array Hash array with counts grouped by status (all|flagged|completed|today|tomorrow|nodate)
1708     + */
1709     + abstract function count_tasks($lists = null);
1710     +
1711     + /**
1712     + * Get all taks records matching the given filter
1713     + *
1714     + * @param array Hash array with filter criterias:
1715     + * - mask: Bitmask representing the filter selection (check against tasklist::FILTER_MASK_* constants)
1716     + * - from: Date range start as string (Y-m-d)
1717     + * - to: Date range end as string (Y-m-d)
1718     + * - search: Search query string
1719     + * @param array List of lists to get tasks from
1720     + * @return array List of tasks records matchin the criteria
1721     + */
1722     + abstract function list_tasks($filter, $lists = null);
1723     +
1724     + /**
1725     + * Get a list of pending alarms to be displayed to the user
1726     + *
1727     + * @param integer Current time (unix timestamp)
1728     + * @param mixed List of list IDs to show alarms for (either as array or comma-separated string)
1729     + * @return array A list of alarms, each encoded as hash array with task properties
1730     + * id: Task identifier
1731     + * uid: Unique identifier of this task
1732     + * date: Task due date
1733     + * time: Task due time
1734     + * title: Task title/summary
1735     + */
1736     + abstract function pending_alarms($time, $lists = null);
1737     +
1738     + /**
1739     + * (User) feedback after showing an alarm notification
1740     + * This should mark the alarm as 'shown' or snooze it for the given amount of time
1741     + *
1742     + * @param string Task identifier
1743     + * @param integer Suspend the alarm for this number of seconds
1744     + */
1745     + abstract function dismiss_alarm($id, $snooze = 0);
1746     +
1747     + /**
1748     + * Return data of a specific task
1749     + *
1750     + * @param mixed Hash array with task properties or task UID
1751     + * @return array Hash array with task properties or false if not found
1752     + */
1753     + abstract public function get_task($prop);
1754     +
1755     + /**
1756     + * Get decendents of the given task record
1757     + *
1758     + * @param mixed Hash array with task properties or task UID
1759     + * @param boolean True if all childrens children should be fetched
1760     + * @return array List of all child task IDs
1761     + */
1762     + abstract public function get_childs($prop, $recursive = false);
1763     +
1764     + /**
1765     + * Add a single task to the database
1766     + *
1767     + * @param array Hash array with task properties (see header of this file)
1768     + * @return mixed New event ID on success, False on error
1769     + */
1770     + abstract function create_task($prop);
1771     +
1772     + /**
1773     + * Update an task entry with the given data
1774     + *
1775     + * @param array Hash array with task properties (see header of this file)
1776     + * @return boolean True on success, False on error
1777     + */
1778     + abstract function edit_task($prop);
1779     +
1780     + /**
1781     + * Move a single task to another list
1782     + *
1783     + * @param array Hash array with task properties:
1784     + * id: Task identifier
1785     + * list: New list identifier to move to
1786     + * _fromlist: Previous list identifier
1787     + * @return boolean True on success, False on error
1788     + */
1789     + abstract function move_task($prop);
1790     +
1791     + /**
1792     + * Remove a single task from the database
1793     + *
1794     + * @param array Hash array with task properties:
1795     + * id: Task identifier
1796     + * @param boolean Remove record irreversible (mark as deleted otherwise, if supported by the backend)
1797     + * @return boolean True on success, False on error
1798     + */
1799     + abstract function delete_task($prop, $force = true);
1800     +
1801     + /**
1802     + * Restores a single deleted task (if supported)
1803     + *
1804     + * @param array Hash array with task properties:
1805     + * id: Task identifier
1806     + * @return boolean True on success, False on error
1807     + */
1808     + public function undelete_task($prop)
1809     + {
1810     + return false;
1811     + }
1812     +
1813     + /**
1814     + * Get attachment properties
1815     + *
1816     + * @param string $id Attachment identifier
1817     + * @param array $task Hash array with event properties:
1818     + * id: Task identifier
1819     + * list: List identifier
1820     + *
1821     + * @return array Hash array with attachment properties:
1822     + * id: Attachment identifier
1823     + * name: Attachment name
1824     + * mimetype: MIME content type of the attachment
1825     + * size: Attachment size
1826     + */
1827     + public function get_attachment($id, $task) { }
1828     +
1829     + /**
1830     + * Get attachment body
1831     + *
1832     + * @param string $id Attachment identifier
1833     + * @param array $task Hash array with event properties:
1834     + * id: Task identifier
1835     + * list: List identifier
1836     + *
1837     + * @return string Attachment body
1838     + */
1839     + public function get_attachment_body($id, $task) { }
1840     +
1841     + /**
1842     + * List availabale categories
1843     + * The default implementation reads them from config/user prefs
1844     + */
1845     + public function list_categories()
1846     + {
1847     + $rcmail = rcube::get_instance();
1848     + return $rcmail->config->get('tasklist_categories', array());
1849     + }
1850     +
1851     + /**
1852     + * Build the edit/create form for lists.
1853     + * This gives the drivers the opportunity to add more list properties
1854     + *
1855     + * @param array List with form fields to be rendered
1856     + * @return string HTML content of the form
1857     + */
1858     + public function tasklist_edit_form($formfields)
1859     + {
1860     + $html = '';
1861     + foreach ($formfields as $field) {
1862     + $html .= html::div('form-section',
1863     + html::label($field['id'], $field['label']) .
1864     + $field['value']);
1865     + }
1866     +
1867     + return $html;
1868     + }
1869     +
1870     +}
1871     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/.gitignore roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/.gitignore
1872     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/.gitignore 1970-01-01 01:00:00.000000000 +0100
1873     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/.gitignore 2013-11-24 00:38:51.000000000 +0100
1874     @@ -0,0 +1 @@
1875     +config.inc.php
1876     \ Pas de fin de ligne à la fin du fichier.
1877     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/jquery.tagedit.js roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/jquery.tagedit.js
1878     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/jquery.tagedit.js 1970-01-01 01:00:00.000000000 +0100
1879     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/jquery.tagedit.js 2013-11-24 00:38:51.000000000 +0100
1880     @@ -0,0 +1,535 @@
1881     +/*
1882     +* Tagedit - jQuery Plugin
1883     +* The Plugin can be used to edit tags from a database the easy way
1884     +*
1885     +* Examples and documentation at: tagedit.webwork-albrecht.de
1886     +*
1887     +* Copyright (c) 2010 Oliver Albrecht <info@webwork-albrecht.de>
1888     +*
1889     +* License:
1890     +* This work is licensed under a MIT License
1891     +* http://www.opensource.org/licenses/mit-license.php
1892     +*
1893     +* @author Oliver Albrecht Mial: info@webwork-albrecht.de Twitter: @webworka
1894     +* @version 1.2.1 (11/2011)
1895     +* Requires: jQuery v1.4+, jQueryUI v1.8+, jQuerry.autoGrowInput
1896     +*
1897     +* Example of usage:
1898     +*
1899     +* $( "input.tag" ).tagedit();
1900     +*
1901     +* Possible options:
1902     +*
1903     +* autocompleteURL: '', // url for a autocompletion
1904     +* deleteEmptyItems: true, // Deletes items with empty value
1905     +* deletedPostfix: '-d', // will be put to the Items that are marked as delete
1906     +* addedPostfix: '-a', // will be put to the Items that are choosem from the database
1907     +* additionalListClass: '', // put a classname here if the wrapper ul shoud receive a special class
1908     +* allowEdit: true, // Switch on/off edit entries
1909     +* allowDelete: true, // Switch on/off deletion of entries. Will be ignored if allowEdit = false
1910     +* allowAdd: true, // switch on/off the creation of new entries
1911     +* direction: 'ltr' // Sets the writing direction for Outputs and Inputs
1912     +* animSpeed: 500 // Sets the animation speed for effects
1913     +* autocompleteOptions: {}, // Setting Options for the jquery UI Autocomplete (http://jqueryui.com/demos/autocomplete/)
1914     +* breakKeyCodes: [ 13, 44 ], // Sets the characters to break on to parse the tags (defaults: return, comma)
1915     +* checkNewEntriesCaseSensitive: false, // If there is a new Entry, it is checked against the autocompletion list. This Flag controlls if the check is (in-)casesensitive
1916     +* texts: { // some texts
1917     +* removeLinkTitle: 'Remove from list.',
1918     +* saveEditLinkTitle: 'Save changes.',
1919     +* deleteLinkTitle: 'Delete this tag from database.',
1920     +* deleteConfirmation: 'Are you sure to delete this entry?',
1921     +* deletedElementTitle: 'This Element will be deleted.',
1922     +* breakEditLinkTitle: 'Cancel'
1923     +* }
1924     +*/
1925     +
1926     +(function($) {
1927     +
1928     + $.fn.tagedit = function(options) {
1929     + /**
1930     + * Merge Options with defaults
1931     + */
1932     + options = $.extend(true, {
1933     + // default options here
1934     + autocompleteURL: null,
1935     + deletedPostfix: '-d',
1936     + addedPostfix: '-a',
1937     + additionalListClass: '',
1938     + allowEdit: true,
1939     + allowDelete: true,
1940     + allowAdd: true,
1941     + direction: 'ltr',
1942     + animSpeed: 500,
1943     + autocompleteOptions: {
1944     + select: function( event, ui ) {
1945     + $(this).val(ui.item.value).trigger('transformToTag', [ui.item.id]);
1946     + return false;
1947     + }
1948     + },
1949     + breakKeyCodes: [ 13, 44 ],
1950     + checkNewEntriesCaseSensitive: false,
1951     + texts: {
1952     + removeLinkTitle: 'Remove from list.',
1953     + saveEditLinkTitle: 'Save changes.',
1954     + deleteLinkTitle: 'Delete this tag from database.',
1955     + deleteConfirmation: 'Are you sure to delete this entry?',
1956     + deletedElementTitle: 'This Element will be deleted.',
1957     + breakEditLinkTitle: 'Cancel'
1958     + },
1959     + tabindex: false
1960     + }, options || {});
1961     +
1962     + // no action if there are no elements
1963     + if(this.length == 0) {
1964     + return;
1965     + }
1966     +
1967     + // set the autocompleteOptions source
1968     + if(options.autocompleteURL) {
1969     + options.autocompleteOptions.source = options.autocompleteURL;
1970     + }
1971     +
1972     + // Set the direction of the inputs
1973     + var direction= this.attr('dir');
1974     + if(direction && direction.length > 0) {
1975     + options.direction = this.attr('dir');
1976     + }
1977     +
1978     + var elements = this;
1979     +
1980     + var baseNameRegexp = new RegExp("^(.*)\\[([0-9]*?("+options.deletedPostfix+"|"+options.addedPostfix+")?)?\]$", "i");
1981     +
1982     + var baseName = elements.eq(0).attr('name').match(baseNameRegexp);
1983     + if(baseName && baseName.length == 4) {
1984     + baseName = baseName[1];
1985     + }
1986     + else {
1987     + // Elementname does not match the expected format, exit
1988     + alert('elementname dows not match the expected format (regexp: '+baseNameRegexp+')')
1989     + return;
1990     + }
1991     +
1992     + // read tabindex from source element
1993     + var ti;
1994     + if (!options.tabindex && (ti = elements.eq(0).attr('tabindex')))
1995     + options.tabindex = ti;
1996     +
1997     + // init elements
1998     + inputsToList();
1999     +
2000     + /**
2001     + * Creates the tageditinput from a list of textinputs
2002     + *
2003     + */
2004     + function inputsToList() {
2005     + var html = '<ul class="tagedit-list '+options.additionalListClass+'">';
2006     +
2007     + elements.each(function() {
2008     + var element_name = $(this).attr('name').match(baseNameRegexp);
2009     + if(element_name && element_name.length == 4 && (options.deleteEmptyItems == false || $(this).val().length > 0)) {
2010     + if(element_name[1].length > 0) {
2011     + var elementId = typeof element_name[2] != 'undefined'? element_name[2]: '';
2012     +
2013     + html += '<li class="tagedit-listelement tagedit-listelement-old">';
2014     + html += '<span dir="'+options.direction+'">' + $(this).val() + '</span>';
2015     + html += '<input type="hidden" name="'+baseName+'['+elementId+']" value="'+$(this).val()+'" />';
2016     + html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'">x</a>';
2017     + html += '</li>';
2018     + }
2019     + }
2020     + });
2021     +
2022     + // replace Elements with the list and save the list in the local variable elements
2023     + elements.last().after(html)
2024     + var newList = elements.last().next();
2025     + elements.remove();
2026     + elements = newList;
2027     +
2028     + // Check if some of the elementshav to be marked as deleted
2029     + if(options.deletedPostfix.length > 0) {
2030     + elements.find('input[name$="'+options.deletedPostfix+'\]"]').each(function() {
2031     + markAsDeleted($(this).parent());
2032     + });
2033     + }
2034     +
2035     + // put an input field at the End
2036     + // Put an empty element at the end
2037     + html = '<li class="tagedit-listelement tagedit-listelement-new">';
2038     + html += '<input type="text" name="'+baseName+'[]" value="" id="tagedit-input" disabled="disabled" class="tagedit-input-disabled" dir="'+options.direction+'"/>';
2039     + html += '</li>';
2040     + html += '</ul>';
2041     +
2042     + elements
2043     + .append(html)
2044     + .attr('tabindex', options.tabindex) // set tabindex to <ul> to recieve focus
2045     +
2046     + // Set function on the input
2047     + .find('#tagedit-input')
2048     + .attr('tabindex', options.tabindex)
2049     + .each(function() {
2050     + $(this).autoGrowInput({comfortZone: 15, minWidth: 15, maxWidth: 20000});
2051     +
2052     + // Event ist triggert in case of choosing an item from the autocomplete, or finish the input
2053     + $(this).bind('transformToTag', function(event, id) {
2054     + var oldValue = (typeof id != 'undefined' && id.length > 0);
2055     +
2056     + var checkAutocomplete = oldValue == true? false : true;
2057     + // check if the Value ist new
2058     + var isNewResult = isNew($(this).val(), checkAutocomplete);
2059     + if(isNewResult[0] === true || isNewResult[1] != null) {
2060     +
2061     + if(oldValue == false && isNewResult[1] != null) {
2062     + oldValue = true;
2063     + id = isNewResult[1];
2064     + }
2065     +
2066     + if(options.allowAdd == true || oldValue) {
2067     + // Make a new tag in front the input
2068     + html = '<li class="tagedit-listelement tagedit-listelement-old">';
2069     + html += '<span dir="'+options.direction+'">' + $(this).val() + '</span>';
2070     + var name = oldValue? baseName + '['+id+options.addedPostfix+']' : baseName + '[]';
2071     + html += '<input type="hidden" name="'+name+'" value="'+$(this).val()+'" />';
2072     + html += '<a class="tagedit-close" title="'+options.texts.removeLinkTitle+'">x</a>';
2073     + html += '</li>';
2074     +
2075     + $(this).parent().before(html);
2076     + }
2077     + }
2078     + $(this).val('');
2079     +
2080     + // close autocomplete
2081     + if(options.autocompleteOptions.source) {
2082     + $(this).autocomplete( "close" );
2083     + }
2084     +
2085     + })
2086     + .keydown(function(event) {
2087     + var code = event.keyCode > 0? event.keyCode : event.which;
2088     +
2089     + switch(code) {
2090     + case 8: // BACKSPACE
2091     + if($(this).val().length == 0) {
2092     + // delete Last Tag
2093     + var elementToRemove = elements.find('li.tagedit-listelement-old').last();
2094     + elementToRemove.fadeOut(options.animSpeed, function() {elementToRemove.remove();})
2095     + event.preventDefault();
2096     + return false;
2097     + }
2098     + break;
2099     + case 9: // TAB
2100     + if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
2101     + $(this).trigger('transformToTag');
2102     + event.preventDefault();
2103     + return false;
2104     + }
2105     + break;
2106     + }
2107     + return true;
2108     + })
2109     + .keypress(function(event) {
2110     + var code = event.keyCode > 0? event.keyCode : event.which;
2111     + if($.inArray(code, options.breakKeyCodes) > -1) {
2112     + if($(this).val().length > 0 && $('ul.ui-autocomplete #ui-active-menuitem').length == 0) {
2113     + $(this).trigger('transformToTag');
2114     + }
2115     + event.preventDefault();
2116     + return false;
2117     + }
2118     + return true;
2119     + })
2120     + .bind('paste', function(e){
2121     + var that = $(this);
2122     + if (e.type == 'paste'){
2123     + setTimeout(function(){
2124     + that.trigger('transformToTag');
2125     + }, 1);
2126     + }
2127     + })
2128     + .blur(function() {
2129     + if($(this).val().length == 0) {
2130     + // disable the field to prevent sending with the form
2131     + $(this).attr('disabled', 'disabled').addClass('tagedit-input-disabled');
2132     + }
2133     + else {
2134     + // Delete entry after a timeout
2135     + var input = $(this);
2136     + $(this).data('blurtimer', window.setTimeout(function() {input.val('');}, 500));
2137     + }
2138     + })
2139     + .focus(function() {
2140     + window.clearTimeout($(this).data('blurtimer'));
2141     + });
2142     +
2143     + if(options.autocompleteOptions.source != false) {
2144     + $(this).autocomplete(options.autocompleteOptions);
2145     + }
2146     + })
2147     + .end()
2148     + .click(function(event) {
2149     + switch(event.target.tagName) {
2150     + case 'A':
2151     + $(event.target).parent().fadeOut(options.animSpeed, function() {
2152     + $(event.target).parent().remove();
2153     + });
2154     + break;
2155     + case 'INPUT':
2156     + case 'SPAN':
2157     + case 'LI':
2158     + if($(event.target).hasClass('tagedit-listelement-deleted') == false &&
2159     + $(event.target).parent('li').hasClass('tagedit-listelement-deleted') == false) {
2160     + // Don't edit an deleted Items
2161     + return doEdit(event);
2162     + }
2163     + default:
2164     + $(this).find('#tagedit-input')
2165     + .removeAttr('disabled')
2166     + .removeClass('tagedit-input-disabled')
2167     + .focus();
2168     + }
2169     + return false;
2170     + })
2171     + // forward focus event
2172     + .focus(function(e){ $(this).click(); })
2173     + }
2174     +
2175     + /**
2176     + * Sets all Actions and events for editing an Existing Tag.
2177     + *
2178     + * @param event {object} The original Event that was given
2179     + * return {boolean}
2180     + */
2181     + function doEdit(event) {
2182     + if(options.allowEdit == false) {
2183     + // Do nothing
2184     + return;
2185     + }
2186     +
2187     + var element = event.target.tagName == 'SPAN'? $(event.target).parent() : $(event.target);
2188     +
2189     + var closeTimer = null;
2190     +
2191     + // Event that is fired if the User finishes the edit of a tag
2192     + element.bind('finishEdit', function(event, doReset) {
2193     + window.clearTimeout(closeTimer);
2194     +
2195     + var textfield = $(this).find(':text');
2196     + var isNewResult = isNew(textfield.val(), true);
2197     + if(textfield.val().length > 0 && (typeof doReset == 'undefined' || doReset === false) && (isNewResult[0] == true)) {
2198     + // This is a new Value and we do not want to do a reset. Set the new value
2199     + $(this).find(':hidden').val(textfield.val());
2200     + $(this).find('span').html(textfield.val());
2201     + }
2202     +
2203     + textfield.remove();
2204     + $(this).find('a.tagedit-save, a.tagedit-break, a.tagedit-delete, tester').remove(); // Workaround. This normaly has to be done by autogrow Plugin
2205     + $(this).removeClass('tagedit-listelement-edit').unbind('finishEdit');
2206     + return false;
2207     + });
2208     +
2209     + var hidden = element.find(':hidden');
2210     + html = '<input type="text" name="tmpinput" autocomplete="off" value="'+hidden.val()+'" class="tagedit-edit-input" dir="'+options.direction+'"/>';
2211     + html += '<a class="tagedit-save" title="'+options.texts.saveEditLinkTitle+'">o</a>';
2212     + html += '<a class="tagedit-break" title="'+options.texts.breakEditLinkTitle+'">x</a>';
2213     +
2214     + // If the Element is one from the Database, it can be deleted
2215     + if(options.allowDelete == true && element.find(':hidden').length > 0 &&
2216     + typeof element.find(':hidden').attr('name').match(baseNameRegexp)[3] != 'undefined') {
2217     + html += '<a class="tagedit-delete" title="'+options.texts.deleteLinkTitle+'">d</a>';
2218     + }
2219     +
2220     + hidden.after(html);
2221     + element
2222     + .addClass('tagedit-listelement-edit')
2223     + .find('a.tagedit-save')
2224     + .click(function() {
2225     + $(this).parent().trigger('finishEdit');
2226     + return false;
2227     + })
2228     + .end()
2229     + .find('a.tagedit-break')
2230     + .click(function() {
2231     + $(this).parent().trigger('finishEdit', [true]);
2232     + return false;
2233     + })
2234     + .end()
2235     + .find('a.tagedit-delete')
2236     + .click(function() {
2237     + window.clearTimeout(closeTimer);
2238     + if(confirm(options.texts.deleteConfirmation)) {
2239     + markAsDeleted($(this).parent());
2240     + }
2241     + else {
2242     + $(this).parent().find(':text').trigger('finishEdit', [true]);
2243     + }
2244     + return false;
2245     + })
2246     + .end()
2247     + .find(':text')
2248     + .focus()
2249     + .autoGrowInput({comfortZone: 10, minWidth: 15, maxWidth: 20000})
2250     + .keypress(function(event) {
2251     + switch(event.keyCode) {
2252     + case 13: // RETURN
2253     + event.preventDefault();
2254     + $(this).parent().trigger('finishEdit');
2255     + return false;
2256     + case 27: // ESC
2257     + event.preventDefault();
2258     + $(this).parent().trigger('finishEdit', [true]);
2259     + return false;
2260     + }
2261     + return true;
2262     + })
2263     + .blur(function() {
2264     + var that = $(this);
2265     + closeTimer = window.setTimeout(function() {that.parent().trigger('finishEdit', [true])}, 500);
2266     + });
2267     + }
2268     +
2269     + /**
2270     + * Marks a single Tag as deleted.
2271     + *
2272     + * @param element {object}
2273     + */
2274     + function markAsDeleted(element) {
2275     + element
2276     + .trigger('finishEdit', [true])
2277     + .addClass('tagedit-listelement-deleted')
2278     + .attr('title', options.deletedElementTitle);
2279     + element.find(':hidden').each(function() {
2280     + var nameEndRegexp = new RegExp('('+options.addedPostfix+'|'+options.deletedPostfix+')?\]');
2281     + var name = $(this).attr('name').replace(nameEndRegexp, options.deletedPostfix+']');
2282     + $(this).attr('name', name);
2283     + });
2284     +
2285     + }
2286     +
2287     + /**
2288     + * Checks if a tag is already choosen.
2289     + *
2290     + * @param value {string}
2291     + * @param checkAutocomplete {boolean} optional Check also the autocomplet values
2292     + * @returns {Array} First item is a boolean, telling if the item should be put to the list, second is optional the ID from autocomplete list
2293     + */
2294     + function isNew(value, checkAutocomplete) {
2295     + checkAutocomplete = typeof checkAutocomplete == 'undefined'? false : checkAutocomplete;
2296     + var autoCompleteId = null;
2297     +
2298     + var compareValue = options.checkNewEntriesCaseSensitive == true? value : value.toLowerCase();
2299     +
2300     + var isNew = true;
2301     + elements.find('li.tagedit-listelement-old input:hidden').each(function() {
2302     + var elementValue = options.checkNewEntriesCaseSensitive == true? $(this).val() : $(this).val().toLowerCase();
2303     + if(elementValue == compareValue) {
2304     + isNew = false;
2305     + }
2306     + });
2307     +
2308     + if (isNew == true && checkAutocomplete == true && options.autocompleteOptions.source != false) {
2309     + var result = [];
2310     + if ($.isArray(options.autocompleteOptions.source)) {
2311     + result = options.autocompleteOptions.source;
2312     + }
2313     + else if ($.isFunction(options.autocompleteOptions.source)) {
2314     + options.autocompleteOptions.source({term: value}, function (data) {result = data});
2315     + }
2316     + else if (typeof options.autocompleteOptions.source === "string") {
2317     + // Check also autocomplete values
2318     + var autocompleteURL = options.autocompleteOptions.source;
2319     + if (autocompleteURL.match(/\?/)) {
2320     + autocompleteURL += '&';
2321     + } else {
2322     + autocompleteURL += '?';
2323     + }
2324     + autocompleteURL += 'term=' + value;
2325     + $.ajax({
2326     + async: false,
2327     + url: autocompleteURL,
2328     + dataType: 'json',
2329     + complete: function (XMLHttpRequest, textStatus) {
2330     + result = $.parseJSON(XMLHttpRequest.responseText);
2331     + }
2332     + });
2333     + }
2334     +
2335     + // If there is an entry for that already in the autocomplete, don't use it (Check could be case sensitive or not)
2336     + for (var i = 0; i < result.length; i++) {
2337     + var label = typeof result[i] == 'string' ? result[i] : result[i].label;
2338     + if (options.checkNewEntriesCaseSensitive == false)
2339     + label = label.toLowerCase();
2340     + if (label == compareValue) {
2341     + isNew = false;
2342     + autoCompleteId = typeof result[i] == 'string' ? i : result[i].id;
2343     + break;
2344     + }
2345     + }
2346     + }
2347     +
2348     + return new Array(isNew, autoCompleteId);
2349     + }
2350     + }
2351     +})(jQuery);
2352     +
2353     +(function($){
2354     +
2355     +// jQuery autoGrowInput plugin by James Padolsey
2356     +// See related thread: http://stackoverflow.com/questions/931207/is-there-a-jquery-autogrow-plugin-for-text-fields
2357     +
2358     +$.fn.autoGrowInput = function(o) {
2359     +
2360     + o = $.extend({
2361     + maxWidth: 1000,
2362     + minWidth: 0,
2363     + comfortZone: 70
2364     + }, o);
2365     +
2366     + this.filter('input:text').each(function(){
2367     +
2368     + var minWidth = o.minWidth || $(this).width(),
2369     + val = '',
2370     + input = $(this),
2371     + testSubject = $('<tester/>').css({
2372     + position: 'absolute',
2373     + top: -9999,
2374     + left: -9999,
2375     + width: 'auto',
2376     + fontSize: input.css('fontSize'),
2377     + fontFamily: input.css('fontFamily'),
2378     + fontWeight: input.css('fontWeight'),
2379     + letterSpacing: input.css('letterSpacing'),
2380     + whiteSpace: 'nowrap'
2381     + }),
2382     + check = function() {
2383     +
2384     + if (val === (val = input.val())) {return;}
2385     +
2386     + // Enter new content into testSubject
2387     + var escaped = val.replace(/&/g, '&amp;').replace(/\s/g,'&nbsp;').replace(/</g, '&lt;').replace(/>/g, '&gt;');
2388     + testSubject.html(escaped);
2389     +
2390     + // Calculate new width + whether to change
2391     + var testerWidth = testSubject.width(),
2392     + newWidth = (testerWidth + o.comfortZone) >= minWidth ? testerWidth + o.comfortZone : minWidth,
2393     + currentWidth = input.width(),
2394     + isValidWidthChange = (newWidth < currentWidth && newWidth >= minWidth)
2395     + || (newWidth > minWidth && newWidth < o.maxWidth);
2396     +
2397     + // Animate width
2398     + if (isValidWidthChange) {
2399     + input.width(newWidth);
2400     + }
2401     +
2402     + };
2403     +
2404     + testSubject.insertAfter(input);
2405     +
2406     + $(this).bind('keyup keydown blur update', check);
2407     +
2408     + check();
2409     + });
2410     +
2411     + return this;
2412     +
2413     +};
2414     +
2415     +})(jQuery);
2416     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/LICENSE roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/LICENSE
2417     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/LICENSE 1970-01-01 01:00:00.000000000 +0100
2418     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/LICENSE 2013-11-24 00:38:51.000000000 +0100
2419     @@ -0,0 +1,661 @@
2420     + GNU AFFERO GENERAL PUBLIC LICENSE
2421     + Version 3, 19 November 2007
2422     +
2423     + Copyright (C) 2007 Free Software Foundation, Inc. <http://fsf.org/>
2424     + Everyone is permitted to copy and distribute verbatim copies
2425     + of this license document, but changing it is not allowed.
2426     +
2427     + Preamble
2428     +
2429     + The GNU Affero General Public License is a free, copyleft license for
2430     +software and other kinds of works, specifically designed to ensure
2431     +cooperation with the community in the case of network server software.
2432     +
2433     + The licenses for most software and other practical works are designed
2434     +to take away your freedom to share and change the works. By contrast,
2435     +our General Public Licenses are intended to guarantee your freedom to
2436     +share and change all versions of a program--to make sure it remains free
2437     +software for all its users.
2438     +
2439     + When we speak of free software, we are referring to freedom, not
2440     +price. Our General Public Licenses are designed to make sure that you
2441     +have the freedom to distribute copies of free software (and charge for
2442     +them if you wish), that you receive source code or can get it if you
2443     +want it, that you can change the software or use pieces of it in new
2444     +free programs, and that you know you can do these things.
2445     +
2446     + Developers that use our General Public Licenses protect your rights
2447     +with two steps: (1) assert copyright on the software, and (2) offer
2448     +you this License which gives you legal permission to copy, distribute
2449     +and/or modify the software.
2450     +
2451     + A secondary benefit of defending all users' freedom is that
2452     +improvements made in alternate versions of the program, if they
2453     +receive widespread use, become available for other developers to
2454     +incorporate. Many developers of free software are heartened and
2455     +encouraged by the resulting cooperation. However, in the case of
2456     +software used on network servers, this result may fail to come about.
2457     +The GNU General Public License permits making a modified version and
2458     +letting the public access it on a server without ever releasing its
2459     +source code to the public.
2460     +
2461     + The GNU Affero General Public License is designed specifically to
2462     +ensure that, in such cases, the modified source code becomes available
2463     +to the community. It requires the operator of a network server to
2464     +provide the source code of the modified version running there to the
2465     +users of that server. Therefore, public use of a modified version, on
2466     +a publicly accessible server, gives the public access to the source
2467     +code of the modified version.
2468     +
2469     + An older license, called the Affero General Public License and
2470     +published by Affero, was designed to accomplish similar goals. This is
2471     +a different license, not a version of the Affero GPL, but Affero has
2472     +released a new version of the Affero GPL which permits relicensing under
2473     +this license.
2474     +
2475     + The precise terms and conditions for copying, distribution and
2476     +modification follow.
2477     +
2478     + TERMS AND CONDITIONS
2479     +
2480     + 0. Definitions.
2481     +
2482     + "This License" refers to version 3 of the GNU Affero General Public License.
2483     +
2484     + "Copyright" also means copyright-like laws that apply to other kinds of
2485     +works, such as semiconductor masks.
2486     +
2487     + "The Program" refers to any copyrightable work licensed under this
2488     +License. Each licensee is addressed as "you". "Licensees" and
2489     +"recipients" may be individuals or organizations.
2490     +
2491     + To "modify" a work means to copy from or adapt all or part of the work
2492     +in a fashion requiring copyright permission, other than the making of an
2493     +exact copy. The resulting work is called a "modified version" of the
2494     +earlier work or a work "based on" the earlier work.
2495     +
2496     + A "covered work" means either the unmodified Program or a work based
2497     +on the Program.
2498     +
2499     + To "propagate" a work means to do anything with it that, without
2500     +permission, would make you directly or secondarily liable for
2501     +infringement under applicable copyright law, except executing it on a
2502     +computer or modifying a private copy. Propagation includes copying,
2503     +distribution (with or without modification), making available to the
2504     +public, and in some countries other activities as well.
2505     +
2506     + To "convey" a work means any kind of propagation that enables other
2507     +parties to make or receive copies. Mere interaction with a user through
2508     +a computer network, with no transfer of a copy, is not conveying.
2509     +
2510     + An interactive user interface displays "Appropriate Legal Notices"
2511     +to the extent that it includes a convenient and prominently visible
2512     +feature that (1) displays an appropriate copyright notice, and (2)
2513     +tells the user that there is no warranty for the work (except to the
2514     +extent that warranties are provided), that licensees may convey the
2515     +work under this License, and how to view a copy of this License. If
2516     +the interface presents a list of user commands or options, such as a
2517     +menu, a prominent item in the list meets this criterion.
2518     +
2519     + 1. Source Code.
2520     +
2521     + The "source code" for a work means the preferred form of the work
2522     +for making modifications to it. "Object code" means any non-source
2523     +form of a work.
2524     +
2525     + A "Standard Interface" means an interface that either is an official
2526     +standard defined by a recognized standards body, or, in the case of
2527     +interfaces specified for a particular programming language, one that
2528     +is widely used among developers working in that language.
2529     +
2530     + The "System Libraries" of an executable work include anything, other
2531     +than the work as a whole, that (a) is included in the normal form of
2532     +packaging a Major Component, but which is not part of that Major
2533     +Component, and (b) serves only to enable use of the work with that
2534     +Major Component, or to implement a Standard Interface for which an
2535     +implementation is available to the public in source code form. A
2536     +"Major Component", in this context, means a major essential component
2537     +(kernel, window system, and so on) of the specific operating system
2538     +(if any) on which the executable work runs, or a compiler used to
2539     +produce the work, or an object code interpreter used to run it.
2540     +
2541     + The "Corresponding Source" for a work in object code form means all
2542     +the source code needed to generate, install, and (for an executable
2543     +work) run the object code and to modify the work, including scripts to
2544     +control those activities. However, it does not include the work's
2545     +System Libraries, or general-purpose tools or generally available free
2546     +programs which are used unmodified in performing those activities but
2547     +which are not part of the work. For example, Corresponding Source
2548     +includes interface definition files associated with source files for
2549     +the work, and the source code for shared libraries and dynamically
2550     +linked subprograms that the work is specifically designed to require,
2551     +such as by intimate data communication or control flow between those
2552     +subprograms and other parts of the work.
2553     +
2554     + The Corresponding Source need not include anything that users
2555     +can regenerate automatically from other parts of the Corresponding
2556     +Source.
2557     +
2558     + The Corresponding Source for a work in source code form is that
2559     +same work.
2560     +
2561     + 2. Basic Permissions.
2562     +
2563     + All rights granted under this License are granted for the term of
2564     +copyright on the Program, and are irrevocable provided the stated
2565     +conditions are met. This License explicitly affirms your unlimited
2566     +permission to run the unmodified Program. The output from running a
2567     +covered work is covered by this License only if the output, given its
2568     +content, constitutes a covered work. This License acknowledges your
2569     +rights of fair use or other equivalent, as provided by copyright law.
2570     +
2571     + You may make, run and propagate covered works that you do not
2572     +convey, without conditions so long as your license otherwise remains
2573     +in force. You may convey covered works to others for the sole purpose
2574     +of having them make modifications exclusively for you, or provide you
2575     +with facilities for running those works, provided that you comply with
2576     +the terms of this License in conveying all material for which you do
2577     +not control copyright. Those thus making or running the covered works
2578     +for you must do so exclusively on your behalf, under your direction
2579     +and control, on terms that prohibit them from making any copies of
2580     +your copyrighted material outside their relationship with you.
2581     +
2582     + Conveying under any other circumstances is permitted solely under
2583     +the conditions stated below. Sublicensing is not allowed; section 10
2584     +makes it unnecessary.
2585     +
2586     + 3. Protecting Users' Legal Rights From Anti-Circumvention Law.
2587     +
2588     + No covered work shall be deemed part of an effective technological
2589     +measure under any applicable law fulfilling obligations under article
2590     +11 of the WIPO copyright treaty adopted on 20 December 1996, or
2591     +similar laws prohibiting or restricting circumvention of such
2592     +measures.
2593     +
2594     + When you convey a covered work, you waive any legal power to forbid
2595     +circumvention of technological measures to the extent such circumvention
2596     +is effected by exercising rights under this License with respect to
2597     +the covered work, and you disclaim any intention to limit operation or
2598     +modification of the work as a means of enforcing, against the work's
2599     +users, your or third parties' legal rights to forbid circumvention of
2600     +technological measures.
2601     +
2602     + 4. Conveying Verbatim Copies.
2603     +
2604     + You may convey verbatim copies of the Program's source code as you
2605     +receive it, in any medium, provided that you conspicuously and
2606     +appropriately publish on each copy an appropriate copyright notice;
2607     +keep intact all notices stating that this License and any
2608     +non-permissive terms added in accord with section 7 apply to the code;
2609     +keep intact all notices of the absence of any warranty; and give all
2610     +recipients a copy of this License along with the Program.
2611     +
2612     + You may charge any price or no price for each copy that you convey,
2613     +and you may offer support or warranty protection for a fee.
2614     +
2615     + 5. Conveying Modified Source Versions.
2616     +
2617     + You may convey a work based on the Program, or the modifications to
2618     +produce it from the Program, in the form of source code under the
2619     +terms of section 4, provided that you also meet all of these conditions:
2620     +
2621     + a) The work must carry prominent notices stating that you modified
2622     + it, and giving a relevant date.
2623     +
2624     + b) The work must carry prominent notices stating that it is
2625     + released under this License and any conditions added under section
2626     + 7. This requirement modifies the requirement in section 4 to
2627     + "keep intact all notices".
2628     +
2629     + c) You must license the entire work, as a whole, under this
2630     + License to anyone who comes into possession of a copy. This
2631     + License will therefore apply, along with any applicable section 7
2632     + additional terms, to the whole of the work, and all its parts,
2633     + regardless of how they are packaged. This License gives no
2634     + permission to license the work in any other way, but it does not
2635     + invalidate such permission if you have separately received it.
2636     +
2637     + d) If the work has interactive user interfaces, each must display
2638     + Appropriate Legal Notices; however, if the Program has interactive
2639     + interfaces that do not display Appropriate Legal Notices, your
2640     + work need not make them do so.
2641     +
2642     + A compilation of a covered work with other separate and independent
2643     +works, which are not by their nature extensions of the covered work,
2644     +and which are not combined with it such as to form a larger program,
2645     +in or on a volume of a storage or distribution medium, is called an
2646     +"aggregate" if the compilation and its resulting copyright are not
2647     +used to limit the access or legal rights of the compilation's users
2648     +beyond what the individual works permit. Inclusion of a covered work
2649     +in an aggregate does not cause this License to apply to the other
2650     +parts of the aggregate.
2651     +
2652     + 6. Conveying Non-Source Forms.
2653     +
2654     + You may convey a covered work in object code form under the terms
2655     +of sections 4 and 5, provided that you also convey the
2656     +machine-readable Corresponding Source under the terms of this License,
2657     +in one of these ways:
2658     +
2659     + a) Convey the object code in, or embodied in, a physical product
2660     + (including a physical distribution medium), accompanied by the
2661     + Corresponding Source fixed on a durable physical medium
2662     + customarily used for software interchange.
2663     +
2664     + b) Convey the object code in, or embodied in, a physical product
2665     + (including a physical distribution medium), accompanied by a
2666     + written offer, valid for at least three years and valid for as
2667     + long as you offer spare parts or customer support for that product
2668     + model, to give anyone who possesses the object code either (1) a
2669     + copy of the Corresponding Source for all the software in the
2670     + product that is covered by this License, on a durable physical
2671     + medium customarily used for software interchange, for a price no
2672     + more than your reasonable cost of physically performing this
2673     + conveying of source, or (2) access to copy the
2674     + Corresponding Source from a network server at no charge.
2675     +
2676     + c) Convey individual copies of the object code with a copy of the
2677     + written offer to provide the Corresponding Source. This
2678     + alternative is allowed only occasionally and noncommercially, and
2679     + only if you received the object code with such an offer, in accord
2680     + with subsection 6b.
2681     +
2682     + d) Convey the object code by offering access from a designated
2683     + place (gratis or for a charge), and offer equivalent access to the
2684     + Corresponding Source in the same way through the same place at no
2685     + further charge. You need not require recipients to copy the
2686     + Corresponding Source along with the object code. If the place to
2687     + copy the object code is a network server, the Corresponding Source
2688     + may be on a different server (operated by you or a third party)
2689     + that supports equivalent copying facilities, provided you maintain
2690     + clear directions next to the object code saying where to find the
2691     + Corresponding Source. Regardless of what server hosts the
2692     + Corresponding Source, you remain obligated to ensure that it is
2693     + available for as long as needed to satisfy these requirements.
2694     +
2695     + e) Convey the object code using peer-to-peer transmission, provided
2696     + you inform other peers where the object code and Corresponding
2697     + Source of the work are being offered to the general public at no
2698     + charge under subsection 6d.
2699     +
2700     + A separable portion of the object code, whose source code is excluded
2701     +from the Corresponding Source as a System Library, need not be
2702     +included in conveying the object code work.
2703     +
2704     + A "User Product" is either (1) a "consumer product", which means any
2705     +tangible personal property which is normally used for personal, family,
2706     +or household purposes, or (2) anything designed or sold for incorporation
2707     +into a dwelling. In determining whether a product is a consumer product,
2708     +doubtful cases shall be resolved in favor of coverage. For a particular
2709     +product received by a particular user, "normally used" refers to a
2710     +typical or common use of that class of product, regardless of the status
2711     +of the particular user or of the way in which the particular user
2712     +actually uses, or expects or is expected to use, the product. A product
2713     +is a consumer product regardless of whether the product has substantial
2714     +commercial, industrial or non-consumer uses, unless such uses represent
2715     +the only significant mode of use of the product.
2716     +
2717     + "Installation Information" for a User Product means any methods,
2718     +procedures, authorization keys, or other information required to install
2719     +and execute modified versions of a covered work in that User Product from
2720     +a modified version of its Corresponding Source. The information must
2721     +suffice to ensure that the continued functioning of the modified object
2722     +code is in no case prevented or interfered with solely because
2723     +modification has been made.
2724     +
2725     + If you convey an object code work under this section in, or with, or
2726     +specifically for use in, a User Product, and the conveying occurs as
2727     +part of a transaction in which the right of possession and use of the
2728     +User Product is transferred to the recipient in perpetuity or for a
2729     +fixed term (regardless of how the transaction is characterized), the
2730     +Corresponding Source conveyed under this section must be accompanied
2731     +by the Installation Information. But this requirement does not apply
2732     +if neither you nor any third party retains the ability to install
2733     +modified object code on the User Product (for example, the work has
2734     +been installed in ROM).
2735     +
2736     + The requirement to provide Installation Information does not include a
2737     +requirement to continue to provide support service, warranty, or updates
2738     +for a work that has been modified or installed by the recipient, or for
2739     +the User Product in which it has been modified or installed. Access to a
2740     +network may be denied when the modification itself materially and
2741     +adversely affects the operation of the network or violates the rules and
2742     +protocols for communication across the network.
2743     +
2744     + Corresponding Source conveyed, and Installation Information provided,
2745     +in accord with this section must be in a format that is publicly
2746     +documented (and with an implementation available to the public in
2747     +source code form), and must require no special password or key for
2748     +unpacking, reading or copying.
2749     +
2750     + 7. Additional Terms.
2751     +
2752     + "Additional permissions" are terms that supplement the terms of this
2753     +License by making exceptions from one or more of its conditions.
2754     +Additional permissions that are applicable to the entire Program shall
2755     +be treated as though they were included in this License, to the extent
2756     +that they are valid under applicable law. If additional permissions
2757     +apply only to part of the Program, that part may be used separately
2758     +under those permissions, but the entire Program remains governed by
2759     +this License without regard to the additional permissions.
2760     +
2761     + When you convey a copy of a covered work, you may at your option
2762     +remove any additional permissions from that copy, or from any part of
2763     +it. (Additional permissions may be written to require their own
2764     +removal in certain cases when you modify the work.) You may place
2765     +additional permissions on material, added by you to a covered work,
2766     +for which you have or can give appropriate copyright permission.
2767     +
2768     + Notwithstanding any other provision of this License, for material you
2769     +add to a covered work, you may (if authorized by the copyright holders of
2770     +that material) supplement the terms of this License with terms:
2771     +
2772     + a) Disclaiming warranty or limiting liability differently from the
2773     + terms of sections 15 and 16 of this License; or
2774     +
2775     + b) Requiring preservation of specified reasonable legal notices or
2776     + author attributions in that material or in the Appropriate Legal
2777     + Notices displayed by works containing it; or
2778     +
2779     + c) Prohibiting misrepresentation of the origin of that material, or
2780     + requiring that modified versions of such material be marked in
2781     + reasonable ways as different from the original version; or
2782     +
2783     + d) Limiting the use for publicity purposes of names of licensors or
2784     + authors of the material; or
2785     +
2786     + e) Declining to grant rights under trademark law for use of some
2787     + trade names, trademarks, or service marks; or
2788     +
2789     + f) Requiring indemnification of licensors and authors of that
2790     + material by anyone who conveys the material (or modified versions of
2791     + it) with contractual assumptions of liability to the recipient, for
2792     + any liability that these contractual assumptions directly impose on
2793     + those licensors and authors.
2794     +
2795     + All other non-permissive additional terms are considered "further
2796     +restrictions" within the meaning of section 10. If the Program as you
2797     +received it, or any part of it, contains a notice stating that it is
2798     +governed by this License along with a term that is a further
2799     +restriction, you may remove that term. If a license document contains
2800     +a further restriction but permits relicensing or conveying under this
2801     +License, you may add to a covered work material governed by the terms
2802     +of that license document, provided that the further restriction does
2803     +not survive such relicensing or conveying.
2804     +
2805     + If you add terms to a covered work in accord with this section, you
2806     +must place, in the relevant source files, a statement of the
2807     +additional terms that apply to those files, or a notice indicating
2808     +where to find the applicable terms.
2809     +
2810     + Additional terms, permissive or non-permissive, may be stated in the
2811     +form of a separately written license, or stated as exceptions;
2812     +the above requirements apply either way.
2813     +
2814     + 8. Termination.
2815     +
2816     + You may not propagate or modify a covered work except as expressly
2817     +provided under this License. Any attempt otherwise to propagate or
2818     +modify it is void, and will automatically terminate your rights under
2819     +this License (including any patent licenses granted under the third
2820     +paragraph of section 11).
2821     +
2822     + However, if you cease all violation of this License, then your
2823     +license from a particular copyright holder is reinstated (a)
2824     +provisionally, unless and until the copyright holder explicitly and
2825     +finally terminates your license, and (b) permanently, if the copyright
2826     +holder fails to notify you of the violation by some reasonable means
2827     +prior to 60 days after the cessation.
2828     +
2829     + Moreover, your license from a particular copyright holder is
2830     +reinstated permanently if the copyright holder notifies you of the
2831     +violation by some reasonable means, this is the first time you have
2832     +received notice of violation of this License (for any work) from that
2833     +copyright holder, and you cure the violation prior to 30 days after
2834     +your receipt of the notice.
2835     +
2836     + Termination of your rights under this section does not terminate the
2837     +licenses of parties who have received copies or rights from you under
2838     +this License. If your rights have been terminated and not permanently
2839     +reinstated, you do not qualify to receive new licenses for the same
2840     +material under section 10.
2841     +
2842     + 9. Acceptance Not Required for Having Copies.
2843     +
2844     + You are not required to accept this License in order to receive or
2845     +run a copy of the Program. Ancillary propagation of a covered work
2846     +occurring solely as a consequence of using peer-to-peer transmission
2847     +to receive a copy likewise does not require acceptance. However,
2848     +nothing other than this License grants you permission to propagate or
2849     +modify any covered work. These actions infringe copyright if you do
2850     +not accept this License. Therefore, by modifying or propagating a
2851     +covered work, you indicate your acceptance of this License to do so.
2852     +
2853     + 10. Automatic Licensing of Downstream Recipients.
2854     +
2855     + Each time you convey a covered work, the recipient automatically
2856     +receives a license from the original licensors, to run, modify and
2857     +propagate that work, subject to this License. You are not responsible
2858     +for enforcing compliance by third parties with this License.
2859     +
2860     + An "entity transaction" is a transaction transferring control of an
2861     +organization, or substantially all assets of one, or subdividing an
2862     +organization, or merging organizations. If propagation of a covered
2863     +work results from an entity transaction, each party to that
2864     +transaction who receives a copy of the work also receives whatever
2865     +licenses to the work the party's predecessor in interest had or could
2866     +give under the previous paragraph, plus a right to possession of the
2867     +Corresponding Source of the work from the predecessor in interest, if
2868     +the predecessor has it or can get it with reasonable efforts.
2869     +
2870     + You may not impose any further restrictions on the exercise of the
2871     +rights granted or affirmed under this License. For example, you may
2872     +not impose a license fee, royalty, or other charge for exercise of
2873     +rights granted under this License, and you may not initiate litigation
2874     +(including a cross-claim or counterclaim in a lawsuit) alleging that
2875     +any patent claim is infringed by making, using, selling, offering for
2876     +sale, or importing the Program or any portion of it.
2877     +
2878     + 11. Patents.
2879     +
2880     + A "contributor" is a copyright holder who authorizes use under this
2881     +License of the Program or a work on which the Program is based. The
2882     +work thus licensed is called the contributor's "contributor version".
2883     +
2884     + A contributor's "essential patent claims" are all patent claims
2885     +owned or controlled by the contributor, whether already acquired or
2886     +hereafter acquired, that would be infringed by some manner, permitted
2887     +by this License, of making, using, or selling its contributor version,
2888     +but do not include claims that would be infringed only as a
2889     +consequence of further modification of the contributor version. For
2890     +purposes of this definition, "control" includes the right to grant
2891     +patent sublicenses in a manner consistent with the requirements of
2892     +this License.
2893     +
2894     + Each contributor grants you a non-exclusive, worldwide, royalty-free
2895     +patent license under the contributor's essential patent claims, to
2896     +make, use, sell, offer for sale, import and otherwise run, modify and
2897     +propagate the contents of its contributor version.
2898     +
2899     + In the following three paragraphs, a "patent license" is any express
2900     +agreement or commitment, however denominated, not to enforce a patent
2901     +(such as an express permission to practice a patent or covenant not to
2902     +sue for patent infringement). To "grant" such a patent license to a
2903     +party means to make such an agreement or commitment not to enforce a
2904     +patent against the party.
2905     +
2906     + If you convey a covered work, knowingly relying on a patent license,
2907     +and the Corresponding Source of the work is not available for anyone
2908     +to copy, free of charge and under the terms of this License, through a
2909     +publicly available network server or other readily accessible means,
2910     +then you must either (1) cause the Corresponding Source to be so
2911     +available, or (2) arrange to deprive yourself of the benefit of the
2912     +patent license for this particular work, or (3) arrange, in a manner
2913     +consistent with the requirements of this License, to extend the patent
2914     +license to downstream recipients. "Knowingly relying" means you have
2915     +actual knowledge that, but for the patent license, your conveying the
2916     +covered work in a country, or your recipient's use of the covered work
2917     +in a country, would infringe one or more identifiable patents in that
2918     +country that you have reason to believe are valid.
2919     +
2920     + If, pursuant to or in connection with a single transaction or
2921     +arrangement, you convey, or propagate by procuring conveyance of, a
2922     +covered work, and grant a patent license to some of the parties
2923     +receiving the covered work authorizing them to use, propagate, modify
2924     +or convey a specific copy of the covered work, then the patent license
2925     +you grant is automatically extended to all recipients of the covered
2926     +work and works based on it.
2927     +
2928     + A patent license is "discriminatory" if it does not include within
2929     +the scope of its coverage, prohibits the exercise of, or is
2930     +conditioned on the non-exercise of one or more of the rights that are
2931     +specifically granted under this License. You may not convey a covered
2932     +work if you are a party to an arrangement with a third party that is
2933     +in the business of distributing software, under which you make payment
2934     +to the third party based on the extent of your activity of conveying
2935     +the work, and under which the third party grants, to any of the
2936     +parties who would receive the covered work from you, a discriminatory
2937     +patent license (a) in connection with copies of the covered work
2938     +conveyed by you (or copies made from those copies), or (b) primarily
2939     +for and in connection with specific products or compilations that
2940     +contain the covered work, unless you entered into that arrangement,
2941     +or that patent license was granted, prior to 28 March 2007.
2942     +
2943     + Nothing in this License shall be construed as excluding or limiting
2944     +any implied license or other defenses to infringement that may
2945     +otherwise be available to you under applicable patent law.
2946     +
2947     + 12. No Surrender of Others' Freedom.
2948     +
2949     + If conditions are imposed on you (whether by court order, agreement or
2950     +otherwise) that contradict the conditions of this License, they do not
2951     +excuse you from the conditions of this License. If you cannot convey a
2952     +covered work so as to satisfy simultaneously your obligations under this
2953     +License and any other pertinent obligations, then as a consequence you may
2954     +not convey it at all. For example, if you agree to terms that obligate you
2955     +to collect a royalty for further conveying from those to whom you convey
2956     +the Program, the only way you could satisfy both those terms and this
2957     +License would be to refrain entirely from conveying the Program.
2958     +
2959     + 13. Remote Network Interaction; Use with the GNU General Public License.
2960     +
2961     + Notwithstanding any other provision of this License, if you modify the
2962     +Program, your modified version must prominently offer all users
2963     +interacting with it remotely through a computer network (if your version
2964     +supports such interaction) an opportunity to receive the Corresponding
2965     +Source of your version by providing access to the Corresponding Source
2966     +from a network server at no charge, through some standard or customary
2967     +means of facilitating copying of software. This Corresponding Source
2968     +shall include the Corresponding Source for any work covered by version 3
2969     +of the GNU General Public License that is incorporated pursuant to the
2970     +following paragraph.
2971     +
2972     + Notwithstanding any other provision of this License, you have
2973     +permission to link or combine any covered work with a work licensed
2974     +under version 3 of the GNU General Public License into a single
2975     +combined work, and to convey the resulting work. The terms of this
2976     +License will continue to apply to the part which is the covered work,
2977     +but the work with which it is combined will remain governed by version
2978     +3 of the GNU General Public License.
2979     +
2980     + 14. Revised Versions of this License.
2981     +
2982     + The Free Software Foundation may publish revised and/or new versions of
2983     +the GNU Affero General Public License from time to time. Such new versions
2984     +will be similar in spirit to the present version, but may differ in detail to
2985     +address new problems or concerns.
2986     +
2987     + Each version is given a distinguishing version number. If the
2988     +Program specifies that a certain numbered version of the GNU Affero General
2989     +Public License "or any later version" applies to it, you have the
2990     +option of following the terms and conditions either of that numbered
2991     +version or of any later version published by the Free Software
2992     +Foundation. If the Program does not specify a version number of the
2993     +GNU Affero General Public License, you may choose any version ever published
2994     +by the Free Software Foundation.
2995     +
2996     + If the Program specifies that a proxy can decide which future
2997     +versions of the GNU Affero General Public License can be used, that proxy's
2998     +public statement of acceptance of a version permanently authorizes you
2999     +to choose that version for the Program.
3000     +
3001     + Later license versions may give you additional or different
3002     +permissions. However, no additional obligations are imposed on any
3003     +author or copyright holder as a result of your choosing to follow a
3004     +later version.
3005     +
3006     + 15. Disclaimer of Warranty.
3007     +
3008     + THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY
3009     +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT
3010     +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY
3011     +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO,
3012     +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
3013     +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM
3014     +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF
3015     +ALL NECESSARY SERVICING, REPAIR OR CORRECTION.
3016     +
3017     + 16. Limitation of Liability.
3018     +
3019     + IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING
3020     +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS
3021     +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY
3022     +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE
3023     +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF
3024     +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD
3025     +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS),
3026     +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF
3027     +SUCH DAMAGES.
3028     +
3029     + 17. Interpretation of Sections 15 and 16.
3030     +
3031     + If the disclaimer of warranty and limitation of liability provided
3032     +above cannot be given local legal effect according to their terms,
3033     +reviewing courts shall apply local law that most closely approximates
3034     +an absolute waiver of all civil liability in connection with the
3035     +Program, unless a warranty or assumption of liability accompanies a
3036     +copy of the Program in return for a fee.
3037     +
3038     + END OF TERMS AND CONDITIONS
3039     +
3040     + How to Apply These Terms to Your New Programs
3041     +
3042     + If you develop a new program, and you want it to be of the greatest
3043     +possible use to the public, the best way to achieve this is to make it
3044     +free software which everyone can redistribute and change under these terms.
3045     +
3046     + To do so, attach the following notices to the program. It is safest
3047     +to attach them to the start of each source file to most effectively
3048     +state the exclusion of warranty; and each file should have at least
3049     +the "copyright" line and a pointer to where the full notice is found.
3050     +
3051     + <one line to give the program's name and a brief idea of what it does.>
3052     + Copyright (C) <year> <name of author>
3053     +
3054     + This program is free software: you can redistribute it and/or modify
3055     + it under the terms of the GNU Affero General Public License as published by
3056     + the Free Software Foundation, either version 3 of the License, or
3057     + (at your option) any later version.
3058     +
3059     + This program is distributed in the hope that it will be useful,
3060     + but WITHOUT ANY WARRANTY; without even the implied warranty of
3061     + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
3062     + GNU Affero General Public License for more details.
3063     +
3064     + You should have received a copy of the GNU Affero General Public License
3065     + along with this program. If not, see <http://www.gnu.org/licenses/>.
3066     +
3067     +Also add information on how to contact you by electronic and paper mail.
3068     +
3069     + If your software can interact with users remotely through a computer
3070     +network, you should also make sure that it provides a way for users to
3071     +get its source. For example, if your program is a web application, its
3072     +interface could display a "Source" link that leads users to an archive
3073     +of the code. There are many ways you could offer source, and different
3074     +solutions will be better for different programs; see section 13 for the
3075     +specific requirements.
3076     +
3077     + You should also get your employer (if you work as a programmer) or school,
3078     +if any, to sign a "copyright disclaimer" for the program, if necessary.
3079     +For more information on this, and how to apply and follow the GNU AGPL, see
3080     +<http://www.gnu.org/licenses/>.
3081     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/cs_CZ.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/cs_CZ.inc
3082     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/cs_CZ.inc 1970-01-01 01:00:00.000000000 +0100
3083     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/cs_CZ.inc 2013-11-24 00:38:51.000000000 +0100
3084     @@ -0,0 +1,68 @@
3085     +<?php
3086     +
3087     +$labels = array();
3088     +$labels['navtitle'] = 'Úkoly';
3089     +$labels['lists'] = 'Seznamy úkolů';
3090     +$labels['list'] = 'Seznam úkolů';
3091     +$labels['tags'] = 'Å títky';
3092     +
3093     +$labels['newtask'] = 'Nový úkol';
3094     +$labels['createnewtask'] = 'Vytvořit nový úkol (např. sobota, posekat trávník)';
3095     +$labels['createfrommail'] = 'Uložit úkol';
3096     +$labels['mark'] = 'Označit';
3097     +$labels['unmark'] = 'Odznačit';
3098     +$labels['edit'] = 'Upravit';
3099     +$labels['delete'] = 'Smazat';
3100     +$labels['title'] = 'Titulek';
3101     +$labels['description'] = 'Popis';
3102     +$labels['datetime'] = 'Konec';
3103     +$labels['start'] = 'Začátek';
3104     +$labels['alarms'] = 'Připomenutí';
3105     +
3106     +$labels['all'] = 'VÅ¡echny';
3107     +$labels['flagged'] = 'Označkované';
3108     +$labels['complete'] = 'Dokončené';
3109     +$labels['overdue'] = 'Zpožděné';
3110     +$labels['today'] = 'Dnes';
3111     +$labels['tomorrow'] = 'Zítra';
3112     +$labels['next7days'] = 'Příštích 7 dnů';
3113     +$labels['later'] = 'Později';
3114     +$labels['nodate'] = 'Žádné datum';
3115     +$labels['removetag'] = 'Odstranit';
3116     +
3117     +$labels['taskdetails'] = 'Podrobnosti';
3118     +$labels['newtask'] = 'Nový úkol';
3119     +$labels['edittask'] = 'Upravit úkol';
3120     +$labels['save'] = 'Uložit';
3121     +$labels['cancel'] = 'Storno';
3122     +$labels['addsubtask'] = 'Přidat podúkol';
3123     +$labels['deletetask'] = 'Smazat úkol';
3124     +$labels['deletethisonly'] = 'Smazat pouze tento úkol';
3125     +$labels['deletewithchilds'] = 'Smazat se vÅ¡emi podúkoly';
3126     +
3127     +$labels['tabsummary'] = 'Souhrn';
3128     +$labels['tabrecurrence'] = 'Opakování';
3129     +$labels['tabattachments'] = 'Přílohy';
3130     +$labels['tabsharing'] = 'Sdílení';
3131     +
3132     +$labels['editlist'] = 'Upravit seznam';
3133     +$labels['createlist'] = 'Přidat seznam';
3134     +$labels['listactions'] = 'Možnosti seznamu...';
3135     +$labels['listname'] = 'Název';
3136     +$labels['showalarms'] = 'Ukázat upozornění';
3137     +$labels['import'] = 'Import';
3138     +
3139     +// date words
3140     +$labels['on'] = ' ';
3141     +$labels['at'] = ' ';
3142     +$labels['this'] = 'tento';
3143     +$labels['next'] = 'příští';
3144     +
3145     +// messages
3146     +$labels['savingdata'] = 'Ukládám data...';
3147     +$labels['errorsaving'] = 'Nelze uložit data.';
3148     +$labels['notasksfound'] = 'Nebyly nalezeny žádné úkoly vyhovující daným kritériím';
3149     +$labels['invalidstartduedates'] = 'Počáteční datum nesmí být pozdější než koncové datum.';
3150     +$labels['deletetasktconfirm'] = 'Opravdu chcete smazat tento úkol?';
3151     +$labels['deleteparenttasktconfirm'] = 'Opravdu chcete smazat tento úkol a vÅ¡echny jeho podúkoly?';
3152     +$labels['deletelistconfirm'] = 'Opravdu chcete smazat tento seznam včetně vÅ¡ech úkolů?';
3153     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/de_CH.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/de_CH.inc
3154     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/de_CH.inc 1970-01-01 01:00:00.000000000 +0100
3155     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/de_CH.inc 2013-11-24 00:38:51.000000000 +0100
3156     @@ -0,0 +1,68 @@
3157     +<?php
3158     +
3159     +$labels = array();
3160     +$labels['navtitle'] = 'Aufgaben';
3161     +$labels['lists'] = 'Aufgabenlisten';
3162     +$labels['list'] = 'Liste';
3163     +$labels['tags'] = 'Tags';
3164     +
3165     +$labels['newtask'] = 'Neue Aufgabe';
3166     +$labels['createnewtask'] = 'Neue Aufgabe eingeben (z.B. Samstag, Rasenmähen)';
3167     +$labels['createfrommail'] = 'Als Aufgabe speichern';
3168     +$labels['mark'] = 'Markieren';
3169     +$labels['unmark'] = 'Markierung aufheben';
3170     +$labels['edit'] = 'Bearbeiten';
3171     +$labels['delete'] = 'Löschen';
3172     +$labels['title'] = 'Titel';
3173     +$labels['description'] = 'Beschreibung';
3174     +$labels['datetime'] = 'Fällig';
3175     +$labels['start'] = 'Beginn';
3176     +$labels['alarms'] = 'Erinnerung';
3177     +
3178     +$labels['all'] = 'Alle';
3179     +$labels['flagged'] = 'Markiert';
3180     +$labels['complete'] = 'Erledigt';
3181     +$labels['overdue'] = 'Überfällig';
3182     +$labels['today'] = 'Heute';
3183     +$labels['tomorrow'] = 'Morgen';
3184     +$labels['next7days'] = 'Nächste 7 Tage';
3185     +$labels['later'] = 'Später';
3186     +$labels['nodate'] = 'kein Datum';
3187     +$labels['removetag'] = 'Löschen';
3188     +
3189     +$labels['taskdetails'] = 'Details';
3190     +$labels['newtask'] = 'Neue Aufgabe';
3191     +$labels['edittask'] = 'Aufgabe bearbeiten';
3192     +$labels['save'] = 'Speichern';
3193     +$labels['cancel'] = 'Abbrechen';
3194     +$labels['addsubtask'] = 'Neue Teilaufgabe';
3195     +$labels['deletetask'] = 'Aufgabe löschen';
3196     +$labels['deletethisonly'] = 'Nur diese Aufgabe löschen';
3197     +$labels['deletewithchilds'] = 'Mit allen Teilaufhaben löschen';
3198     +
3199     +$labels['tabsummary'] = 'Übersicht';
3200     +$labels['tabrecurrence'] = 'Wiederholung';
3201     +$labels['tabattachments'] = 'Anhänge';
3202     +$labels['tabsharing'] = 'Freigabe';
3203     +
3204     +$labels['editlist'] = 'Liste bearbeiten';
3205     +$labels['createlist'] = 'Neue Liste';
3206     +$labels['listactions'] = 'Listenoptionen...';
3207     +$labels['listname'] = 'Name';
3208     +$labels['showalarms'] = 'Erinnerungen anzeigen';
3209     +$labels['import'] = 'Importieren';
3210     +
3211     +// date words
3212     +$labels['on'] = 'am';
3213     +$labels['at'] = 'um';
3214     +$labels['this'] = 'diesen';
3215     +$labels['next'] = 'nächsten';
3216     +
3217     +// mesages
3218     +$labels['savingdata'] = 'Daten werden gespeichert...';
3219     +$labels['errorsaving'] = 'Fehler beim Speichern.';
3220     +$labels['notasksfound'] = 'Für die aktuellen Kriterien wurden keine Aufgaben gefunden.';
3221     +$labels['invalidstartduedates'] = 'Beginn der Aufgabe darf nicht grösser als das Enddatum sein.';
3222     +$labels['deletetasktconfirm'] = 'Möchten Sie diese Aufgabe wirklich löschen?';
3223     +$labels['deleteparenttasktconfirm'] = 'Möchten Sie diese Aufgabe inklusive aller Teilaufgaben wirklich löschen?';
3224     +$labels['deletelistconfirm'] = 'Möchten Sie diese Liste mit allen Aufgaben wirklich löschen?';
3225     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/de_DE.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/de_DE.inc
3226     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/de_DE.inc 1970-01-01 01:00:00.000000000 +0100
3227     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/de_DE.inc 2013-11-24 00:38:51.000000000 +0100
3228     @@ -0,0 +1,69 @@
3229     +<?php
3230     +
3231     +$labels = array();
3232     +$labels['navtitle'] = 'Aufgaben';
3233     +$labels['lists'] = 'Aufgabenlisten';
3234     +$labels['list'] = 'Liste';
3235     +$labels['tags'] = 'Tags';
3236     +
3237     +$labels['newtask'] = 'Neue Aufgabe';
3238     +$labels['createnewtask'] = 'Neue Aufgabe eingeben (z.B. Samstag, Rasenmähen)';
3239     +$labels['createfrommail'] = 'Als Aufgabe speichern';
3240     +$labels['mark'] = 'Markieren';
3241     +$labels['unmark'] = 'Markierung aufheben';
3242     +$labels['edit'] = 'Bearbeiten';
3243     +$labels['delete'] = 'Löschen';
3244     +$labels['title'] = 'Titel';
3245     +$labels['description'] = 'Beschreibung';
3246     +$labels['datetime'] = 'Fällig';
3247     +$labels['start'] = 'Beginn';
3248     +$labels['alarms'] = 'Erinnerung';
3249     +
3250     +$labels['all'] = 'Alle';
3251     +$labels['flagged'] = 'Markiert';
3252     +$labels['complete'] = 'Erledigt';
3253     +$labels['overdue'] = 'Überfällig';
3254     +$labels['today'] = 'Heute';
3255     +$labels['tomorrow'] = 'Morgen';
3256     +$labels['next7days'] = 'Nächste 7 Tage';
3257     +$labels['later'] = 'Später';
3258     +$labels['nodate'] = 'kein Datum';
3259     +$labels['removetag'] = 'Löschen';
3260     +
3261     +$labels['taskdetails'] = 'Details';
3262     +$labels['newtask'] = 'Neue Aufgabe';
3263     +$labels['edittask'] = 'Aufgabe bearbeiten';
3264     +$labels['save'] = 'Speichern';
3265     +$labels['cancel'] = 'Abbrechen';
3266     +$labels['addsubtask'] = 'Neue Teilaufgabe';
3267     +$labels['deletetask'] = 'Aufgabe löschen';
3268     +$labels['deletethisonly'] = 'Nur diese Aufgabe löschen';
3269     +$labels['deletewithchilds'] = 'Mit allen Teilaufhaben löschen';
3270     +
3271     +
3272     +$labels['tabsummary'] = 'Übersicht';
3273     +$labels['tabrecurrence'] = 'Wiederholung';
3274     +$labels['tabattachments'] = 'Anhänge';
3275     +$labels['tabsharing'] = 'Freigabe';
3276     +
3277     +$labels['editlist'] = 'Liste bearbeiten';
3278     +$labels['createlist'] = 'Neue Liste';
3279     +$labels['listactions'] = 'Listenoptionen...';
3280     +$labels['listname'] = 'Name';
3281     +$labels['showalarms'] = 'Erinnerungen anzeigen';
3282     +$labels['import'] = 'Importieren';
3283     +
3284     +// date words
3285     +$labels['on'] = 'am';
3286     +$labels['at'] = 'um';
3287     +$labels['this'] = 'diesen';
3288     +$labels['next'] = 'nächsten';
3289     +
3290     +// mesages
3291     +$labels['savingdata'] = 'Daten werden gespeichert...';
3292     +$labels['errorsaving'] = 'Fehler beim Speichern.';
3293     +$labels['notasksfound'] = 'Für die aktuellen Kriterien wurden keine Aufgaben gefunden.';
3294     +$labels['invalidstartduedates'] = 'Beginn der Aufgabe darf nicht größer als das Enddatum sein.';
3295     +$labels['deletetasktconfirm'] = 'Möchten Sie diese Aufgabe wirklich löschen?';
3296     +$labels['deleteparenttasktconfirm'] = 'Möchten Sie diese Aufgabe inklusive aller Teilaufgaben wirklich löschen?';
3297     +$labels['deletelistconfirm'] = 'Möchten Sie diese Liste mit allen Aufgaben wirklich löschen?';
3298     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/en_US.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/en_US.inc
3299     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/en_US.inc 1970-01-01 01:00:00.000000000 +0100
3300     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/en_US.inc 2013-11-24 00:38:51.000000000 +0100
3301     @@ -0,0 +1,68 @@
3302     +<?php
3303     +
3304     +$labels = array();
3305     +$labels['navtitle'] = 'Tasks';
3306     +$labels['lists'] = 'Tasklists';
3307     +$labels['list'] = 'Tasklist';
3308     +$labels['tags'] = 'Tags';
3309     +
3310     +$labels['newtask'] = 'New Task';
3311     +$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
3312     +$labels['createfrommail'] = 'Save as task';
3313     +$labels['mark'] = 'Mark';
3314     +$labels['unmark'] = 'Unmark';
3315     +$labels['edit'] = 'Edit';
3316     +$labels['delete'] = 'Delete';
3317     +$labels['title'] = 'Title';
3318     +$labels['description'] = 'Description';
3319     +$labels['datetime'] = 'Due';
3320     +$labels['start'] = 'Start';
3321     +$labels['alarms'] = 'Reminder';
3322     +
3323     +$labels['all'] = 'All';
3324     +$labels['flagged'] = 'Flagged';
3325     +$labels['complete'] = 'Complete';
3326     +$labels['overdue'] = 'Overdue';
3327     +$labels['today'] = 'Today';
3328     +$labels['tomorrow'] = 'Tomorrow';
3329     +$labels['next7days'] = 'Next 7 days';
3330     +$labels['later'] = 'Later';
3331     +$labels['nodate'] = 'no date';
3332     +$labels['removetag'] = 'Remove';
3333     +
3334     +$labels['taskdetails'] = 'Details';
3335     +$labels['newtask'] = 'New Task';
3336     +$labels['edittask'] = 'Edit Task';
3337     +$labels['save'] = 'Save';
3338     +$labels['cancel'] = 'Cancel';
3339     +$labels['addsubtask'] = 'Add subtask';
3340     +$labels['deletetask'] = 'Delete task';
3341     +$labels['deletethisonly'] = 'Delete this task only';
3342     +$labels['deletewithchilds'] = 'Delete with all subtasks';
3343     +
3344     +$labels['tabsummary'] = 'Summary';
3345     +$labels['tabrecurrence'] = 'Recurrence';
3346     +$labels['tabattachments'] = 'Attachments';
3347     +$labels['tabsharing'] = 'Sharing';
3348     +
3349     +$labels['editlist'] = 'Edit list';
3350     +$labels['createlist'] = 'Add list';
3351     +$labels['listactions'] = 'List options...';
3352     +$labels['listname'] = 'Name';
3353     +$labels['showalarms'] = 'Show alarms';
3354     +$labels['import'] = 'Import';
3355     +
3356     +// date words
3357     +$labels['on'] = 'on';
3358     +$labels['at'] = 'at';
3359     +$labels['this'] = 'this';
3360     +$labels['next'] = 'next';
3361     +
3362     +// mesages
3363     +$labels['savingdata'] = 'Saving data...';
3364     +$labels['errorsaving'] = 'Failed to save data.';
3365     +$labels['notasksfound'] = 'No tasks found for the given criteria';
3366     +$labels['invalidstartduedates'] = 'Start date must not be greater than due date.';
3367     +$labels['deletetasktconfirm'] = 'Do you really want to delete this task?';
3368     +$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?';
3369     +$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
3370     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/es_ES.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/es_ES.inc
3371     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/es_ES.inc 1970-01-01 01:00:00.000000000 +0100
3372     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/es_ES.inc 2013-11-24 00:38:51.000000000 +0100
3373     @@ -0,0 +1,68 @@
3374     +<?php
3375     +
3376     +$labels = array();
3377     +$labels['navtitle'] = 'Tareas';
3378     +$labels['lists'] = 'Tasklists';
3379     +$labels['list'] = 'Tasklist';
3380     +$labels['tags'] = 'Tags';
3381     +
3382     +$labels['newtask'] = 'New Task';
3383     +$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
3384     +$labels['createfrommail'] = 'Save as task';
3385     +$labels['mark'] = 'Mark';
3386     +$labels['unmark'] = 'Unmark';
3387     +$labels['edit'] = 'Edit';
3388     +$labels['delete'] = 'Borrar';
3389     +$labels['title'] = 'Título';
3390     +$labels['description'] = 'Description';
3391     +$labels['datetime'] = 'Date/Time';
3392     +$labels['start'] = 'Start';
3393     +$labels['alarms'] = 'Reminder';
3394     +
3395     +$labels['all'] = 'All';
3396     +$labels['flagged'] = 'Flagged';
3397     +$labels['complete'] = 'Complete';
3398     +$labels['overdue'] = 'Overdue';
3399     +$labels['today'] = 'Today';
3400     +$labels['tomorrow'] = 'Tomorrow';
3401     +$labels['next7days'] = 'Next 7 days';
3402     +$labels['later'] = 'Later';
3403     +$labels['nodate'] = 'no date';
3404     +$labels['removetag'] = 'Remove';
3405     +
3406     +$labels['taskdetails'] = 'Details';
3407     +$labels['newtask'] = 'New Task';
3408     +$labels['edittask'] = 'Edit Task';
3409     +$labels['save'] = 'Guardar';
3410     +$labels['cancel'] = 'Cancel';
3411     +$labels['addsubtask'] = 'Add subtask';
3412     +$labels['deletetask'] = 'Delete task';
3413     +$labels['deletethisonly'] = 'Delete this task only';
3414     +$labels['deletewithchilds'] = 'Delete with all subtasks';
3415     +
3416     +$labels['tabsummary'] = 'Summary';
3417     +$labels['tabrecurrence'] = 'Recurrence';
3418     +$labels['tabattachments'] = 'Attachments';
3419     +$labels['tabsharing'] = 'Sharing';
3420     +
3421     +$labels['editlist'] = 'Edit list';
3422     +$labels['createlist'] = 'Add list';
3423     +$labels['listactions'] = 'List options...';
3424     +$labels['listname'] = 'Nombre';
3425     +$labels['showalarms'] = 'Show alarms';
3426     +$labels['import'] = 'Import';
3427     +
3428     +// date words
3429     +$labels['on'] = 'on';
3430     +$labels['at'] = 'at';
3431     +$labels['this'] = 'this';
3432     +$labels['next'] = 'next';
3433     +
3434     +// mesages
3435     +$labels['savingdata'] = 'Guardando datos...';
3436     +$labels['errorsaving'] = 'Failed to save data.';
3437     +$labels['notasksfound'] = 'No tasks found for the given criteria';
3438     +$labels['invalidstartduedates'] = 'Start date must not be greater than due date.';
3439     +$labels['deletetasktconfirm'] = 'Do you really want to delete this task?';
3440     +$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?';
3441     +$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
3442     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/et_EE.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/et_EE.inc
3443     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/et_EE.inc 1970-01-01 01:00:00.000000000 +0100
3444     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/et_EE.inc 2013-11-24 00:38:51.000000000 +0100
3445     @@ -0,0 +1,68 @@
3446     +<?php
3447     +
3448     +$labels = array();
3449     +$labels['navtitle'] = 'Tasks';
3450     +$labels['lists'] = 'Tasklists';
3451     +$labels['list'] = 'Tasklist';
3452     +$labels['tags'] = 'Tags';
3453     +
3454     +$labels['newtask'] = 'New Task';
3455     +$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
3456     +$labels['createfrommail'] = 'Save as task';
3457     +$labels['mark'] = 'Mark';
3458     +$labels['unmark'] = 'Unmark';
3459     +$labels['edit'] = 'Edit';
3460     +$labels['delete'] = 'Kustuta';
3461     +$labels['title'] = 'Pealkiri';
3462     +$labels['description'] = 'Description';
3463     +$labels['datetime'] = 'Date/Time';
3464     +$labels['start'] = 'Start';
3465     +$labels['alarms'] = 'Reminder';
3466     +
3467     +$labels['all'] = 'All';
3468     +$labels['flagged'] = 'Flagged';
3469     +$labels['complete'] = 'Complete';
3470     +$labels['overdue'] = 'Overdue';
3471     +$labels['today'] = 'Today';
3472     +$labels['tomorrow'] = 'Tomorrow';
3473     +$labels['next7days'] = 'Next 7 days';
3474     +$labels['later'] = 'Later';
3475     +$labels['nodate'] = 'no date';
3476     +$labels['removetag'] = 'Remove';
3477     +
3478     +$labels['taskdetails'] = 'Details';
3479     +$labels['newtask'] = 'New Task';
3480     +$labels['edittask'] = 'Edit Task';
3481     +$labels['save'] = 'Salvesta';
3482     +$labels['cancel'] = 'Cancel';
3483     +$labels['addsubtask'] = 'Add subtask';
3484     +$labels['deletetask'] = 'Delete task';
3485     +$labels['deletethisonly'] = 'Delete this task only';
3486     +$labels['deletewithchilds'] = 'Delete with all subtasks';
3487     +
3488     +$labels['tabsummary'] = 'Summary';
3489     +$labels['tabrecurrence'] = 'Recurrence';
3490     +$labels['tabattachments'] = 'Attachments';
3491     +$labels['tabsharing'] = 'Sharing';
3492     +
3493     +$labels['editlist'] = 'Edit list';
3494     +$labels['createlist'] = 'Add list';
3495     +$labels['listactions'] = 'List options...';
3496     +$labels['listname'] = 'Nimi';
3497     +$labels['showalarms'] = 'Show alarms';
3498     +$labels['import'] = 'Import';
3499     +
3500     +// date words
3501     +$labels['on'] = 'on';
3502     +$labels['at'] = 'at';
3503     +$labels['this'] = 'this';
3504     +$labels['next'] = 'next';
3505     +
3506     +// mesages
3507     +$labels['savingdata'] = 'Saving data...';
3508     +$labels['errorsaving'] = 'Failed to save data.';
3509     +$labels['notasksfound'] = 'No tasks found for the given criteria';
3510     +$labels['invalidstartduedates'] = 'Start date must not be greater than due date.';
3511     +$labels['deletetasktconfirm'] = 'Do you really want to delete this task?';
3512     +$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?';
3513     +$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
3514     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/fr_FR.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/fr_FR.inc
3515     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/fr_FR.inc 1970-01-01 01:00:00.000000000 +0100
3516     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/fr_FR.inc 2013-11-24 00:38:51.000000000 +0100
3517     @@ -0,0 +1,68 @@
3518     +<?php
3519     +
3520     +$labels = array();
3521     +$labels['navtitle'] = 'Tâches';
3522     +$labels['lists'] = 'Liste des tâches';
3523     +$labels['list'] = 'Liste de tâche';
3524     +$labels['tags'] = 'Étiquettes';
3525     +
3526     +$labels['newtask'] = 'Nouvelle tâche';
3527     +$labels['createnewtask'] = 'Créer une nouvelle tâche (ex : Samedi, tondre la pelouse)';
3528     +$labels['createfrommail'] = 'Enregistrer la tâche';
3529     +$labels['mark'] = 'Sélectionner';
3530     +$labels['unmark'] = 'Désélectionner';
3531     +$labels['edit'] = 'Modifier';
3532     +$labels['delete'] = 'Supprimet';
3533     +$labels['title'] = 'Titre';
3534     +$labels['description'] = 'Description';
3535     +$labels['datetime'] = 'Date/heure';
3536     +$labels['start'] = 'Début';
3537     +$labels['alarms'] = 'Rappel';
3538     +
3539     +$labels['all'] = 'Tous';
3540     +$labels['flagged'] = 'Marqué';
3541     +$labels['complete'] = 'Terminé';
3542     +$labels['overdue'] = 'En retard';
3543     +$labels['today'] = 'Aujourd\'hui';
3544     +$labels['tomorrow'] = 'Demain';
3545     +$labels['next7days'] = '7 prochains jours';
3546     +$labels['later'] = 'Plus tard';
3547     +$labels['nodate'] = 'Pas de date';
3548     +$labels['removetag'] = 'Supprimer';
3549     +
3550     +$labels['taskdetails'] = 'Détails';
3551     +$labels['newtask'] = 'Nouvelle tâche';
3552     +$labels['edittask'] = 'Modifier la tâche';
3553     +$labels['save'] = 'Enregistrer';
3554     +$labels['cancel'] = 'Annuler';
3555     +$labels['addsubtask'] = 'Ajouter une sous-tâche';
3556     +$labels['deletetask'] = 'Supprimer la tâche';
3557     +$labels['deletethisonly'] = 'Supprimer seulement cette tâche';
3558     +$labels['deletewithchilds'] = 'Supprimer avec toutes les sous-tâches';
3559     +
3560     +$labels['tabsummary'] = 'Résumé';
3561     +$labels['tabrecurrence'] = 'Récurrence';
3562     +$labels['tabattachments'] = 'Pièces jointes';
3563     +$labels['tabsharing'] = 'Partage';
3564     +
3565     +$labels['editlist'] = 'Modifier une liste';
3566     +$labels['createlist'] = 'Ajouter une liste';
3567     +$labels['listactions'] = 'Liste des options';
3568     +$labels['listname'] = 'Nom';
3569     +$labels['showalarms'] = 'Montrer les alarmes';
3570     +$labels['import'] = 'Importer';
3571     +
3572     +// date words
3573     +$labels['on'] = 'sur';
3574     +$labels['at'] = 'à';
3575     +$labels['this'] = 'ce';
3576     +$labels['next'] = 'suivant';
3577     +
3578     +// mesages
3579     +$labels['savingdata'] = 'Enregistrer...';
3580     +$labels['errorsaving'] = 'Échec de l\'enregistrement';
3581     +$labels['notasksfound'] = 'Pas de tâche trouvé avec les critères sélectionnés';
3582     +$labels['invalidstartduedates'] = 'La date de début ne doit pas être supérieur à la date de fin';
3583     +$labels['deletetasktconfirm'] = 'Êtes-vous sur de vouloir supprimer cette tâche?';
3584     +$labels['deleteparenttasktconfirm'] = 'Êtes-vous sur de vouloir supprimer cette tâche et ses sous-tâches?';
3585     +$labels['deletelistconfirm'] = 'Êtes-vous sur de vouloir supprimer cette liste et toutes ses tâches?';
3586     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/ja_JP.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/ja_JP.inc
3587     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/ja_JP.inc 1970-01-01 01:00:00.000000000 +0100
3588     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/ja_JP.inc 2013-11-24 00:38:52.000000000 +0100
3589     @@ -0,0 +1,68 @@
3590     +<?php
3591     +
3592     +$labels = array();
3593     +$labels['navtitle'] = 'Tasks';
3594     +$labels['lists'] = 'Tasklists';
3595     +$labels['list'] = 'Tasklist';
3596     +$labels['tags'] = 'Tags';
3597     +
3598     +$labels['newtask'] = 'New Task';
3599     +$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
3600     +$labels['createfrommail'] = 'Save as task';
3601     +$labels['mark'] = 'Mark';
3602     +$labels['unmark'] = 'Unmark';
3603     +$labels['edit'] = 'Edit';
3604     +$labels['delete'] = 'Delete';
3605     +$labels['title'] = 'Title';
3606     +$labels['description'] = 'Description';
3607     +$labels['datetime'] = 'Date/Time';
3608     +$labels['start'] = 'Start';
3609     +$labels['alarms'] = 'Reminder';
3610     +
3611     +$labels['all'] = 'All';
3612     +$labels['flagged'] = 'Flagged';
3613     +$labels['complete'] = 'Complete';
3614     +$labels['overdue'] = 'Overdue';
3615     +$labels['today'] = 'Today';
3616     +$labels['tomorrow'] = 'Tomorrow';
3617     +$labels['next7days'] = 'Next 7 days';
3618     +$labels['later'] = 'Later';
3619     +$labels['nodate'] = 'no date';
3620     +$labels['removetag'] = 'Remove';
3621     +
3622     +$labels['taskdetails'] = 'Details';
3623     +$labels['newtask'] = 'New Task';
3624     +$labels['edittask'] = 'Edit Task';
3625     +$labels['save'] = 'Save';
3626     +$labels['cancel'] = 'Cancel';
3627     +$labels['addsubtask'] = 'Add subtask';
3628     +$labels['deletetask'] = 'Delete task';
3629     +$labels['deletethisonly'] = 'Delete this task only';
3630     +$labels['deletewithchilds'] = 'Delete with all subtasks';
3631     +
3632     +$labels['tabsummary'] = 'Summary';
3633     +$labels['tabrecurrence'] = 'Recurrence';
3634     +$labels['tabattachments'] = 'Attachments';
3635     +$labels['tabsharing'] = 'Sharing';
3636     +
3637     +$labels['editlist'] = 'Edit list';
3638     +$labels['createlist'] = 'Add list';
3639     +$labels['listactions'] = 'List options...';
3640     +$labels['listname'] = 'Name';
3641     +$labels['showalarms'] = 'Show alarms';
3642     +$labels['import'] = 'Import';
3643     +
3644     +// date words
3645     +$labels['on'] = 'on';
3646     +$labels['at'] = 'at';
3647     +$labels['this'] = 'this';
3648     +$labels['next'] = 'next';
3649     +
3650     +// mesages
3651     +$labels['savingdata'] = 'Saving data...';
3652     +$labels['errorsaving'] = 'Failed to save data.';
3653     +$labels['notasksfound'] = 'No tasks found for the given criteria';
3654     +$labels['invalidstartduedates'] = 'Start date must not be greater than due date.';
3655     +$labels['deletetasktconfirm'] = 'Do you really want to delete this task?';
3656     +$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?';
3657     +$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
3658     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/nl_NL.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/nl_NL.inc
3659     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/nl_NL.inc 1970-01-01 01:00:00.000000000 +0100
3660     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/nl_NL.inc 2013-11-24 00:38:52.000000000 +0100
3661     @@ -0,0 +1,68 @@
3662     +<?php
3663     +
3664     +$labels = array();
3665     +$labels['navtitle'] = 'Taken';
3666     +$labels['lists'] = 'Takenlijsten';
3667     +$labels['list'] = 'Takenlijst';
3668     +$labels['tags'] = 'Tags';
3669     +
3670     +$labels['newtask'] = 'Nieuwe Taak';
3671     +$labels['createnewtask'] = 'Creer een nieuwe Taak (bijv. Zaterdag, gras maaien)';
3672     +$labels['createfrommail'] = 'Als taak opslaan';
3673     +$labels['mark'] = 'Markeren';
3674     +$labels['unmark'] = 'Demarkeren';
3675     +$labels['edit'] = 'Wijzigen';
3676     +$labels['delete'] = 'Verwijderen';
3677     +$labels['title'] = 'Titel';
3678     +$labels['description'] = 'Beschrijving';
3679     +$labels['datetime'] = 'Datum/Tijd';
3680     +$labels['start'] = 'Begin';
3681     +$labels['alarms'] = 'Herinnering';
3682     +
3683     +$labels['all'] = 'Alle';
3684     +$labels['flagged'] = 'Gemarkeerd';
3685     +$labels['complete'] = 'Compleet';
3686     +$labels['overdue'] = 'Achterstallig';
3687     +$labels['today'] = 'Vandaag';
3688     +$labels['tomorrow'] = 'Morgen';
3689     +$labels['next7days'] = 'Deze week';
3690     +$labels['later'] = 'Later';
3691     +$labels['nodate'] = 'geen datum';
3692     +$labels['removetag'] = 'Verwijder';
3693     +
3694     +$labels['taskdetails'] = 'Details';
3695     +$labels['newtask'] = 'Nieuwe Taak';
3696     +$labels['edittask'] = 'Taak Wijzigen';
3697     +$labels['save'] = 'Opslaan';
3698     +$labels['cancel'] = 'Annuleren';
3699     +$labels['addsubtask'] = 'Voeg subtaak toe';
3700     +$labels['deletetask'] = 'Verwijder taak';
3701     +$labels['deletethisonly'] = 'Verwijder alleen deze taak';
3702     +$labels['deletewithchilds'] = 'Verwijder inclusief subtaken';
3703     +
3704     +$labels['tabsummary'] = 'Samenvatting';
3705     +$labels['tabrecurrence'] = 'Herhaling';
3706     +$labels['tabattachments'] = 'Toebehoren';
3707     +$labels['tabsharing'] = 'Delen';
3708     +
3709     +$labels['editlist'] = 'Lijst wijzigen';
3710     +$labels['createlist'] = 'Lijst toevoegen';
3711     +$labels['listactions'] = 'Lijst opties...';
3712     +$labels['listname'] = 'Naam';
3713     +$labels['showalarms'] = 'Alarmen tonen';
3714     +$labels['import'] = 'Importeren';
3715     +
3716     +// date words
3717     +$labels['on'] = 'op';
3718     +$labels['at'] = 'om';
3719     +$labels['this'] = 'deze';
3720     +$labels['next'] = 'volgende';
3721     +
3722     +// mesages
3723     +$labels['savingdata'] = 'Data wordt opgeslagen...';
3724     +$labels['errorsaving'] = 'Fout in opslaan van data.';
3725     +$labels['notasksfound'] = 'Geen taken gevonden voor de gegeven criteria';
3726     +$labels['invalidstartduedates'] = 'Begin datum kan niet later zijn dan vervaldag.';
3727     +$labels['deletetasktconfirm'] = 'Wilt u deze taak echt verwijderen?';
3728     +$labels['deleteparenttasktconfirm'] = 'Wilt u deze taak en alle subtaken echt verwijderen?';
3729     +$labels['deletelistconfirm'] = 'Wilt u deze lijst met alle taken echt verwijderen?';
3730     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/pl_PL.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/pl_PL.inc
3731     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/pl_PL.inc 1970-01-01 01:00:00.000000000 +0100
3732     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/pl_PL.inc 2013-11-24 00:38:52.000000000 +0100
3733     @@ -0,0 +1,68 @@
3734     +<?php
3735     +
3736     +$labels = array();
3737     +$labels['navtitle'] = 'Tasks';
3738     +$labels['lists'] = 'Tasklists';
3739     +$labels['list'] = 'Tasklist';
3740     +$labels['tags'] = 'Tags';
3741     +
3742     +$labels['newtask'] = 'New Task';
3743     +$labels['createnewtask'] = 'Create new Task (e.g. Saturday, Mow the lawn)';
3744     +$labels['createfrommail'] = 'Save as task';
3745     +$labels['mark'] = 'Mark';
3746     +$labels['unmark'] = 'Unmark';
3747     +$labels['edit'] = 'Edit';
3748     +$labels['delete'] = 'Usuń';
3749     +$labels['title'] = 'Title';
3750     +$labels['description'] = 'Description';
3751     +$labels['datetime'] = 'Date/Time';
3752     +$labels['start'] = 'Start';
3753     +$labels['alarms'] = 'Reminder';
3754     +
3755     +$labels['all'] = 'All';
3756     +$labels['flagged'] = 'Flagged';
3757     +$labels['complete'] = 'Complete';
3758     +$labels['overdue'] = 'Overdue';
3759     +$labels['today'] = 'Today';
3760     +$labels['tomorrow'] = 'Tomorrow';
3761     +$labels['next7days'] = 'Next 7 days';
3762     +$labels['later'] = 'Later';
3763     +$labels['nodate'] = 'no date';
3764     +$labels['removetag'] = 'Remove';
3765     +
3766     +$labels['taskdetails'] = 'Details';
3767     +$labels['newtask'] = 'New Task';
3768     +$labels['edittask'] = 'Edit Task';
3769     +$labels['save'] = 'Save';
3770     +$labels['cancel'] = 'Cancel';
3771     +$labels['addsubtask'] = 'Add subtask';
3772     +$labels['deletetask'] = 'Delete task';
3773     +$labels['deletethisonly'] = 'Delete this task only';
3774     +$labels['deletewithchilds'] = 'Delete with all subtasks';
3775     +
3776     +$labels['tabsummary'] = 'Summary';
3777     +$labels['tabrecurrence'] = 'Recurrence';
3778     +$labels['tabattachments'] = 'Attachments';
3779     +$labels['tabsharing'] = 'Sharing';
3780     +
3781     +$labels['editlist'] = 'Edit list';
3782     +$labels['createlist'] = 'Add list';
3783     +$labels['listactions'] = 'List options...';
3784     +$labels['listname'] = 'Nazwa';
3785     +$labels['showalarms'] = 'Show alarms';
3786     +$labels['import'] = 'Import';
3787     +
3788     +// date words
3789     +$labels['on'] = 'on';
3790     +$labels['at'] = 'at';
3791     +$labels['this'] = 'this';
3792     +$labels['next'] = 'next';
3793     +
3794     +// mesages
3795     +$labels['savingdata'] = 'Zapisuję dane...';
3796     +$labels['errorsaving'] = 'Failed to save data.';
3797     +$labels['notasksfound'] = 'No tasks found for the given criteria';
3798     +$labels['invalidstartduedates'] = 'Start date must not be greater than due date.';
3799     +$labels['deletetasktconfirm'] = 'Do you really want to delete this task?';
3800     +$labels['deleteparenttasktconfirm'] = 'Do you really want to delete this task and all its subtasks?';
3801     +$labels['deletelistconfirm'] = 'Do you really want to delete this list with all its tasks?';
3802     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/ru_RU.inc roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/ru_RU.inc
3803     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/localization/ru_RU.inc 1970-01-01 01:00:00.000000000 +0100
3804     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/localization/ru_RU.inc 2013-11-24 00:38:52.000000000 +0100
3805     @@ -0,0 +1,68 @@
3806     +<?php
3807     +
3808     +$labels = array();
3809     +$labels['navtitle'] = 'Задачи';
3810     +$labels['lists'] = 'Списки задач';
3811     +$labels['list'] = 'Список задач';
3812     +$labels['tags'] = 'Теги';
3813     +
3814     +$labels['newtask'] = 'Новая задача';
3815     +$labels['createnewtask'] = 'Создать новую задачу (пример: Суббота, покосить газон)';
3816     +$labels['createfrommail'] = 'Сохранить как задачу';
3817     +$labels['mark'] = 'Отметить';
3818     +$labels['unmark'] = 'Снять';
3819     +$labels['edit'] = 'Редактировать';
3820     +$labels['delete'] = 'Удалить';
3821     +$labels['title'] = 'Заголовок';
3822     +$labels['description'] = 'Описание';
3823     +$labels['datetime'] = 'Дата/Время';
3824     +$labels['start'] = 'Начало';
3825     +$labels['alarms'] = 'Напоминание';
3826     +
3827     +$labels['all'] = 'Все';
3828     +$labels['flagged'] = 'Отмеченные';
3829     +$labels['complete'] = 'Завершенные';
3830     +$labels['overdue'] = 'Просроченные';
3831     +$labels['today'] = 'Сегодня';
3832     +$labels['tomorrow'] = 'Завтра';
3833     +$labels['next7days'] = 'Ближайшая неделя';
3834     +$labels['later'] = 'Позднее';
3835     +$labels['nodate'] = 'без даты';
3836     +$labels['removetag'] = 'Убрать';
3837     +
3838     +$labels['taskdetails'] = 'Подробнее';
3839     +$labels['newtask'] = 'Новая задача';
3840     +$labels['edittask'] = 'Редактировать задачу';
3841     +$labels['save'] = 'Сохранить';
3842     +$labels['cancel'] = 'Отмена';
3843     +$labels['addsubtask'] = 'Добавить подзадачу';
3844     +$labels['deletetask'] = 'Удалить задачу';
3845     +$labels['deletethisonly'] = 'Удалить только эту задачу';
3846     +$labels['deletewithchilds'] = 'Удалить задачу и все ее подзадачи';
3847     +
3848     +$labels['tabsummary'] = 'Сводка';
3849     +$labels['tabrecurrence'] = 'Повтор';
3850     +$labels['tabattachments'] = 'Вложения';
3851     +$labels['tabsharing'] = 'Поделиться';
3852     +
3853     +$labels['editlist'] = 'Изменить список';
3854     +$labels['createlist'] = 'Добавить список';
3855     +$labels['listactions'] = 'Действия со списком...';
3856     +$labels['listname'] = 'Название';
3857     +$labels['showalarms'] = 'Показывать уведомления';
3858     +$labels['import'] = 'Импорт';
3859     +
3860     +// date words
3861     +$labels['on'] = 'в';
3862     +$labels['at'] = 'в';
3863     +$labels['this'] = 'этот';
3864     +$labels['next'] = 'следующий';
3865     +
3866     +// mesages
3867     +$labels['savingdata'] = 'Сохранение данных...';
3868     +$labels['errorsaving'] = 'Не удалось сохранить.';
3869     +$labels['notasksfound'] = 'По заданному условию не найдено задач';
3870     +$labels['invalidstartduedates'] = 'Начальная дата не может превышать конечную.';
3871     +$labels['deletetasktconfirm'] = 'Вы действительно хотите удалить эту задачу?';
3872     +$labels['deleteparenttasktconfirm'] = 'Вы действительно хотите удалить эту задачу и все ее подзадачи?';
3873     +$labels['deletelistconfirm'] = 'Вы действительно хотите удалить этот список и все его задачи?';
3874     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/package.xml roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/package.xml
3875     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/package.xml 1970-01-01 01:00:00.000000000 +0100
3876     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/package.xml 2013-11-24 00:38:51.000000000 +0100
3877     @@ -0,0 +1,92 @@
3878     +<?xml version="1.0" encoding="UTF-8"?>
3879     +<package xmlns="http://pear.php.net/dtd/package-2.0" xmlns:tasks="http://pear.php.net/dtd/tasks-1.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" packagerversion="1.9.0" version="2.0" xsi:schemaLocation="http://pear.php.net/dtd/tasks-1.0
3880     + http://pear.php.net/dtd/tasks-1.0.xsd
3881     + http://pear.php.net/dtd/package-2.0
3882     + http://pear.php.net/dtd/package-2.0.xsd">
3883     + <name>tasklist</name>
3884     + <uri>http://git.kolab.org/roundcubemail-plugins-kolab/</uri>
3885     + <summary>Task management plugin</summary>
3886     + <description>-</description>
3887     + <lead>
3888     + <name>Thomas Bruederli</name>
3889     + <user>bruederli</user>
3890     + <email>bruederli@kolabsys.com</email>
3891     + <active>yes</active>
3892     + </lead>
3893     + <date>2012-11-21</date>
3894     + <version>
3895     + <release>0.9-beta</release>
3896     + <api>0.9-beta</api>
3897     + </version>
3898     + <stability>
3899     + <release>beta</release>
3900     + <api>beta</api>
3901     + </stability>
3902     + <license uri="http://www.gnu.org/licenses/agpl.html">GNU AGPLv3</license>
3903     + <notes>-</notes>
3904     + <contents>
3905     + <dir baseinstalldir="/" name="/">
3906     + <file name="tasklist.php" role="php">
3907     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3908     + </file>
3909     + <file name="tasklist_ui.php" role="php">
3910     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3911     + </file>
3912     + <file name="tasklist_base.js" role="data">
3913     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3914     + </file>
3915     + <file name="tasklist.js" role="data">
3916     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3917     + </file>
3918     + <file name="jquery.tagedit.js" role="data">
3919     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3920     + </file>
3921     + <file name="drivers/tasklist_driver.php" role="php">
3922     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3923     + </file>
3924     + <file name="drivers/kolab/tasklist_kolab_driver.php" role="php">
3925     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3926     + </file>
3927     + <file name="drivers/database/tasklist_database_driver.php" role="php">
3928     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3929     + </file>
3930     + <file name="drivers/database/SQL/mysql.sql" role="data">
3931     + <tasks:replace from="@package_version@" to="version" type="package-info"/>
3932     + </file>
3933     +
3934     + <file name="config.inc.php.dist" role="data"></file>
3935     + <file name="LICENSE" role="data"></file>
3936     +
3937     + <file name="localization/de_CH.inc" role="data"></file>
3938     + <file name="localization/en_US.inc" role="data"></file>
3939     +
3940     + <file name="skins/larry/tasklist.css" role="data"></file>
3941     + <file name="skins/larry/iehacks.css" role="data"></file>
3942     + <file name="skins/larry/buttons.png" role="data"></file>
3943     + <file name="skins/larry/sprites.png" role="data"></file>
3944     + <file name="skins/larry/templates/mainview.html" role="data"></file>
3945     + <file name="skins/larry/templates/taskedit.html" role="data"></file>
3946     + <file name="skins/larry/templates/attachment.html" role="data"></file>
3947     + </dir>
3948     + <!-- / -->
3949     + </contents>
3950     + <dependencies>
3951     + <required>
3952     + <php>
3953     + <min>5.3.1</min>
3954     + </php>
3955     + <pearinstaller>
3956     + <min>1.7.0</min>
3957     + </pearinstaller>
3958     + <package>
3959     + <name>libkolab</name>
3960     + <uri>http://git.kolab.org/roundcubemail-plugins-kolab/tree/plugins/libkolab</uri>
3961     + </package>
3962     + <package>
3963     + <name>libcalendaring</name>
3964     + <uri>http://git.kolab.org/roundcubemail-plugins-kolab/tree/plugins/libcalendaring</uri>
3965     + </package>
3966     + </required>
3967     + </dependencies>
3968     + <phprelease/>
3969     +</package>
3970     Les fichiers binaires roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/buttons.png et roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/buttons.png sont différents.
3971     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/iehacks.css roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/iehacks.css
3972     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/iehacks.css 1970-01-01 01:00:00.000000000 +0100
3973     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/iehacks.css 2013-11-24 00:38:52.000000000 +0100
3974     @@ -0,0 +1,90 @@
3975     +/**
3976     + * Roundcube Taklist plugin CSS hacks for IE < 9
3977     + *
3978     + * Copyright (c) 2012, Kolab Systems AG <contact@kolabsys.com>
3979     + *
3980     + * The contents are subject to the Creative Commons Attribution-ShareAlike
3981     + * License. It is allowed to copy, distribute, transmit and to adapt the work
3982     + * by keeping credits to the original autors in the README file.
3983     + * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
3984     + *
3985     + * $Id$
3986     + */
3987     +
3988     +#tasksview {
3989     + background: transparent;
3990     + filter:progid:DXImageTransform.Microsoft.gradient(startColorstr=#33ffffff,endColorstr=#33ffffff);
3991     + zoom: 1;
3992     +}
3993     +
3994     +#tasksview .buttonbar {
3995     + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#eeeeee', endColorstr='#dfdfdf', GradientType=0);
3996     +}
3997     +
3998     +html.ie #taskselector li .count:after {
3999     + bottom: -4px;
4000     +}
4001     +
4002     +#thelist .taskitem.dragging .taskhead {
4003     + filter: alpha(opacity=50);
4004     +}
4005     +
4006     +#thelist .taskhead.complete {
4007     + filter: alpha(opacity=60);
4008     +}
4009     +
4010     +#thelist .taskhead {
4011     + filter: progid:DXImageTransform.Microsoft.Shadow(color=#33666666,direction=180,strength=2);
4012     +}
4013     +
4014     +/*** Special hacks for IE7 only ***/
4015     +
4016     +html.ie7 #taskselector li .count {
4017     + position: relative;
4018     + top: 0px;
4019     + left: 5px;
4020     +}
4021     +
4022     +html.ie7 #taskselector li.selected .count {
4023     + background: #d9ecf4;
4024     + color: #004458;
4025     +}
4026     +
4027     +html.ie7 #taskselector li.selected.overdue .count {
4028     + background: #ff3800;
4029     + color: #fff;
4030     +}
4031     +
4032     +html.ie7 #tagslist li,
4033     +html.ie7 #taskselector li {
4034     + float: left;
4035     +}
4036     +
4037     +html.ie7 .taskitem {
4038     +
4039     +}
4040     +
4041     +html.ie7 .taskhead .title {
4042     + position: relative;
4043     + top: -3px;
4044     +}
4045     +
4046     +html.ie7 .taskitem .childtoggle {
4047     + display: block;
4048     + /* workaround for text-indent which also offsets the background image */
4049     + text-indent: 0;
4050     + font-size: 0;
4051     + line-height: 0;
4052     + text-align: right;
4053     + text-decoration: none;
4054     +}
4055     +
4056     +html.ie7 .taskhead .tags {
4057     + height: 18px;
4058     +}
4059     +
4060     +html.ie7 .taskhead .tags .tag {
4061     + display: inline-block;
4062     + padding-bottom: 2px;
4063     +}
4064     +
4065     Les fichiers binaires roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/sprites.png et roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/sprites.png sont différents.
4066     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/tasklist.css roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/tasklist.css
4067     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/tasklist.css 1970-01-01 01:00:00.000000000 +0100
4068     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/tasklist.css 2013-11-24 00:38:52.000000000 +0100
4069     @@ -0,0 +1,836 @@
4070     +/**
4071     + * Roundcube Taklist plugin styles for skin "Larry"
4072     + *
4073     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
4074     + * Screendesign by FLINT / Büro für Gestaltung, bueroflint.com
4075     + *
4076     + * The contents are subject to the Creative Commons Attribution-ShareAlike
4077     + * License. It is allowed to copy, distribute, transmit and to adapt the work
4078     + * by keeping credits to the original autors in the README file.
4079     + * See http://creativecommons.org/licenses/by-sa/3.0/ for details.
4080     + */
4081     +
4082     +#taskbar a.button-tasklist span.button-inner {
4083     + background-image: url(buttons.png);
4084     + background-position: 0 0;
4085     +}
4086     +
4087     +#taskbar a.button-tasklist:hover span.button-inner,
4088     +#taskbar a.button-tasklist.button-selected span.button-inner {
4089     + background-position: 0 -26px;
4090     +}
4091     +
4092     +ul.toolbarmenu li span.icon.taskadd {
4093     + background-image: url(buttons.png);
4094     + background-position: -4px -90px;
4095     +}
4096     +
4097     +div.uidialog {
4098     + display: none;
4099     +}
4100     +
4101     +body.attachmentwin #mainscreen {
4102     + top: 60px;
4103     +}
4104     +
4105     +body.attachmentwin #topnav .topright {
4106     + margin-top: 20px;
4107     +}
4108     +
4109     +#sidebar {
4110     + position: absolute;
4111     + top: 0;
4112     + left: 0;
4113     + bottom: 0;
4114     + width: 240px;
4115     +}
4116     +
4117     +.tasklistview #searchmenulink {
4118     + width: 15px;
4119     +}
4120     +
4121     +#tagsbox {
4122     + position: absolute;
4123     + top: 42px;
4124     + left: 0;
4125     + width: 100%;
4126     + height: 242px;
4127     +}
4128     +
4129     +#tasklistsbox {
4130     + position: absolute;
4131     + top: 300px;
4132     + left: 0;
4133     + width: 100%;
4134     + bottom: 0px;
4135     +}
4136     +
4137     +#taskselector {
4138     + margin: -4px 0 0;
4139     + padding: 0;
4140     +}
4141     +
4142     +#taskselector li {
4143     + display: inline-block;
4144     + position: relative;
4145     + font-size: 90%;
4146     + padding-right: 0.3em;
4147     +}
4148     +
4149     +#tagslist li,
4150     +#taskselector li a {
4151     + display: inline-block;
4152     + color: #004458;
4153     + min-width: 4em;
4154     + padding: 0.2em 0.6em 0.3em 0.6em;
4155     + text-align: center;
4156     + text-decoration: none;
4157     + border: 1px solid #eee;
4158     + border-color: transparent;
4159     +}
4160     +
4161     +#taskselector li:first-child {
4162     + border-top: 0;
4163     + border-radius: 4px 4px 0 0;
4164     +}
4165     +
4166     +#taskselector li:last-child {
4167     + border-bottom: 0;
4168     + border-radius: 0 0 4px 4px;
4169     +}
4170     +
4171     +#taskselector li.overdue a {
4172     + color: #b72a2a;
4173     + font-weight: bold;
4174     +}
4175     +
4176     +#taskselector li.inactive a {
4177     + color: #97b3bf;
4178     +}
4179     +
4180     +#tagslist li.selected,
4181     +#taskselector li.selected a {
4182     + color: #fff;
4183     + background: #005d76;
4184     + background: -moz-linear-gradient(top, #005d76 0%, #004558 100%);
4185     + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#005d76), color-stop(100%,#004558));
4186     + background: -o-linear-gradient(top, #005d76 0%, #004558 100%);
4187     + background: -ms-linear-gradient(top, #005d76 0%, #004558 100%);
4188     + background: linear-gradient(top, #005d76 0%, #004558 100%);
4189     + box-shadow: inset 0 1px 1px 0 #003645;
4190     + -o-box-shadow: inset 0 1px 1px 0 #003645;
4191     + -webkit-box-shadow: inset 0 1px 1px 0 #003645;
4192     + -moz-box-shadow: inset 0 1px 1px 0 #003645;
4193     + border-color: #003645;
4194     + border-radius: 9px;
4195     + text-shadow: none;
4196     + outline: none;
4197     +}
4198     +
4199     +#taskselector li .count {
4200     + display: none;
4201     + position: absolute;
4202     + top: -18px;
4203     + right: 5px;
4204     + min-width: 1.8em;
4205     + padding: 2px 4px;
4206     + background: #004558;
4207     + background: -moz-linear-gradient(top, #005d76 0%, #004558 100%);
4208     + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#005d76), color-stop(100%,#004558));
4209     + background: -o-linear-gradient(top, #005d76 0%, #004558 100%);
4210     + background: -ms-linear-gradient(top, #005d76 0%, #004558 100%);
4211     + background: linear-gradient(top, #005d76 0%, #004558 100%);
4212     + box-shadow: 0 1px 2px 0 rgba(24,24,24,0.6);
4213     + color: #fff;
4214     + border-radius: 3px;
4215     + text-align: center;
4216     + font-weight: bold;
4217     + font-size: 80%;
4218     + text-shadow: none;
4219     +}
4220     +
4221     +#taskselector li .count:after {
4222     + content: "";
4223     + position: absolute;
4224     + bottom: -5px;
4225     + left: 50%;
4226     + margin-left: -5px;
4227     + border-style: solid;
4228     + border-width: 5px 5px 0;
4229     + border-color: #004558 transparent;
4230     + /* reduce the damage in FF3.0 */
4231     + display: block;
4232     + width: 0;
4233     +}
4234     +
4235     +#taskselector li.overdue .count {
4236     + background: #ff3800;
4237     +}
4238     +
4239     +#taskselector li.overdue .count:after {
4240     + border-color: #ff3800 transparent;
4241     +}
4242     +
4243     +#tagslist {
4244     + padding: 0;
4245     + margin: 6px;
4246     + list-style: none;
4247     +}
4248     +
4249     +#tagslist li {
4250     + display: inline-block;
4251     + color: #004458;
4252     + margin-right: 0.5em;
4253     + margin-bottom: 0.4em;
4254     + min-width: 1.2em;
4255     + cursor: pointer;
4256     +}
4257     +
4258     +#tasklists li {
4259     + margin: 0;
4260     + height: 20px;
4261     + padding: 6px 8px 2px;
4262     + display: block;
4263     + position: relative;
4264     + white-space: nowrap;
4265     +}
4266     +
4267     +#tasklists li label {
4268     + display: block;
4269     +}
4270     +
4271     +#tasklists li span.listname {
4272     + display: block;
4273     + cursor: default;
4274     + padding-bottom: 2px;
4275     + padding-right: 30px;
4276     + margin-right: 20px;
4277     + color: #004458;
4278     + overflow: hidden;
4279     + text-overflow: ellipsis;
4280     + white-space: nowrap;
4281     + background: url(sprites.png) right 20px no-repeat;
4282     +}
4283     +
4284     +#tasklists li span.handle {
4285     + display: none;
4286     +}
4287     +
4288     +#tasklists li.selected span.listname {
4289     + font-weight: bold;
4290     +}
4291     +
4292     +#tasklists li.readonly span.listname {
4293     + background-position: right -142px;
4294     +}
4295     +
4296     +#tasklists li.other span.listname {
4297     + background-position: right -160px;
4298     +}
4299     +
4300     +#tasklists li.other.readonly span.listname {
4301     + background-position: right -178px;
4302     +}
4303     +
4304     +#tasklists li.shared span.listname {
4305     + background-position: right -196px;
4306     +}
4307     +
4308     +#tasklists li.shared.readonly span.listname {
4309     + background-position: right -214px;
4310     +}
4311     +
4312     +#tasklists li input {
4313     + position: absolute;
4314     + top: 5px;
4315     + right: 5px;
4316     +}
4317     +
4318     +#mainview-right {
4319     + position: absolute;
4320     + top: 0;
4321     + left: 256px;
4322     + right: 0;
4323     + bottom: 0;
4324     +}
4325     +
4326     +#taskstoolbar {
4327     + position: absolute;
4328     + top: -6px;
4329     + left: 0;
4330     + width: 100%;
4331     + height: 40px;
4332     + white-space: nowrap;
4333     +}
4334     +
4335     +#taskstoolbar a.button.newtask {
4336     + background-image: url(buttons.png);
4337     + background-position: center -53px;
4338     +}
4339     +
4340     +#quickaddbox {
4341     + position: absolute;
4342     + top: 7px;
4343     + left: 0;
4344     + width: 60%;
4345     + height: 32px;
4346     + white-space: nowrap;
4347     +}
4348     +
4349     +#quickaddinput {
4350     + width: 85%;
4351     + margin: 0;
4352     + padding: 3px 8px;
4353     + height: 18px;
4354     + background: #f1f1f1;
4355     + background: rgba(255, 255, 255, 0.7);
4356     + border-color: #a3a3a3;
4357     + font-weight: bold;
4358     +}
4359     +
4360     +#quickaddbox .button {
4361     + margin-left: 5px;
4362     + padding: 3px 10px;
4363     + font-weight: bold;
4364     +}
4365     +
4366     +#tasksview {
4367     + position: absolute;
4368     + top: 42px;
4369     + left: 0;
4370     + right: 0;
4371     + bottom: 0;
4372     + padding-bottom: 28px;
4373     + background: rgba(255, 255, 255, 0.2);
4374     + overflow: visible;
4375     +}
4376     +
4377     +#message.statusbar {
4378     + border-top: 1px solid #c3c3c3;
4379     +}
4380     +
4381     +#tasksview .scroller {
4382     + position: absolute;
4383     + left: 0;
4384     + top: 35px;
4385     + width: 100%;
4386     + bottom: 28px;
4387     + overflow: auto;
4388     +}
4389     +
4390     +#tasksview .buttonbar {
4391     + color: #777;
4392     + background: #eee;
4393     + background: -moz-linear-gradient(top, #eee 0%, #dfdfdf 100%);
4394     + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#eee), color-stop(100%,#dfdfdf));
4395     + background: -o-linear-gradient(top, #eee 0%, #dfdfdf 100%);
4396     + background: -ms-linear-gradient(top, #eee 0%, #dfdfdf 100%);
4397     + background: linear-gradient(top, #eee 0%, #dfdfdf 100%);
4398     + border-bottom: 1px solid #ccc;
4399     +}
4400     +
4401     +#thelist {
4402     + padding: 0;
4403     + margin: 1em;
4404     + list-style: none;
4405     +}
4406     +
4407     +#listmessagebox {
4408     + display: none;
4409     + font-size: 14px;
4410     + color: #666;
4411     + margin: 1.5em;
4412     + text-shadow: 0px 1px 1px #fff;
4413     + text-align:center;
4414     +}
4415     +
4416     +.taskitem {
4417     + position: relative;
4418     + display: block;
4419     + margin-bottom: 3px;
4420     +}
4421     +
4422     +.taskitem.dragging {
4423     + opacity: 0.5;
4424     +}
4425     +
4426     +.taskitem .childtasks {
4427     + position: relative;
4428     + padding: 0;
4429     + margin: 3px 0 0 20px;
4430     + list-style: none;
4431     +}
4432     +
4433     +.taskitem .childtoggle {
4434     + display: none;
4435     + position: absolute;
4436     + top: 4px;
4437     + left: -5px;
4438     + padding: 2px;
4439     + font-size: 10px;
4440     + color: #727272;
4441     + cursor: pointer;
4442     +
4443     + width: 14px;
4444     + height: 14px;
4445     + background: url(sprites.png) -2px -80px no-repeat;
4446     + text-indent: -1000px;
4447     + overflow: hidden;
4448     +}
4449     +
4450     +.taskitem .childtoggle.collapsed {
4451     + background-position: -18px -81px;
4452     +}
4453     +
4454     +.taskhead {
4455     + position: relative;
4456     + margin-left: 14px;
4457     + padding: 4px 5px 3px 5px;
4458     + border: 1px solid #fff;
4459     + border-radius: 5px;
4460     + background: #fff;
4461     + -webkit-box-shadow: 0 1px 1px 0 rgba(50, 50, 50, 0.5);
4462     + -moz-box-shadow: 0 1px 1px 0 rgba(50, 50, 50, 0.5);
4463     + box-shadow: 0 1px 1px 0 rgba(50, 50, 50, 0.5);
4464     + padding-right: 26em;
4465     + white-space: nowrap;
4466     + overflow: hidden;
4467     + text-overflow: ellipsis;
4468     + cursor: default;
4469     +}
4470     +
4471     +.taskhead.droptarget {
4472     + border-color: #4787b1;
4473     + box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4474     + -moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4475     + -webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4476     + -o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4477     +}
4478     +
4479     +.taskhead .complete {
4480     + margin: -1px 1em 0 0;
4481     +}
4482     +
4483     +.taskhead .title {
4484     + font-size: 12px;
4485     +}
4486     +
4487     +.taskhead .flagged {
4488     + display: inline-block;
4489     + visibility: hidden;
4490     + width: 16px;
4491     + height: 16px;
4492     + background: url(sprites.png) -2px -3px no-repeat;
4493     + margin: -3px 1em 0 0;
4494     + vertical-align: middle;
4495     + cursor: pointer;
4496     +}
4497     +
4498     +.taskhead:hover .flagged {
4499     + visibility: visible;
4500     +}
4501     +
4502     +.taskhead.flagged .flagged {
4503     + visibility: visible;
4504     + background-position: -2px -23px;
4505     +}
4506     +
4507     +.taskhead .tags {
4508     + display: block;
4509     + position: absolute;
4510     + top: 3px;
4511     + right: 10em;
4512     + max-width: 14em;
4513     + height: 16px;
4514     + overflow: hidden;
4515     + padding-top: 1px;
4516     + text-align: right;
4517     +}
4518     +
4519     +.taskhead .tags .tag {
4520     + font-size: 85%;
4521     + background: #d9ecf4;
4522     + border: 1px solid #c2dae5;
4523     + border-radius: 4px;
4524     + padding: 1px 7px;
4525     + margin-right: 3px;
4526     +}
4527     +
4528     +.taskhead .date {
4529     + position: absolute;
4530     + top: 4px;
4531     + right: 30px;
4532     + text-align: right;
4533     + cursor: pointer;
4534     +}
4535     +
4536     +.taskhead.nodate .date {
4537     + color: #ddd;
4538     +}
4539     +
4540     +.taskhead.overdue .date {
4541     + color: #d00;
4542     +}
4543     +
4544     +.taskhead.nodate:hover .date {
4545     + color: #999;
4546     +}
4547     +
4548     +.taskhead .date input {
4549     + padding: 1px 2px;
4550     + border: 1px solid #ddd;
4551     + -webkit-box-shadow: none;
4552     + -moz-box-shadow: none;
4553     + box-shadow: none;
4554     + outline: none;
4555     + text-align: right;
4556     + width: 6em;
4557     + font-size: 11px;
4558     +}
4559     +
4560     +.taskhead .actions,
4561     +.taskhead .delete {
4562     + display: block;
4563     + visibility: hidden;
4564     + position: absolute;
4565     + top: 3px;
4566     + right: 6px;
4567     + width: 18px;
4568     + height: 18px;
4569     + background: url(sprites.png) 0 -80px no-repeat;
4570     + text-indent: -1000px;
4571     + overflow: hidden;
4572     + cursor: pointer;
4573     +}
4574     +
4575     +.taskhead .delete {
4576     + background-position: 0 -40px;
4577     +}
4578     +
4579     +.taskhead:hover .actions,
4580     +.taskhead:hover .delete {
4581     + visibility: visible;
4582     +}
4583     +
4584     +.taskhead.complete {
4585     + opacity: 0.6;
4586     +}
4587     +
4588     +.taskhead.complete .title {
4589     + text-decoration: line-through;
4590     +}
4591     +
4592     +.taskhead .progressbar {
4593     + position: absolute;
4594     + bottom: 1px;
4595     + left: 6px;
4596     + right: 6px;
4597     + height: 2px;
4598     +}
4599     +
4600     +.taskhead.complete .progressbar {
4601     + display: none;
4602     +}
4603     +
4604     +.taskhead .progressvalue {
4605     + height: 1px;
4606     + background: rgba(1, 124, 180, 0.2);
4607     + border-top: 1px solid #219de6;
4608     +}
4609     +
4610     +ul.toolbarmenu li span.add {
4611     + background-image: url(sprites.png);
4612     + background-position: 0 -100px;
4613     +}
4614     +
4615     +ul.toolbarmenu li span.delete {
4616     + background-position: 0 -1508px;
4617     +}
4618     +
4619     +.taskitem-draghelper {
4620     +/*
4621     + width: 32px;
4622     + height: 26px;
4623     +*/
4624     + background: #444;
4625     + border: 1px solid #555;
4626     + border-radius: 4px;
4627     + box-shadow: 0 2px 6px 0 #333;
4628     + -moz-box-shadow: 0 2px 6px 0 #333;
4629     + -webkit-box-shadow: 0 2px 6px 0 #333;
4630     + -o-box-shadow: 0 2px 6px 0 #333;
4631     + z-index: 5000;
4632     + padding: 2px 10px;
4633     + font-size: 20px;
4634     + color: #ccc;
4635     + opacity: 0.92;
4636     + filter: alpha(opacity=90);
4637     + text-shadow: 0px 1px 1px #333;
4638     +}
4639     +
4640     +#rootdroppable {
4641     + display: none;
4642     + position: absolute;
4643     + top: 36px;
4644     + left: 1em;
4645     + right: 1em;
4646     + height: 5px;
4647     + background: #ddd;
4648     + border-radius: 3px;
4649     +}
4650     +
4651     +#rootdroppable.droptarget {
4652     + background: #4787b1;
4653     + box-shadow: 0 0 2px 1px rgba(71,135,177, 0.9);
4654     + -moz-box-shadow: 0 0 2px 1px rgba(71,135,177, 0.9);
4655     + -webkit-box-shadow: 0 0 2px 1px rgba(71,135,177, 0.9);
4656     + -o-box-shadow: 0 0 2px 1px rgba(71,135,177, 0.9);
4657     +
4658     +}
4659     +
4660     +/*** task edit form ***/
4661     +
4662     +#taskedit,
4663     +#taskshow {
4664     + display:none;
4665     +}
4666     +
4667     +#taskedit {
4668     + position: relative;
4669     + top: -1.5em;
4670     + padding: 0.5em 0.1em;
4671     + margin: 0 -0.2em;
4672     +}
4673     +
4674     +#taskshow h2 {
4675     + margin-top: -0.5em;
4676     +}
4677     +
4678     +#taskshow label {
4679     + color: #999;
4680     +}
4681     +
4682     +#task-parent-title {
4683     + position: relative;
4684     + top: -0.6em;
4685     +}
4686     +
4687     +a.morelink {
4688     + font-size: 90%;
4689     + color: #0069a6;
4690     + text-decoration: none;
4691     + outline: none;
4692     +}
4693     +
4694     +a.morelink:hover {
4695     + text-decoration: underline;
4696     +}
4697     +
4698     +#taskedit .ui-tabs-panel {
4699     + min-height: 24em;
4700     +}
4701     +
4702     +#taskeditform input.text,
4703     +#taskeditform textarea {
4704     + width: 97%;
4705     +}
4706     +
4707     +#taskeditform .formbuttons {
4708     + margin: 0.5em 0;
4709     +}
4710     +
4711     +#taskedit-attachments {
4712     + margin: 0.6em 0;
4713     +}
4714     +
4715     +#taskedit-attachments ul li {
4716     + display: block;
4717     + color: #333;
4718     + font-weight: bold;
4719     + padding: 8px 4px 3px 30px;
4720     + text-shadow: 0px 1px 1px #fff;
4721     + text-decoration: none;
4722     + white-space: nowrap;
4723     +}
4724     +
4725     +#taskedit-attachments ul li a.file {
4726     + padding: 0;
4727     +}
4728     +
4729     +#taskedit-attachments-form {
4730     + margin-top: 1em;
4731     + padding-top: 0.8em;
4732     + border-top: 2px solid #fafafa;
4733     +}
4734     +
4735     +div.form-section {
4736     + position: relative;
4737     + margin-top: 0.2em;
4738     + margin-bottom: 0.8em;
4739     +}
4740     +
4741     +.form-section label {
4742     + display: inline-block;
4743     + min-width: 7em;
4744     + padding-right: 0.5em;
4745     + margin-bottom: 0.3em;
4746     +}
4747     +
4748     +label.block {
4749     + display: block;
4750     + margin-bottom: 0.3em;
4751     +}
4752     +
4753     +#taskedit-completeness-slider {
4754     + display: inline-block;
4755     + margin-left: 2em;
4756     + width: 30em;
4757     + height: 0.8em;
4758     + border: 1px solid #ccc;
4759     +}
4760     +
4761     +#taskedit-tagline {
4762     + width: 97%;
4763     +}
4764     +
4765     +#taskedit .droptarget {
4766     + background-image: url(../../../../skins/larry/images/filedrop.png) !important;
4767     + background-position: center bottom !important;
4768     + background-repeat: no-repeat !important;
4769     +}
4770     +
4771     +#taskedit .droptarget.hover,
4772     +#taskedit .droptarget.active {
4773     + border-color: #019bc6;
4774     + box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
4775     + -moz-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
4776     + -webkit-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
4777     + -o-box-shadow: 0 0 3px 2px rgba(71,135,177, 0.5);
4778     +}
4779     +
4780     +#taskedit .droptarget.hover {
4781     + background-color: #d9ecf4;
4782     + box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4783     + -moz-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4784     + -webkit-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4785     + -o-box-shadow: 0 0 5px 2px rgba(71,135,177, 0.9);
4786     +}
4787     +
4788     +#task-attachments .attachmentslist li {
4789     + float: left;
4790     + margin-right: 1em;
4791     +}
4792     +
4793     +#task-attachments .attachmentslist li a {
4794     + outline: none;
4795     +}
4796     +
4797     +
4798     +/**
4799     + * Styles of the tagedit inputsforms
4800     + */
4801     +.tagedit-list {
4802     + width: 100%;
4803     + margin: 0;
4804     + padding: 4px 4px 0 5px;
4805     + overflow: auto;
4806     + min-height: 26px;
4807     + background: #fff;
4808     + border: 1px solid #b2b2b2;
4809     + border-radius: 4px;
4810     + box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
4811     + -moz-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
4812     + -webkit-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
4813     + -o-box-shadow: inset 0 0 2px 1px rgba(0,0,0, 0.1);
4814     +}
4815     +.tagedit-list li.tagedit-listelement {
4816     + list-style-type: none;
4817     + float: left;
4818     + margin: 0 4px 4px 0;
4819     + padding: 0;
4820     +}
4821     +
4822     +/* New Item input */
4823     +.tagedit-list li.tagedit-listelement-new input {
4824     + border: 0;
4825     + height: 100%;
4826     + padding: 4px 1px;
4827     + width: 15px;
4828     + background: #fff;
4829     + border-radius: 0;
4830     + box-shadow: none;
4831     + -moz-box-shadow: none;
4832     + -webkit-box-shadow: none;
4833     + -o-box-shadow: none;
4834     +}
4835     +.tagedit-list li.tagedit-listelement-new input:focus {
4836     + box-shadow: none;
4837     + -moz-box-shadow: none;
4838     + -webkit-box-shadow: none;
4839     + -o-box-shadow: none;
4840     + outline: none;
4841     +}
4842     +.tagedit-list li.tagedit-listelement-new input.tagedit-input-disabled {
4843     + display: none;
4844     +}
4845     +
4846     +/* Item that is put to the List */
4847     +.form-section span.tag-element,
4848     +.tagedit-list li.tagedit-listelement-old {
4849     + padding: 3px 0 1px 6px;
4850     + background: #ddeef5;
4851     + background: -moz-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
4852     + background: -webkit-gradient(linear, left top, left bottom, color-stop(0%,#edf6fa), color-stop(100%,#d6e9f3));
4853     + background: -o-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
4854     + background: -ms-linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
4855     + background: linear-gradient(top, #edf6fa 0%, #d6e9f3 100%);
4856     + border: 1px solid #c2dae5;
4857     + -moz-border-radius: 4px;
4858     + -webkit-border-radius: 4px;
4859     + border-radius: 4px;
4860     + color: #0d5165;
4861     +}
4862     +
4863     +.form-section span.tag-element {
4864     + margin-right: 0.6em;
4865     + padding: 2px 6px;
4866     +/* cursor: pointer; */
4867     +}
4868     +
4869     +.tagedit-list li.tagedit-listelement-old a.tagedit-close,
4870     +.tagedit-list li.tagedit-listelement-old a.tagedit-break,
4871     +.tagedit-list li.tagedit-listelement-old a.tagedit-delete,
4872     +.tagedit-list li.tagedit-listelement-old a.tagedit-save {
4873     + text-indent: -2000px;
4874     + display: inline-block;
4875     + position: relative;
4876     + top: -1px;
4877     + width: 16px;
4878     + height: 16px;
4879     + margin: 0 2px 0 6px;
4880     + background: url(sprites.png) -2px -122px no-repeat;
4881     + cursor: pointer;
4882     +}
4883     +
4884     +
4885     +/** Special hacks for IE7 **/
4886     +/** They need to be in this file to also affect the task-create dialog embedded in mail view **/
4887     +
4888     +html.ie7 #taskedit-completeness-slider {
4889     + display: inline;
4890     +}
4891     +
4892     +html.ie7 .form-section span.tag-element,
4893     +html.ie7 .tagedit-list li.tagedit-listelement-old {
4894     + filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#edf6fa', endColorstr='#d6e9f3', GradientType=0);
4895     +}
4896     +
4897     +html.ie7 .tagedit-list li.tagedit-listelement span {
4898     + position: relative;
4899     + top: -3px;
4900     +}
4901     +
4902     +html.ie7 .tagedit-list li.tagedit-listelement-old a.tagedit-close {
4903     + left: 5px;
4904     +}
4905     +
4906     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/templates/attachment.html roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/templates/attachment.html
4907     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/templates/attachment.html 1970-01-01 01:00:00.000000000 +0100
4908     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/templates/attachment.html 2013-11-24 00:38:52.000000000 +0100
4909     @@ -0,0 +1,33 @@
4910     +<roundcube:object name="doctype" value="html5" />
4911     +<html>
4912     +<head>
4913     +<title><roundcube:object name="pagetitle" /></title>
4914     +<roundcube:include file="/includes/links.html" />
4915     +</head>
4916     +<body class="extwin attachmentwin">
4917     +
4918     +<div id="header">
4919     + <div id="topnav">
4920     + <roundcube:object name="logo" src="/images/roundcube_logo.png" id="toplogo" border="0" alt="Logo" />
4921     + <div class="topright">
4922     + <a href="#close" class="closelink" onclick="self.close()"><roundcube:label name="close" /></a>
4923     + </div>
4924     + </div>
4925     +
4926     + <br style="clear:both" />
4927     +</div>
4928     +
4929     +<div id="mainscreen">
4930     + <div id="partheader" class="uibox">
4931     + <roundcube:object name="plugin.attachmentcontrols" class="headers-table" />
4932     + </div>
4933     +
4934     + <div id="attachmentcontainer" class="uibox">
4935     + <roundcube:object name="plugin.attachmentframe" id="attachmentframe" class="header-table" style="width:100%" />
4936     + </div>
4937     +
4938     +</div>
4939     +
4940     +</body>
4941     +</html>
4942     +
4943     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/templates/mainview.html roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/templates/mainview.html
4944     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/templates/mainview.html 1970-01-01 01:00:00.000000000 +0100
4945     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/templates/mainview.html 2013-11-24 00:38:52.000000000 +0100
4946     @@ -0,0 +1,153 @@
4947     +<roundcube:object name="doctype" value="html5" />
4948     +<html>
4949     +<head>
4950     +<title><roundcube:object name="pagetitle" /></title>
4951     +<roundcube:include file="/includes/links.html" />
4952     +<!--[if lte IE 8]><link rel="stylesheet" type="text/css" href="/this/iehacks.css" /><![endif]-->
4953     +</head>
4954     +<body class="tasklistview noscroll">
4955     +
4956     +<roundcube:include file="/includes/header.html" />
4957     +
4958     +<div id="mainscreen">
4959     + <div id="sidebar">
4960     + <div id="taskstoolbar" class="toolbar">
4961     + <roundcube:button command="newtask" type="link" class="button newtask disabled" classAct="button newtask" classSel="button newtask pressed" label="tasklist.newtask" title="tasklist.newtask" />
4962     + <roundcube:container name="toolbar" id="taskstoolbar" />
4963     + </div>
4964     +
4965     + <div id="tagsbox" class="uibox listbox">
4966     + <h2 class="boxtitle"><roundcube:label name="tasklist.tags" id="taglist" /></h2>
4967     + <div class="scroller">
4968     + <roundcube:object name="plugin.tagslist" id="tagslist" />
4969     + </div>
4970     + </div>
4971     +
4972     + <div id="tasklistsbox" class="uibox listbox">
4973     + <h2 class="boxtitle"><roundcube:label name="tasklist.lists" /></h2>
4974     + <div class="scroller withfooter">
4975     + <roundcube:object name="plugin.tasklists" id="tasklists" class="listing" />
4976     + </div>
4977     + <div class="boxfooter">
4978     + <roundcube:button command="list-create" type="link" title="tasklist.createlist" class="listbutton add disabled" classAct="listbutton add" innerClass="inner" content="+" /><roundcube:button name="tasklistoptionslink" id="tasklistoptionsmenulink" type="link" title="tasklist.listactions" class="listbutton groupactions" onclick="UI.show_popup('tasklistoptionsmenu', undefined, { above:true });return false" innerClass="inner" content="&#9881;" />
4979     + </div>
4980     + </div>
4981     + </div>
4982     +
4983     + <div id="mainview-right">
4984     +
4985     + <div id="quickaddbox">
4986     + <roundcube:object name="plugin.quickaddform" />
4987     + </div>
4988     +
4989     + <div id="quicksearchbar">
4990     + <roundcube:object name="plugin.searchform" id="quicksearchbox" />
4991     + <a id="searchmenulink" class="iconbutton searchoptions" > </a>
4992     + <roundcube:button command="reset-search" id="searchreset" class="iconbutton reset" title="resetsearch" content=" " />
4993     + </div>
4994     +
4995     + <div id="tasksview" class="uibox">
4996     + <div class="boxtitle buttonbar">
4997     + <ul id="taskselector">
4998     + <li class="all selected"><a href="#all"><roundcube:label name="tasklist.all" /><span class="count"></span></a></li>
4999     + <li class="overdue inactive"><a href="#overdue"><roundcube:label name="tasklist.overdue" /><span class="count"></span></a></li>
5000     + <li class="flagged"><a href="#flagged"><roundcube:label name="tasklist.flagged" /><span class="count"></span></a></li>
5001     + <li class="today"><a href="#today"><roundcube:label name="tasklist.today" /><span class="count"></span></a></li>
5002     + <li class="tomorrow"><a href="#tomorrow"><roundcube:label name="tasklist.tomorrow" /><span class="count"></span></a></li>
5003     + <li class="week"><a href="#week"><roundcube:label name="tasklist.next7days" /></a></li>
5004     + <li class="later"><a href="#later"><roundcube:label name="tasklist.later" /></a></li>
5005     + <li class="nodate"><a href="#nodate"><roundcube:label name="tasklist.nodate" ucfirst="true" /></a></li>
5006     + <li class="complete"><a href="#complete"><roundcube:label name="tasklist.complete" /><span class="count"></span></a></li>
5007     + </ul>
5008     + </div>
5009     +
5010     + <div class="scroller">
5011     + <roundcube:object name="plugin.tasks" id="thelist" />
5012     + <div id="listmessagebox"></div>
5013     + </div>
5014     + <div id="rootdroppable"></div>
5015     + <roundcube:object name="message" id="message" class="statusbar" />
5016     + </div>
5017     +
5018     + </div>
5019     +
5020     +</div>
5021     +
5022     +<div id="taskitemmenu" class="popupmenu">
5023     + <ul class="toolbarmenu iconized">
5024     + <li><roundcube:button name="edit" type="link" onclick="rctasks.edit_task(rctasks.selected_task.id, 'edit'); return false" label="edit" class="icon active" innerclass="icon edit" /></li>
5025     + <li><roundcube:button name="delete" type="link" onclick="rctasks.delete_task(rctasks.selected_task.id); return false" label="delete" class="icon active" innerclass="icon delete" /></li>
5026     + <li><roundcube:button name="addchild" type="link" onclick="rctasks.add_childtask(rctasks.selected_task.id); return false" label="tasklist.addsubtask" class="icon active" innerclass="icon add" /></li>
5027     + </ul>
5028     +</div>
5029     +
5030     +<div id="tasklistoptionsmenu" class="popupmenu">
5031     + <ul class="toolbarmenu">
5032     + <li><roundcube:button command="list-edit" label="edit" classAct="active" /></li>
5033     + <li><roundcube:button command="list-remove" label="delete" classAct="active" /></li>
5034     + <!--<li><roundcube:button command="list-import" label="tasklist.import" classAct="active" /></li>-->
5035     + <roundcube:if condition="env:tasklist_driver == 'kolab'" />
5036     + <li><roundcube:button command="folders" task="settings" type="link" label="managefolders" classAct="active" /></li>
5037     + <roundcube:endif />
5038     + </ul>
5039     +</div>
5040     +
5041     +<div id="taskshow">
5042     + <div class="form-section" id="task-parent-title"></div>
5043     + <div class="form-section">
5044     + <h2 id="task-title"></h2>
5045     + </div>
5046     + <div id="task-description" class="form-section">
5047     + </div>
5048     + <div id="task-tags" class="form-section">
5049     + <label><roundcube:label name="tasklist.tags" /></label>
5050     + <span class="task-text"></span>
5051     + </div>
5052     + <div id="task-start" class="form-section">
5053     + <label><roundcube:label name="tasklist.start" /></label>
5054     + <span class="task-text"></span>
5055     + <span id="task-starttime"></span>
5056     + </div>
5057     + <div id="task-date" class="form-section">
5058     + <label><roundcube:label name="tasklist.datetime" /></label>
5059     + <span class="task-text"></span>
5060     + <span id="task-time"></span>
5061     + </div>
5062     + <div id="task-alarm" class="form-section">
5063     + <label><roundcube:label name="tasklist.alarms" /></label>
5064     + <span class="task-text"></span>
5065     + </div>
5066     + <div id="task-list" class="form-section">
5067     + <label><roundcube:label name="tasklist.list" /></label>
5068     + <span class="task-text"></span>
5069     + </div>
5070     + <div id="task-completeness" class="form-section">
5071     + <label><roundcube:label name="tasklist.complete" /></label>
5072     + <span class="task-text"></span>
5073     + </div>
5074     + <div id="task-attachments" class="form-section">
5075     + <label><roundcube:label name="attachments" /></label>
5076     + <div class="task-text"></div>
5077     + </div>
5078     +</div>
5079     +
5080     +<roundcube:include file="/templates/taskedit.html" />
5081     +
5082     +<div id="tasklistform" class="uidialog">
5083     + <roundcube:object name="plugin.tasklist_editform" />
5084     +</div>
5085     +
5086     +<script type="text/javascript">
5087     +
5088     +// UI startup
5089     +var UI = new rcube_mail_ui();
5090     +
5091     +$(document).ready(function(e){
5092     + UI.init();
5093     +
5094     +});
5095     +
5096     +</script>
5097     +
5098     +</body>
5099     +</html>
5100     \ Pas de fin de ligne à la fin du fichier.
5101     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/templates/taskedit.html roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/templates/taskedit.html
5102     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/skins/larry/templates/taskedit.html 1970-01-01 01:00:00.000000000 +0100
5103     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/skins/larry/templates/taskedit.html 2013-11-24 00:38:52.000000000 +0100
5104     @@ -0,0 +1,59 @@
5105     +<div id="taskedit" class="uidialog uidialog-tabbed">
5106     + <form id="taskeditform" action="#" method="post" enctype="multipart/form-data">
5107     + <ul>
5108     + <li><a href="#taskedit-tab-1"><roundcube:label name="tasklist.tabsummary" /></a></li><li id="taskedit-tab-attachments"><a href="#taskedit-tab-2"><roundcube:label name="tasklist.tabattachments" /></a></li>
5109     + </ul>
5110     + <!-- basic info -->
5111     + <div id="taskedit-tab-1">
5112     + <div class="form-section">
5113     + <label for="taskedit-title"><roundcube:label name="tasklist.title" /></label>
5114     + <br />
5115     + <input type="text" class="text" name="title" id="taskedit-title" size="60" tabindex="1" />
5116     + </div>
5117     + <div class="form-section">
5118     + <label for="taskedit-description"><roundcube:label name="tasklist.description" /></label>
5119     + <br />
5120     + <textarea name="description" id="taskedit-description" class="text" rows="5" cols="60" tabindex="2"></textarea>
5121     + </div>
5122     + <div class="form-section">
5123     + <label for="taskedit-tags"><roundcube:label name="tasklist.tags" /></label>
5124     + <roundcube:object name="plugin.tags_editline" id="taskedit-tagline" class="tagedit" tabindex="3" />
5125     + </div>
5126     + <div class="form-section">
5127     + <label for="taskedit-startdate"><roundcube:label name="tasklist.start" /></label>
5128     + <input type="text" name="startdate" size="10" id="taskedit-startdate" tabindex="23" /> &nbsp;
5129     + <input type="text" name="starttime" size="6" id="taskedit-starttime" tabindex="24" />
5130     + <a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#taskedit-startdate,#taskedit-starttime"><roundcube:label name="tasklist.nodate" /></a>
5131     + </div>
5132     + <div class="form-section">
5133     + <label for="taskedit-date"><roundcube:label name="tasklist.datetime" /></label>
5134     + <input type="text" name="date" size="10" id="taskedit-date" tabindex="20" /> &nbsp;
5135     + <input type="text" name="time" size="6" id="taskedit-time" tabindex="21" />
5136     + <a href="#nodate" style="margin-left:1em" class="edit-nodate" rel="#taskedit-date,#taskedit-time"><roundcube:label name="tasklist.nodate" /></a>
5137     + </div>
5138     + <div class="form-section" id="taskedit-alarms">
5139     + <label for="taskedit-alarm"><roundcube:label name="tasklist.alarms" /></label>
5140     + <roundcube:object name="plugin.alarm_select" />
5141     + </div>
5142     + <div class="form-section">
5143     + <label for="taskedit-completeness"><roundcube:label name="tasklist.complete" /></label>
5144     + <input type="text" name="title" id="taskedit-completeness" size="3" tabindex="25" />&nbsp;%
5145     + <div id="taskedit-completeness-slider"></div>
5146     + </div>
5147     + <div class="form-section" id="tasklist-select">
5148     + <label for="taskedit-tasklist"><roundcube:label name="tasklist.list" /></label>
5149     + <roundcube:object name="plugin.tasklist_select" id="taskedit-tasklist" tabindex="26" />
5150     + </div>
5151     + </div>
5152     + <!-- attachments list (with upload form) -->
5153     + <div id="taskedit-tab-2">
5154     + <div id="taskedit-attachments">
5155     + <roundcube:object name="plugin.attachments_list" id="taskedit-attachment-list" class="attachmentslist" />
5156     + </div>
5157     + <div id="taskedit-attachments-form">
5158     + <roundcube:object name="plugin.attachments_form" id="taskedit-attachment-form" attachmentFieldSize="30" />
5159     + </div>
5160     + <roundcube:object name="plugin.filedroparea" id="taskedit-tab-2" />
5161     + </div>
5162     + </form>
5163     +</div>
5164     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist_base.js roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist_base.js
5165     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist_base.js 1970-01-01 01:00:00.000000000 +0100
5166     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist_base.js 2013-11-24 00:38:51.000000000 +0100
5167     @@ -0,0 +1,94 @@
5168     +/**
5169     + * Client scripts for the Tasklist plugin
5170     + *
5171     + * @version @package_version@
5172     + * @author Thomas Bruederli <bruederli@kolabsys.com>
5173     + *
5174     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
5175     + *
5176     + * This program is free software: you can redistribute it and/or modify
5177     + * it under the terms of the GNU Affero General Public License as
5178     + * published by the Free Software Foundation, either version 3 of the
5179     + * License, or (at your option) any later version.
5180     + *
5181     + * This program is distributed in the hope that it will be useful,
5182     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5183     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5184     + * GNU Affero General Public License for more details.
5185     + *
5186     + * You should have received a copy of the GNU Affero General Public License
5187     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
5188     + */
5189     +
5190     +function rcube_tasklist(settings)
5191     +{
5192     + /* private vars */
5193     + var ui_loaded = false;
5194     + var me = this;
5195     +
5196     + /* public members */
5197     + this.ui;
5198     +
5199     + /* public methods */
5200     + this.create_from_mail = create_from_mail;
5201     + this.mail2taskdialog = mail2task_dialog;
5202     +
5203     +
5204     + /**
5205     + * Open a new task dialog prefilled with contents from the currently selected mail message
5206     + */
5207     + function create_from_mail()
5208     + {
5209     + var uid;
5210     + if ((uid = rcmail.get_single_uid())) {
5211     + // load calendar UI (scripts and edit dialog template)
5212     + if (!ui_loaded) {
5213     + $.when(
5214     + $.getScript('./plugins/tasklist/tasklist.js'),
5215     + $.getScript('./plugins/tasklist/jquery.tagedit.js'),
5216     + $.get(rcmail.url('tasks/inlineui'), function(html){ $(document.body).append(html); }, 'html')
5217     + ).then(function() {
5218     + // register attachments form
5219     + // rcmail.gui_object('attachmentlist', 'attachmentlist');
5220     +
5221     + ui_loaded = true;
5222     + me.ui = new rcube_tasklist_ui(settings);
5223     + create_from_mail(); // start over
5224     + });
5225     + return;
5226     + }
5227     + else {
5228     + // get message contents for task dialog
5229     + var lock = rcmail.set_busy(true, 'loading');
5230     + rcmail.http_post('tasks/mail2task', {
5231     + '_mbox': rcmail.env.mailbox,
5232     + '_uid': uid
5233     + }, lock);
5234     + }
5235     + }
5236     + }
5237     +
5238     + /**
5239     + * Callback function to put the given task properties into the dialog
5240     + */
5241     + function mail2task_dialog(prop)
5242     + {
5243     + this.ui.edit_task(null, 'new', prop);
5244     + }
5245     +
5246     +}
5247     +
5248     +/* tasklist plugin initialization (for email task) */
5249     +window.rcmail && rcmail.env.task == 'mail' && rcmail.addEventListener('init', function(evt) {
5250     + var tasks = new rcube_tasklist(rcmail.env.libcal_settings);
5251     +
5252     + rcmail.register_command('tasklist-create-from-mail', function() { tasks.create_from_mail() });
5253     + rcmail.addEventListener('plugin.mail2taskdialog', function(p){ tasks.mail2taskdialog(p) });
5254     + rcmail.addEventListener('plugin.unlock_saving', function(p){ tasks.ui && tasks.ui.unlock_saving(); });
5255     +
5256     + if (rcmail.env.action != 'show')
5257     + rcmail.env.message_commands.push('tasklist-create-from-mail');
5258     + else
5259     + rcmail.enable_command('tasklist-create-from-mail', true);
5260     +});
5261     +
5262     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist.js roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist.js
5263     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist.js 1970-01-01 01:00:00.000000000 +0100
5264     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist.js 2013-11-24 00:38:51.000000000 +0100
5265     @@ -0,0 +1,1669 @@
5266     +/**
5267     + * Client scripts for the Tasklist plugin
5268     + *
5269     + * @version @package_version@
5270     + * @author Thomas Bruederli <bruederli@kolabsys.com>
5271     + *
5272     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
5273     + *
5274     + * This program is free software: you can redistribute it and/or modify
5275     + * it under the terms of the GNU Affero General Public License as
5276     + * published by the Free Software Foundation, either version 3 of the
5277     + * License, or (at your option) any later version.
5278     + *
5279     + * This program is distributed in the hope that it will be useful,
5280     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
5281     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
5282     + * GNU Affero General Public License for more details.
5283     + *
5284     + * You should have received a copy of the GNU Affero General Public License
5285     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
5286     + */
5287     +
5288     +function rcube_tasklist_ui(settings)
5289     +{
5290     + // extend base class
5291     + rcube_libcalendaring.call(this, settings);
5292     +
5293     + /* constants */
5294     + var FILTER_MASK_ALL = 0;
5295     + var FILTER_MASK_TODAY = 1;
5296     + var FILTER_MASK_TOMORROW = 2;
5297     + var FILTER_MASK_WEEK = 4;
5298     + var FILTER_MASK_LATER = 8;
5299     + var FILTER_MASK_NODATE = 16;
5300     + var FILTER_MASK_OVERDUE = 32;
5301     + var FILTER_MASK_FLAGGED = 64;
5302     + var FILTER_MASK_COMPLETE = 128;
5303     +
5304     + var filter_masks = {
5305     + all: FILTER_MASK_ALL,
5306     + today: FILTER_MASK_TODAY,
5307     + tomorrow: FILTER_MASK_TOMORROW,
5308     + week: FILTER_MASK_WEEK,
5309     + later: FILTER_MASK_LATER,
5310     + nodate: FILTER_MASK_NODATE,
5311     + overdue: FILTER_MASK_OVERDUE,
5312     + flagged: FILTER_MASK_FLAGGED,
5313     + complete: FILTER_MASK_COMPLETE
5314     + };
5315     +
5316     + /* private vars */
5317     + var selector = 'all';
5318     + var tagsfilter = [];
5319     + var filtermask = FILTER_MASK_ALL;
5320     + var loadstate = { filter:-1, lists:'', search:null };
5321     + var idcount = 0;
5322     + var saving_lock;
5323     + var ui_loading;
5324     + var taskcounts = {};
5325     + var listindex = [];
5326     + var listdata = {};
5327     + var tags = [];
5328     + var draghelper;
5329     + var search_request;
5330     + var search_query;
5331     + var completeness_slider;
5332     + var me = this;
5333     +
5334     + // general datepicker settings
5335     + var datepicker_settings = {
5336     + // translate from PHP format to datepicker format
5337     + dateFormat: settings['date_format'].replace(/M/g, 'm').replace(/mmmmm/, 'MM').replace(/mmm/, 'M').replace(/dddd/, 'DD').replace(/ddd/, 'D').replace(/yy/g, 'y'),
5338     + firstDay : settings['first_day'],
5339     +// dayNamesMin: settings['days_short'],
5340     +// monthNames: settings['months'],
5341     +// monthNamesShort: settings['months'],
5342     + changeMonth: false,
5343     + showOtherMonths: true,
5344     + selectOtherMonths: true
5345     + };
5346     + var extended_datepicker_settings;
5347     +
5348     + /* public members */
5349     + this.tasklists = rcmail.env.tasklists;
5350     + this.selected_task;
5351     + this.selected_list;
5352     +
5353     + /* public methods */
5354     + this.init = init;
5355     + this.edit_task = task_edit_dialog;
5356     + this.delete_task = delete_task;
5357     + this.add_childtask = add_childtask;
5358     + this.quicksearch = quicksearch;
5359     + this.reset_search = reset_search;
5360     + this.list_remove = list_remove;
5361     + this.list_edit_dialog = list_edit_dialog;
5362     + this.unlock_saving = unlock_saving;
5363     +
5364     + /* imports */
5365     + var Q = this.quote_html;
5366     + var text2html = this.text2html;
5367     + var event_date_text = this.event_date_text;
5368     + var parse_datetime = this.parse_datetime;
5369     + var date2unixtime = this.date2unixtime;
5370     + var fromunixtime = this.fromunixtime;
5371     + var init_alarms_edit = this.init_alarms_edit;
5372     +
5373     + /**
5374     + * initialize the tasks UI
5375     + */
5376     + function init()
5377     + {
5378     + // initialize task list selectors
5379     + for (var id in me.tasklists) {
5380     + if ((li = rcmail.get_folder_li(id, 'rcmlitasklist'))) {
5381     + init_tasklist_li(li, id);
5382     + }
5383     +
5384     + if (me.tasklists[id].editable && !me.selected_list) {
5385     + me.selected_list = id;
5386     + rcmail.enable_command('addtask', true);
5387     + $(li).click();
5388     + }
5389     + }
5390     +
5391     + // register server callbacks
5392     + rcmail.addEventListener('plugin.data_ready', data_ready);
5393     + rcmail.addEventListener('plugin.refresh_task', update_taskitem);
5394     + rcmail.addEventListener('plugin.update_counts', update_counts);
5395     + rcmail.addEventListener('plugin.insert_tasklist', insert_list);
5396     + rcmail.addEventListener('plugin.update_tasklist', update_list);
5397     + rcmail.addEventListener('plugin.destroy_tasklist', destroy_list);
5398     + rcmail.addEventListener('plugin.reload_data', function(){ list_tasks(null); });
5399     + rcmail.addEventListener('plugin.unlock_saving', unlock_saving);
5400     +
5401     + // start loading tasks
5402     + fetch_counts();
5403     + list_tasks();
5404     +
5405     + // register event handlers for UI elements
5406     + $('#taskselector a').click(function(e){
5407     + if (!$(this).parent().hasClass('inactive'))
5408     + list_tasks(this.href.replace(/^.*#/, ''));
5409     + return false;
5410     + });
5411     +
5412     + // quick-add a task
5413     + $(rcmail.gui_objects.quickaddform).submit(function(e){
5414     + var tasktext = this.elements.text.value,
5415     + rec = { id:-(++idcount), title:tasktext, readonly:true, mask:0, complete:0 };
5416     +
5417     + if (tasktext && tasktext.length) {
5418     + save_task({ tempid:rec.id, raw:tasktext, list:me.selected_list }, 'new');
5419     + render_task(rec);
5420     +
5421     + $('#listmessagebox').hide();
5422     + }
5423     +
5424     + // clear form
5425     + this.reset();
5426     + return false;
5427     + }).find('input[type=text]').placeholder(rcmail.gettext('createnewtask','tasklist'));
5428     +
5429     + // click-handler on tags list
5430     + $(rcmail.gui_objects.tagslist).click(function(e){
5431     + if (e.target.nodeName != 'LI')
5432     + return false;
5433     +
5434     + var item = $(e.target),
5435     + tag = item.data('value');
5436     +
5437     + // reset selection on regular clicks
5438     + var index = $.inArray(tag, tagsfilter);
5439     + var shift = e.shiftKey || e.ctrlKey || e.metaKey;
5440     +
5441     + if (!shift) {
5442     + if (tagsfilter.length > 1)
5443     + index = -1;
5444     +
5445     + $('li', this).removeClass('selected');
5446     + tagsfilter = [];
5447     + }
5448     +
5449     + // add tag to filter
5450     + if (index < 0) {
5451     + item.addClass('selected');
5452     + tagsfilter.push(tag);
5453     + }
5454     + else if (shift) {
5455     + item.removeClass('selected');
5456     + var a = tagsfilter.slice(0,index);
5457     + tagsfilter = a.concat(tagsfilter.slice(index+1));
5458     + }
5459     +
5460     + list_tasks();
5461     +
5462     + // clear text selection in IE after shift+click
5463     + if (shift && document.selection)
5464     + document.selection.empty();
5465     +
5466     + e.preventDefault();
5467     + return false;
5468     + })
5469     + .mousedown(function(e){
5470     + // disable content selection with the mouse
5471     + e.preventDefault();
5472     + return false;
5473     + });
5474     +
5475     + // click-handler on task list items (delegate)
5476     + $(rcmail.gui_objects.resultlist).click(function(e){
5477     + var item = $(e.target);
5478     + var className = e.target.className;
5479     +
5480     + if (item.hasClass('childtoggle')) {
5481     + item = item.parent().find('.taskhead');
5482     + className = 'childtoggle';
5483     + }
5484     + else if (!item.hasClass('taskhead'))
5485     + item = item.closest('div.taskhead');
5486     +
5487     + // ignore
5488     + if (!item.length)
5489     + return false;
5490     +
5491     + var id = item.data('id'),
5492     + li = item.parent(),
5493     + rec = listdata[id];
5494     +
5495     + switch (className) {
5496     + case 'childtoggle':
5497     + rec.collapsed = !rec.collapsed;
5498     + li.children('.childtasks:first').toggle();
5499     + $(e.target).toggleClass('collapsed').html(rec.collapsed ? '&#9654;' : '&#9660;');
5500     + rcmail.http_post('tasks/task', { action:'collapse', t:{ id:rec.id, list:rec.list }, collapsed:rec.collapsed?1:0 });
5501     + break;
5502     +
5503     + case 'complete':
5504     + if (rcmail.busy)
5505     + return false;
5506     +
5507     + rec.complete = e.target.checked ? 1 : 0;
5508     + li.toggleClass('complete');
5509     + save_task(rec, 'edit');
5510     + return true;
5511     +
5512     + case 'flagged':
5513     + if (rcmail.busy)
5514     + return false;
5515     +
5516     + rec.flagged = rec.flagged ? 0 : 1;
5517     + li.toggleClass('flagged');
5518     + save_task(rec, 'edit');
5519     + break;
5520     +
5521     + case 'date':
5522     + if (rcmail.busy)
5523     + return false;
5524     +
5525     + var link = $(e.target).html(''),
5526     + input = $('<input type="text" size="10" />').appendTo(link).val(rec.date || '')
5527     +
5528     + input.datepicker($.extend({
5529     + onClose: function(dateText, inst) {
5530     + if (dateText != (rec.date || '')) {
5531     + rec.date = dateText;
5532     + save_task(rec, 'edit');
5533     + }
5534     + input.datepicker('destroy').remove();
5535     + link.html(dateText || rcmail.gettext('nodate','tasklist'));
5536     + }
5537     + }, extended_datepicker_settings)
5538     + )
5539     + .datepicker('setDate', rec.date)
5540     + .datepicker('show');
5541     + break;
5542     +
5543     + case 'delete':
5544     + delete_task(id);
5545     + break;
5546     +
5547     + case 'actions':
5548     + var pos, ref = $(e.target),
5549     + menu = $('#taskitemmenu');
5550     + if (menu.is(':visible') && menu.data('refid') == id) {
5551     + menu.hide();
5552     + }
5553     + else {
5554     + pos = ref.offset();
5555     + pos.top += ref.outerHeight();
5556     + pos.left += ref.width() - menu.outerWidth();
5557     + menu.css({ top:pos.top+'px', left:pos.left+'px' }).show();
5558     + menu.data('refid', id);
5559     + me.selected_task = rec;
5560     + }
5561     + e.bubble = false;
5562     + break;
5563     +
5564     + default:
5565     + if (e.target.nodeName != 'INPUT')
5566     + task_show_dialog(id);
5567     + break;
5568     + }
5569     +
5570     + return false;
5571     + })
5572     + .dblclick(function(e){
5573     + var id, rec, item = $(e.target);
5574     + if (!item.hasClass('taskhead'))
5575     + item = item.closest('div.taskhead');
5576     +
5577     + if (!rcmail.busy && item.length && (id = item.data('id')) && (rec = listdata[id])) {
5578     + var list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] : {};
5579     + if (rec.readonly || !list.editable)
5580     + task_show_dialog(id);
5581     + else
5582     + task_edit_dialog(id, 'edit');
5583     + clearSelection();
5584     + }
5585     + });
5586     +
5587     + // handle global document clicks: close popup menus
5588     + $(document.body).click(clear_popups);
5589     +
5590     + // extended datepicker settings
5591     + var extended_datepicker_settings = $.extend({
5592     + showButtonPanel: true,
5593     + beforeShow: function(input, inst) {
5594     + setTimeout(function(){
5595     + $(input).datepicker('widget').find('button.ui-datepicker-close')
5596     + .html(rcmail.gettext('nodate','tasklist'))
5597     + .attr('onclick', '')
5598     + .click(function(e){
5599     + $(input).datepicker('setDate', null).datepicker('hide');
5600     + });
5601     + }, 1);
5602     + }
5603     + }, datepicker_settings);
5604     + }
5605     +
5606     + /**
5607     + * initialize task edit form elements
5608     + */
5609     + function init_taskedit()
5610     + {
5611     + $('#taskedit').tabs();
5612     +
5613     + var completeness_slider_change = function(e, ui){
5614     + var v = completeness_slider.slider('value');
5615     + if (v >= 98) v = 100;
5616     + if (v <= 2) v = 0;
5617     + $('#taskedit-completeness').val(v);
5618     + };
5619     + completeness_slider = $('#taskedit-completeness-slider').slider({
5620     + range: 'min',
5621     + animate: 'fast',
5622     + slide: completeness_slider_change,
5623     + change: completeness_slider_change
5624     + });
5625     + $('#taskedit-completeness').change(function(e){
5626     + completeness_slider.slider('value', parseInt(this.value))
5627     + });
5628     +
5629     + // register events on alarm fields
5630     + init_alarms_edit('#taskedit');
5631     +
5632     + $('#taskedit-date, #taskedit-startdate').datepicker(datepicker_settings);
5633     +
5634     + $('a.edit-nodate').click(function(){
5635     + var sel = $(this).attr('rel');
5636     + if (sel) $(sel).val('');
5637     + return false;
5638     + });
5639     + }
5640     +
5641     + /**
5642     + * Request counts from the server
5643     + */
5644     + function fetch_counts()
5645     + {
5646     + var active = active_lists();
5647     + if (active.length)
5648     + rcmail.http_request('counts', { lists:active.join(',') });
5649     + else
5650     + update_counts({});
5651     + }
5652     +
5653     + /**
5654     + * List tasks matching the given selector
5655     + */
5656     + function list_tasks(sel)
5657     + {
5658     + if (rcmail.busy)
5659     + return;
5660     +
5661     + if (sel && filter_masks[sel] !== undefined) {
5662     + filtermask = filter_masks[sel];
5663     + selector = sel;
5664     + }
5665     +
5666     + var active = active_lists(),
5667     + basefilter = filtermask == FILTER_MASK_COMPLETE ? FILTER_MASK_COMPLETE : FILTER_MASK_ALL,
5668     + reload = active.join(',') != loadstate.lists || basefilter != loadstate.filter || loadstate.search != search_query;
5669     +
5670     + if (active.length && reload) {
5671     + ui_loading = rcmail.set_busy(true, 'loading');
5672     + rcmail.http_request('fetch', { filter:basefilter, lists:active.join(','), q:search_query }, true);
5673     + }
5674     + else if (reload)
5675     + data_ready({ data:[], lists:'', filter:basefilter, search:search_query });
5676     + else
5677     + render_tasklist();
5678     +
5679     + $('#taskselector li.selected').removeClass('selected');
5680     + $('#taskselector li.'+selector).addClass('selected');
5681     + }
5682     +
5683     + /**
5684     + * Remove all tasks of the given list from the UI
5685     + */
5686     + function remove_tasks(list_id)
5687     + {
5688     + // remove all tasks of the given list from index
5689     + var newindex = $.grep(listindex, function(id, i){
5690     + return listdata[id] && listdata[id].list != list_id;
5691     + });
5692     +
5693     + listindex = newindex;
5694     + render_tasklist();
5695     +
5696     + // avoid reloading
5697     + me.tasklists[list_id].active = false;
5698     + loadstate.lists = active_lists();
5699     + }
5700     +
5701     + /**
5702     + * Callback if task data from server is ready
5703     + */
5704     + function data_ready(response)
5705     + {
5706     + listdata = {};
5707     + listindex = [];
5708     + loadstate.lists = response.lists;
5709     + loadstate.filter = response.filter;
5710     + loadstate.search = response.search;
5711     +
5712     + for (var id, i=0; i < response.data.length; i++) {
5713     + id = response.data[i].id;
5714     + listindex.push(id);
5715     + listdata[id] = response.data[i];
5716     + listdata[id].children = [];
5717     + // register a forward-pointer to child tasks
5718     + if (listdata[id].parent_id && listdata[listdata[id].parent_id])
5719     + listdata[listdata[id].parent_id].children.push(id);
5720     + }
5721     +
5722     + render_tasklist();
5723     + append_tags(response.tags || []);
5724     + rcmail.set_busy(false, 'loading', ui_loading);
5725     + }
5726     +
5727     + /**
5728     + *
5729     + */
5730     + function render_tasklist()
5731     + {
5732     + // clear display
5733     + var id, rec,
5734     + count = 0,
5735     + msgbox = $('#listmessagebox').hide(),
5736     + list = $(rcmail.gui_objects.resultlist).html('');
5737     +
5738     + for (var i=0; i < listindex.length; i++) {
5739     + id = listindex[i];
5740     + rec = listdata[id];
5741     + if (match_filter(rec)) {
5742     + render_task(rec);
5743     + count++;
5744     + }
5745     + }
5746     +
5747     + fix_tree_toggles();
5748     +
5749     + if (!count)
5750     + msgbox.html(rcmail.gettext('notasksfound','tasklist')).show();
5751     + }
5752     +
5753     + /**
5754     + * Show/hide child toggle buttons on all visible task items
5755     + */
5756     + function fix_tree_toggles()
5757     + {
5758     + $('.taskitem', rcmail.gui_objects.resultlist).each(function(i,elem){
5759     + var li = $(elem),
5760     + rec = listdata[li.attr('rel')],
5761     + childs = $('.childtasks li', li);
5762     +
5763     + $('.childtoggle', li)[(childs.length ? 'show' : 'hide')]();
5764     + })
5765     + }
5766     +
5767     + /**
5768     + *
5769     + */
5770     + function append_tags(taglist)
5771     + {
5772     + // find new tags
5773     + var newtags = [];
5774     + for (var i=0; i < taglist.length; i++) {
5775     + if ($.inArray(taglist[i], tags) < 0)
5776     + newtags.push(taglist[i]);
5777     + }
5778     + tags = tags.concat(newtags);
5779     +
5780     + // append new tags to tag cloud
5781     + $.each(newtags, function(i, tag){
5782     + $('<li>').attr('rel', tag).data('value', tag).html(Q(tag)).appendTo(rcmail.gui_objects.tagslist);
5783     + });
5784     +
5785     + // re-sort tags list
5786     + $(rcmail.gui_objects.tagslist).children('li').sortElements(function(a,b){
5787     + return $.text([a]).toLowerCase() > $.text([b]).toLowerCase() ? 1 : -1;
5788     + });
5789     + }
5790     +
5791     + /**
5792     + *
5793     + */
5794     + function update_counts(counts)
5795     + {
5796     + // got new data
5797     + if (counts)
5798     + taskcounts = counts;
5799     +
5800     + // iterate over all selector links and update counts
5801     + $('#taskselector a').each(function(i, elem){
5802     + var link = $(elem),
5803     + f = link.parent().attr('class').replace(/\s\w+/, '');
5804     + if (f != 'all')
5805     + link.children('span').html(taskcounts[f] || '')[(taskcounts[f] ? 'show' : 'hide')]();
5806     + });
5807     +
5808     + // spacial case: overdue
5809     + $('#taskselector li.overdue')[(taskcounts.overdue ? 'removeClass' : 'addClass')]('inactive');
5810     + }
5811     +
5812     + /**
5813     + * Callback from server to update a single task item
5814     + */
5815     + function update_taskitem(rec)
5816     + {
5817     + // handle a list of task records
5818     + if ($.isArray(rec)) {
5819     + $.each(rec, function(i,r){ update_taskitem(r); });
5820     + return;
5821     + }
5822     +
5823     + var id = rec.id,
5824     + oldid = rec.tempid || id,
5825     + oldindex = $.inArray(oldid, listindex),
5826     + list = me.tasklists[rec.list];
5827     +
5828     + if (oldindex >= 0)
5829     + listindex[oldindex] = id;
5830     + else
5831     + listindex.push(id);
5832     +
5833     + listdata[id] = rec;
5834     +
5835     + // register a forward-pointer to child tasks
5836     + if (rec.parent_id && listdata[rec.parent_id] && listdata[rec.parent_id].children && $.inArray(id, listdata[rec.parent_id].children) >= 0)
5837     + listdata[rec.parent_id].children.push(id);
5838     +
5839     + if (list.active)
5840     + render_task(rec, oldid);
5841     + else
5842     + $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).remove();
5843     +
5844     + append_tags(rec.tags || []);
5845     + fix_tree_toggles();
5846     + }
5847     +
5848     + /**
5849     + * Submit the given (changed) task record to the server
5850     + */
5851     + function save_task(rec, action)
5852     + {
5853     + if (!rcmail.busy) {
5854     + saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
5855     + rcmail.http_post('tasks/task', { action:action, t:rec, filter:filtermask });
5856     + $('button.ui-button:ui-button').button('option', 'disabled', rcmail.busy);
5857     + return true;
5858     + }
5859     +
5860     + return false;
5861     + }
5862     +
5863     + /**
5864     + * Remove saving lock and free the UI for new input
5865     + */
5866     + function unlock_saving()
5867     + {
5868     + if (saving_lock) {
5869     + rcmail.set_busy(false, null, saving_lock);
5870     + $('button.ui-button:ui-button').button('option', 'disabled', false);
5871     + }
5872     + }
5873     +
5874     + /**
5875     + * Render the given task into the tasks list
5876     + */
5877     + function render_task(rec, replace)
5878     + {
5879     + var tags_html = '';
5880     + for (var j=0; rec.tags && j < rec.tags.length; j++)
5881     + tags_html += '<span class="tag">' + Q(rec.tags[j]) + '</span>';
5882     +
5883     + var div = $('<div>').addClass('taskhead').html(
5884     + '<div class="progressbar"><div class="progressvalue" style="width:' + (rec.complete * 100) + '%"></div></div>' +
5885     + '<input type="checkbox" name="completed[]" value="1" class="complete" ' + (rec.complete == 1.0 ? 'checked="checked" ' : '') + '/>' +
5886     + '<span class="flagged"></span>' +
5887     + '<span class="title">' + Q(rec.title) + '</span>' +
5888     + '<span class="tags">' + tags_html + '</span>' +
5889     + '<span class="date">' + Q(rec.date || rcmail.gettext('nodate','tasklist')) + '</span>' +
5890     + '<a href="#" class="actions">V</a>'
5891     + )
5892     + .data('id', rec.id)
5893     + .draggable({
5894     + revert: 'invalid',
5895     + addClasses: false,
5896     + cursorAt: { left:-10, top:12 },
5897     + helper: draggable_helper,
5898     + appendTo: 'body',
5899     + start: draggable_start,
5900     + stop: draggable_stop,
5901     + revertDuration: 300
5902     + });
5903     +
5904     + if (rec.complete == 1.0)
5905     + div.addClass('complete');
5906     + if (rec.flagged)
5907     + div.addClass('flagged');
5908     + if (!rec.date)
5909     + div.addClass('nodate');
5910     + if ((rec.mask & FILTER_MASK_OVERDUE))
5911     + div.addClass('overdue');
5912     +
5913     + var li, inplace = false, parent = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > ul.childtasks', rcmail.gui_objects.resultlist) : null;
5914     + if (replace && (li = $('li[rel="'+replace+'"]', rcmail.gui_objects.resultlist)) && li.length) {
5915     + li.children('div.taskhead').first().replaceWith(div);
5916     + li.attr('rel', rec.id);
5917     + inplace = true;
5918     + }
5919     + else {
5920     + li = $('<li>')
5921     + .attr('rel', rec.id)
5922     + .addClass('taskitem')
5923     + .append((rec.collapsed ? '<span class="childtoggle collapsed">&#9654;' : '<span class="childtoggle expanded">&#9660;') + '</span>')
5924     + .append(div)
5925     + .append('<ul class="childtasks" style="' + (rec.collapsed ? 'display:none' : '') + '"></ul>');
5926     +
5927     + if (!parent || !parent.length)
5928     + li.appendTo(rcmail.gui_objects.resultlist);
5929     + }
5930     +
5931     + if (!inplace && parent && parent.length)
5932     + li.appendTo(parent);
5933     +
5934     + if (replace) {
5935     + resort_task(rec, li, true);
5936     + // TODO: remove the item after a while if it doesn't match the current filter anymore
5937     + }
5938     + }
5939     +
5940     + /**
5941     + * Move the given task item to the right place in the list
5942     + */
5943     + function resort_task(rec, li, animated)
5944     + {
5945     + var dir = 0, index, slice, next_li, next_id, next_rec;
5946     +
5947     + // animated moving
5948     + var insert_animated = function(li, before, after) {
5949     + if (before && li.next().get(0) == before.get(0))
5950     + return; // nothing to do
5951     + else if (after && li.prev().get(0) == after.get(0))
5952     + return; // nothing to do
5953     +
5954     + var speed = 300;
5955     + li.slideUp(speed, function(){
5956     + if (before) li.insertBefore(before);
5957     + else if (after) li.insertAfter(after);
5958     + li.slideDown(speed);
5959     + });
5960     + }
5961     +
5962     + // remove from list index
5963     + var oldlist = listindex.join('%%%');
5964     + var oldindex = $.inArray(rec.id, listindex);
5965     + if (oldindex >= 0) {
5966     + slice = listindex.slice(0,oldindex);
5967     + listindex = slice.concat(listindex.slice(oldindex+1));
5968     + }
5969     +
5970     + // find the right place to insert the task item
5971     + li.siblings().each(function(i, elem){
5972     + next_li = $(elem);
5973     + next_id = next_li.attr('rel');
5974     + next_rec = listdata[next_id];
5975     +
5976     + if (next_id == rec.id) {
5977     + next_li = null;
5978     + return 1; // continue
5979     + }
5980     +
5981     + if (next_rec && task_cmp(rec, next_rec) > 0) {
5982     + return 1; // continue;
5983     + }
5984     + else if (next_rec && next_li && task_cmp(rec, next_rec) < 0) {
5985     + if (animated) insert_animated(li, next_li);
5986     + else li.insertBefore(next_li);
5987     + next_li = null;
5988     + return false;
5989     + }
5990     + });
5991     +
5992     + index = $.inArray(next_id, listindex);
5993     +
5994     + if (next_li) {
5995     + if (animated) insert_animated(li, null, next_li);
5996     + else li.insertAfter(next_li);
5997     + index++;
5998     + }
5999     +
6000     + // insert into list index
6001     + if (next_id && index >= 0) {
6002     + slice = listindex.slice(0,index);
6003     + slice.push(rec.id);
6004     + listindex = slice.concat(listindex.slice(index));
6005     + }
6006     + else { // restore old list index
6007     + listindex = oldlist.split('%%%');
6008     + }
6009     + }
6010     +
6011     + /**
6012     + * Compare function of two task records.
6013     + * (used for sorting)
6014     + */
6015     + function task_cmp(a, b)
6016     + {
6017     + var d = Math.floor(a.complete) - Math.floor(b.complete);
6018     + if (!d) d = (b._hasdate-0) - (a._hasdate-0);
6019     + if (!d) d = (a.datetime||99999999999) - (b.datetime||99999999999);
6020     + return d;
6021     + }
6022     +
6023     + /**
6024     + *
6025     + */
6026     + function get_all_childs(id)
6027     + {
6028     + var cid, childs = [];
6029     + for (var i=0; listdata[id].children && i < listdata[id].children.length; i++) {
6030     + cid = listdata[id].children[i];
6031     + childs.push(cid);
6032     + childs = childs.concat(get_all_childs(cid));
6033     + }
6034     +
6035     + return childs;
6036     + }
6037     +
6038     +
6039     + /* Helper functions for drag & drop functionality */
6040     +
6041     + function draggable_helper()
6042     + {
6043     + if (!draghelper)
6044     + draghelper = $('<div class="taskitem-draghelper">&#x2714;</div>');
6045     +
6046     + return draghelper;
6047     + }
6048     +
6049     + function draggable_start(event, ui)
6050     + {
6051     + $('.taskhead, #rootdroppable, #'+rcmail.gui_objects.folderlist.id+' li').droppable({
6052     + hoverClass: 'droptarget',
6053     + accept: droppable_accept,
6054     + drop: draggable_dropped,
6055     + addClasses: false
6056     + });
6057     +
6058     + $(this).parent().addClass('dragging');
6059     + $('#rootdroppable').show();
6060     + }
6061     +
6062     + function draggable_stop(event, ui)
6063     + {
6064     + $(this).parent().removeClass('dragging');
6065     + $('#rootdroppable').hide();
6066     + }
6067     +
6068     + function droppable_accept(draggable)
6069     + {
6070     + if (rcmail.busy)
6071     + return false;
6072     +
6073     + var drag_id = draggable.data('id'),
6074     + drop_id = $(this).data('id'),
6075     + drag_rec = listdata[drag_id] || {},
6076     + drop_rec = listdata[drop_id];
6077     +
6078     + // drop target is another list
6079     + if (drag_rec && $(this).data('type') == 'tasklist') {
6080     + var drop_list = me.tasklists[drop_id],
6081     + from_list = me.tasklists[drag_rec.list];
6082     + return !drag_rec.parent_id && drop_id != drag_rec.list && drop_list && drop_list.editable && from_list && from_list.editable;
6083     + }
6084     +
6085     + if (drop_rec && drop_rec.list != drag_rec.list)
6086     + return false;
6087     +
6088     + if (drop_id == drag_rec.parent_id)
6089     + return false;
6090     +
6091     + while (drop_rec && drop_rec.parent_id) {
6092     + if (drop_rec.parent_id == drag_id)
6093     + return false;
6094     + drop_rec = listdata[drop_rec.parent_id];
6095     + }
6096     +
6097     + return true;
6098     + }
6099     +
6100     + function draggable_dropped(event, ui)
6101     + {
6102     + var drop_id = $(this).data('id'),
6103     + task_id = ui.draggable.data('id'),
6104     + rec = listdata[task_id],
6105     + parent, li;
6106     +
6107     + // dropped on another list -> move
6108     + if ($(this).data('type') == 'tasklist') {
6109     + if (rec) {
6110     + save_task({ id:rec.id, list:drop_id, _fromlist:rec.list }, 'move');
6111     + rec.list = drop_id;
6112     + }
6113     + }
6114     + // dropped on a new parent task or root
6115     + else {
6116     + parent = drop_id ? $('li[rel="'+drop_id+'"] > ul.childtasks', rcmail.gui_objects.resultlist) : $(rcmail.gui_objects.resultlist)
6117     +
6118     + if (rec && parent.length) {
6119     + // submit changes to server
6120     + rec.parent_id = drop_id || 0;
6121     + save_task(rec, 'edit');
6122     +
6123     + li = ui.draggable.parent();
6124     + li.slideUp(300, function(){
6125     + li.appendTo(parent);
6126     + resort_task(rec, li);
6127     + li.slideDown(300);
6128     + fix_tree_toggles();
6129     + });
6130     + }
6131     + }
6132     + }
6133     +
6134     +
6135     + /**
6136     + * Show task details in a dialog
6137     + */
6138     + function task_show_dialog(id)
6139     + {
6140     + var $dialog = $('#taskshow'), rec;
6141     +
6142     + if ($dialog.is(':ui-dialog'))
6143     + $dialog.dialog('close');
6144     +
6145     + if (!(rec = listdata[id]) || clear_popups({}))
6146     + return;
6147     +
6148     + me.selected_task = rec;
6149     +
6150     + // fill dialog data
6151     + $('#task-parent-title').html(Q(rec.parent_title || '')+' &raquo;').css('display', rec.parent_title ? 'block' : 'none');
6152     + $('#task-title').html(Q(rec.title || ''));
6153     + $('#task-description').html(text2html(rec.description || '', 300, 6))[(rec.description ? 'show' : 'hide')]();
6154     + $('#task-date')[(rec.date ? 'show' : 'hide')]().children('.task-text').html(Q(rec.date || rcmail.gettext('nodate','tasklist')));
6155     + $('#task-time').html(Q(rec.time || ''));
6156     + $('#task-start')[(rec.startdate ? 'show' : 'hide')]().children('.task-text').html(Q(rec.startdate || ''));
6157     + $('#task-starttime').html(Q(rec.starttime || ''));
6158     + $('#task-alarm')[(rec.alarms_text ? 'show' : 'hide')]().children('.task-text').html(Q(rec.alarms_text));
6159     + $('#task-completeness .task-text').html(((rec.complete || 0) * 100) + '%');
6160     + $('#task-list .task-text').html(Q(me.tasklists[rec.list] ? me.tasklists[rec.list].name : ''));
6161     +
6162     + var taglist = $('#task-tags')[(rec.tags && rec.tags.length ? 'show' : 'hide')]().children('.task-text').empty();
6163     + if (rec.tags && rec.tags.length) {
6164     + $.each(rec.tags, function(i,val){
6165     + $('<span>').addClass('tag-element').html(Q(val)).data('value', val).appendTo(taglist);
6166     + });
6167     + }
6168     +
6169     + // build attachments list
6170     + $('#task-attachments').hide();
6171     + if ($.isArray(rec.attachments)) {
6172     + task_show_attachments(rec.attachments || [], $('#task-attachments').children('.task-text'), rec);
6173     + if (rec.attachments.length > 0) {
6174     + $('#task-attachments').show();
6175     + }
6176     + }
6177     +
6178     + // define dialog buttons
6179     + var buttons = [];
6180     + buttons.push({
6181     + text: rcmail.gettext('edit','tasklist'),
6182     + click: function() {
6183     + task_edit_dialog(me.selected_task.id, 'edit');
6184     + },
6185     + disabled: rcmail.busy
6186     + });
6187     +
6188     + buttons.push({
6189     + text: rcmail.gettext('delete','tasklist'),
6190     + click: function() {
6191     + if (delete_task(me.selected_task.id))
6192     + $dialog.dialog('close');
6193     + },
6194     + disabled: rcmail.busy
6195     + });
6196     +
6197     + // open jquery UI dialog
6198     + $dialog.dialog({
6199     + modal: false,
6200     + resizable: true,
6201     + closeOnEscape: true,
6202     + title: rcmail.gettext('taskdetails', 'tasklist'),
6203     + open: function() {
6204     + $dialog.parent().find('.ui-button').first().focus();
6205     + },
6206     + close: function() {
6207     + $dialog.dialog('destroy').appendTo(document.body);
6208     + },
6209     + buttons: buttons,
6210     + minWidth: 500,
6211     + width: 580
6212     + }).show();
6213     +
6214     + // set dialog size according to content
6215     + me.dialog_resize($dialog.get(0), $dialog.height(), 580);
6216     + }
6217     +
6218     + /**
6219     + * Opens the dialog to edit a task
6220     + */
6221     + function task_edit_dialog(id, action, presets)
6222     + {
6223     + $('#taskshow:ui-dialog').dialog('close');
6224     +
6225     + var rec = listdata[id] || presets,
6226     + $dialog = $('<div>'),
6227     + editform = $('#taskedit'),
6228     + list = rec.list && me.tasklists[rec.list] ? me.tasklists[rec.list] :
6229     + (me.selected_list ? me.tasklists[me.selected_list] : { editable: action=='new' });
6230     +
6231     + if (rcmail.busy || !list.editable || (action == 'edit' && (!rec || rec.readonly)))
6232     + return false;
6233     +
6234     + me.selected_task = $.extend({ alarms:'' }, rec); // clone task object
6235     + rec = me.selected_task;
6236     +
6237     + // assign temporary id
6238     + if (!me.selected_task.id)
6239     + me.selected_task.id = -(++idcount);
6240     +
6241     + // reset dialog first
6242     + $('#taskeditform').get(0).reset();
6243     +
6244     + // fill form data
6245     + var title = $('#taskedit-title').val(rec.title || '');
6246     + var description = $('#taskedit-description').val(rec.description || '');
6247     + var recdate = $('#taskedit-date').val(rec.date || '');
6248     + var rectime = $('#taskedit-time').val(rec.time || '');
6249     + var recstartdate = $('#taskedit-startdate').val(rec.startdate || '');
6250     + var recstarttime = $('#taskedit-starttime').val(rec.starttime || '');
6251     + var complete = $('#taskedit-completeness').val((rec.complete || 0) * 100);
6252     + completeness_slider.slider('value', complete.val());
6253     + var tasklist = $('#taskedit-tasklist').val(rec.list || 0).prop('disabled', rec.parent_id ? true : false);
6254     +
6255     + // tag-edit line
6256     + var tagline = $(rcmail.gui_objects.edittagline).empty();
6257     + $.each(typeof rec.tags == 'object' && rec.tags.length ? rec.tags : [''], function(i,val){
6258     + $('<input>')
6259     + .attr('name', 'tags[]')
6260     + .attr('tabindex', '3')
6261     + .addClass('tag')
6262     + .val(val)
6263     + .appendTo(tagline);
6264     + });
6265     +
6266     + $('input.tag', rcmail.gui_objects.edittagline).tagedit({
6267     + animSpeed: 100,
6268     + allowEdit: false,
6269     + checkNewEntriesCaseSensitive: false,
6270     + autocompleteOptions: { source: tags, minLength: 0 },
6271     + texts: { removeLinkTitle: rcmail.gettext('removetag', 'tasklist') }
6272     + });
6273     +
6274     + // set alarm(s)
6275     + if (rec.alarms || action != 'new') {
6276     + var valarms = (typeof rec.alarms == 'string' ? rec.alarms.split(';') : rec.alarms) || [''];
6277     + for (var alarm, i=0; i < valarms.length; i++) {
6278     + alarm = String(valarms[i]).split(':');
6279     + if (!alarm[1] && alarm[0]) alarm[1] = 'DISPLAY';
6280     + $('#taskedit select.edit-alarm-type').val(alarm[1]);
6281     +
6282     + if (alarm[0].match(/@(\d+)/)) {
6283     + var ondate = fromunixtime(parseInt(RegExp.$1));
6284     + $('#taskedit select.edit-alarm-offset').val('@');
6285     + $('#taskedit input.edit-alarm-date').val(me.format_datetime(ondate, 1));
6286     + $('#taskedit input.edit-alarm-time').val(me.format_datetime(ondate, 2));
6287     + }
6288     + else if (alarm[0].match(/([-+])(\d+)([MHD])/)) {
6289     + $('#taskedit input.edit-alarm-value').val(RegExp.$2);
6290     + $('#taskedit select.edit-alarm-offset').val(''+RegExp.$1+RegExp.$3);
6291     + }
6292     +
6293     + break; // only one alarm is currently supported
6294     + }
6295     + }
6296     + // set correct visibility by triggering onchange handlers
6297     + $('#taskedit select.edit-alarm-type, #taskedit select.edit-alarm-offset').change();
6298     +
6299     + // attachments
6300     + rcmail.enable_command('remove-attachment', list.editable);
6301     + me.selected_task.deleted_attachments = [];
6302     + // we're sharing some code for uploads handling with app.js
6303     + rcmail.env.attachments = [];
6304     + rcmail.env.compose_id = me.selected_task.id; // for rcmail.async_upload_form()
6305     +
6306     + if ($.isArray(rec.attachments)) {
6307     + task_show_attachments(rec.attachments, $('#taskedit-attachments'), rec, true);
6308     + }
6309     + else {
6310     + $('#taskedit-attachments > ul').empty();
6311     + }
6312     +
6313     + // show/hide tabs according to calendar's feature support
6314     + $('#taskedit-tab-attachments')[(list.attachments||rec.attachments?'show':'hide')]();
6315     +
6316     + // activate the first tab
6317     + $('#taskedit').tabs('select', 0);
6318     +
6319     + // define dialog buttons
6320     + var buttons = {};
6321     + buttons[rcmail.gettext('save', 'tasklist')] = function() {
6322     + // copy form field contents into task object to save
6323     + $.each({ title:title, description:description, date:recdate, time:rectime, startdate:recstartdate, starttime:recstarttime, list:tasklist }, function(key,input){
6324     + me.selected_task[key] = input.val();
6325     + });
6326     + me.selected_task.tags = [];
6327     + me.selected_task.attachments = [];
6328     +
6329     + // do some basic input validation
6330     + if (!me.selected_task.title || !me.selected_task.title.length) {
6331     + title.focus();
6332     + return false;
6333     + }
6334     + else if (me.selected_task.startdate && me.selected_task.date) {
6335     + var startdate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.startdate, datepicker_settings);
6336     + var duedate = $.datepicker.parseDate(datepicker_settings.dateFormat, me.selected_task.date, datepicker_settings);
6337     + if (startdate > duedate) {
6338     + alert(rcmail.gettext('invalidstartduedates', 'tasklist'));
6339     + return false;
6340     + }
6341     + }
6342     +
6343     + $('input[type="hidden"]', rcmail.gui_objects.edittagline).each(function(i,elem){
6344     + if (elem.value)
6345     + me.selected_task.tags.push(elem.value);
6346     + });
6347     +
6348     + // serialize alarm settings
6349     + var alarm = $('#taskedit select.edit-alarm-type').val();
6350     + if (alarm) {
6351     + var val, offset = $('#taskedit select.edit-alarm-offset').val();
6352     + if (offset == '@')
6353     + me.selected_task.alarms = '@' + date2unixtime(parse_datetime($('#taskedit input.edit-alarm-time').val(), $('#taskedit input.edit-alarm-date').val())) + ':' + alarm;
6354     + else if ((val = parseInt($('#taskedit input.edit-alarm-value').val())) && !isNaN(val) && val >= 0)
6355     + me.selected_task.alarms = offset[0] + val + offset[1] + ':' + alarm;
6356     + }
6357     +
6358     + // uploaded attachments list
6359     + for (var i in rcmail.env.attachments) {
6360     + if (i.match(/^rcmfile(.+)/))
6361     + me.selected_task.attachments.push(RegExp.$1);
6362     + }
6363     +
6364     + // task assigned to a new list
6365     + if (me.selected_task.list && me.selected_task.list != rec.list) {
6366     + me.selected_task._fromlist = rec.list;
6367     + }
6368     +
6369     + me.selected_task.complete = complete.val() / 100;
6370     + if (isNaN(me.selected_task.complete))
6371     + me.selected_task.complete = null;
6372     +
6373     + if (!me.selected_task.list && list.id)
6374     + me.selected_task.list = list.id;
6375     +
6376     + if (save_task(me.selected_task, action))
6377     + $dialog.dialog('close');
6378     + };
6379     +
6380     + if (action != 'new') {
6381     + buttons[rcmail.gettext('delete', 'tasklist')] = function() {
6382     + if (delete_task(rec.id))
6383     + $dialog.dialog('close');
6384     + };
6385     + }
6386     +
6387     + buttons[rcmail.gettext('cancel', 'tasklist')] = function() {
6388     + $dialog.dialog('close');
6389     + };
6390     +
6391     + // open jquery UI dialog
6392     + $dialog.dialog({
6393     + modal: true,
6394     + resizable: (!bw.ie6 && !bw.ie7), // disable for performance reasons
6395     + closeOnEscape: false,
6396     + title: rcmail.gettext((action == 'edit' ? 'edittask' : 'newtask'), 'tasklist'),
6397     + close: function() {
6398     + editform.hide().appendTo(document.body);
6399     + $dialog.dialog('destroy').remove();
6400     + },
6401     + buttons: buttons,
6402     + minHeight: 460,
6403     + minWidth: 500,
6404     + width: 580
6405     + }).append(editform.show()); // adding form content AFTERWARDS massively speeds up opening on IE
6406     +
6407     + title.select();
6408     +
6409     + // set dialog size according to content
6410     + me.dialog_resize($dialog.get(0), $dialog.height(), 580);
6411     + }
6412     +
6413     +
6414     + /**
6415     + * Open a task attachment either in a browser window for inline view or download it
6416     + */
6417     + function load_attachment(rec, att)
6418     + {
6419     + // can't open temp attachments
6420     + if (!rec.id || rec.id < 0)
6421     + return false;
6422     +
6423     + var qstring = '_id='+urlencode(att.id)+'&_t='+urlencode(rec.recurrence_id||rec.id)+'&_list='+urlencode(rec.list);
6424     +
6425     + // open attachment in frame if it's of a supported mimetype
6426     + // similar as in app.js and calendar_ui.js
6427     + if (att.id && att.mimetype && $.inArray(att.mimetype, settings.mimetypes)>=0) {
6428     + rcmail.attachment_win = window.open(rcmail.env.comm_path+'&_action=get-attachment&'+qstring+'&_frame=1', 'rcubetaskattachment');
6429     + if (rcmail.attachment_win) {
6430     + window.setTimeout(function() { rcmail.attachment_win.focus(); }, 10);
6431     + return;
6432     + }
6433     + }
6434     +
6435     + rcmail.goto_url('get-attachment', qstring+'&_download=1', false);
6436     + };
6437     +
6438     + /**
6439     + * Build task attachments list
6440     + */
6441     + function task_show_attachments(list, container, rec, edit)
6442     + {
6443     + var i, id, len, content, li, elem,
6444     + ul = $('<ul>').addClass('attachmentslist');
6445     +
6446     + for (i=0, len=list.length; i<len; i++) {
6447     + elem = list[i];
6448     + li = $('<li>').addClass(elem.classname);
6449     +
6450     + if (edit) {
6451     + rcmail.env.attachments[elem.id] = elem;
6452     + // delete icon
6453     + content = $('<a>')
6454     + .attr('href', '#delete')
6455     + .attr('title', rcmail.gettext('delete'))
6456     + .addClass('delete')
6457     + .click({ id:elem.id }, function(e) {
6458     + remove_attachment(this, e.data.id);
6459     + return false;
6460     + });
6461     +
6462     + if (!rcmail.env.deleteicon) {
6463     + content.html(rcmail.gettext('delete'));
6464     + }
6465     + else {
6466     + $('<img>').attr('src', rcmail.env.deleteicon).attr('alt', rcmail.gettext('delete')).appendTo(content);
6467     + }
6468     +
6469     + li.append(content);
6470     + }
6471     +
6472     + // name/link
6473     + $('<a>')
6474     + .attr('href', '#load')
6475     + .addClass('file')
6476     + .html(elem.name).click({ task:rec, att:elem }, function(e) {
6477     + load_attachment(e.data.task, e.data.att);
6478     + return false;
6479     + }).appendTo(li);
6480     +
6481     + ul.append(li);
6482     + }
6483     +
6484     + if (edit && rcmail.gui_objects.attachmentlist) {
6485     + ul.id = rcmail.gui_objects.attachmentlist.id;
6486     + rcmail.gui_objects.attachmentlist = ul.get(0);
6487     + }
6488     +
6489     + container.empty().append(ul);
6490     + };
6491     +
6492     + /**
6493     + *
6494     + */
6495     + var remove_attachment = function(elem, id)
6496     + {
6497     + $(elem.parentNode).hide();
6498     + me.selected_task.deleted_attachments.push(id);
6499     + delete rcmail.env.attachments[id];
6500     + };
6501     +
6502     + /**
6503     + *
6504     + */
6505     + function add_childtask(id)
6506     + {
6507     + if (rcmail.busy)
6508     + return false;
6509     +
6510     + var rec = listdata[id];
6511     + task_edit_dialog(null, 'new', { parent_id:id, list:rec.list });
6512     + }
6513     +
6514     + /**
6515     + * Delete the given task
6516     + */
6517     + function delete_task(id)
6518     + {
6519     + var rec = listdata[id];
6520     + if (!rec || rec.readonly || rcmail.busy)
6521     + return false;
6522     +
6523     + var html, buttons = [{
6524     + text: rcmail.gettext('cancel', 'tasklist'),
6525     + click: function() {
6526     + $(this).dialog('close');
6527     + }
6528     + }];
6529     +
6530     + if (rec.children && rec.children.length) {
6531     + html = rcmail.gettext('deleteparenttasktconfirm','tasklist');
6532     + buttons.push({
6533     + text: rcmail.gettext('deletethisonly','tasklist'),
6534     + click: function() {
6535     + _delete_task(id, 0);
6536     + $(this).dialog('close');
6537     + }
6538     + });
6539     + buttons.push({
6540     + text: rcmail.gettext('deletewithchilds','tasklist'),
6541     + click: function() {
6542     + _delete_task(id, 1);
6543     + $(this).dialog('close');
6544     + }
6545     + });
6546     + }
6547     + else {
6548     + html = rcmail.gettext('deletetasktconfirm','tasklist');
6549     + buttons.push({
6550     + text: rcmail.gettext('delete','tasklist'),
6551     + click: function() {
6552     + _delete_task(id, 0);
6553     + $(this).dialog('close');
6554     + }
6555     + });
6556     + }
6557     +
6558     + var $dialog = $('<div>').html(html);
6559     + $dialog.dialog({
6560     + modal: true,
6561     + width: 520,
6562     + dialogClass: 'warning',
6563     + title: rcmail.gettext('deletetask', 'tasklist'),
6564     + buttons: buttons,
6565     + close: function(){
6566     + $dialog.dialog('destroy').hide();
6567     + }
6568     + }).addClass('tasklist-confirm').show();
6569     +
6570     + return true;
6571     + }
6572     +
6573     + /**
6574     + * Subfunction to submit the delete command after confirm
6575     + */
6576     + function _delete_task(id, mode)
6577     + {
6578     + var rec = listdata[id],
6579     + li = $('li[rel="'+id+'"]', rcmail.gui_objects.resultlist).hide();
6580     +
6581     + saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
6582     + rcmail.http_post('task', { action:'delete', t:{ id:rec.id, list:rec.list }, mode:mode, filter:filtermask });
6583     +
6584     + // move childs to parent/root
6585     + if (mode != 1 && rec.children !== undefined) {
6586     + var parent_node = rec.parent_id ? $('li[rel="'+rec.parent_id+'"] > .childtasks', rcmail.gui_objects.resultlist) : null;
6587     + if (!parent_node || !parent_node.length)
6588     + parent_node = rcmail.gui_objects.resultlist;
6589     +
6590     + $.each(rec.children, function(i,cid) {
6591     + var child = listdata[cid];
6592     + child.parent_id = rec.parent_id;
6593     + resort_task(child, $('li[rel="'+cid+'"]').appendTo(parent_node), true);
6594     + });
6595     + }
6596     +
6597     + li.remove();
6598     + }
6599     +
6600     + /**
6601     + * Check if the given task matches the current filtermask and tag selection
6602     + */
6603     + function match_filter(rec)
6604     + {
6605     + var match = !filtermask || (filtermask & rec.mask) > 0;
6606     +
6607     + if (match && tagsfilter.length) {
6608     + match = rec.tags && rec.tags.length;
6609     + for (var i=0; match && i < tagsfilter.length; i++) {
6610     + if ($.inArray(tagsfilter[i], rec.tags) < 0)
6611     + match = false;
6612     + }
6613     + }
6614     +
6615     + return match;
6616     + }
6617     +
6618     + /**
6619     + *
6620     + */
6621     + function list_edit_dialog(id)
6622     + {
6623     + var list = me.tasklists[id],
6624     + $dialog = $('#tasklistform');
6625     + editform = $('#tasklisteditform');
6626     +
6627     + if ($dialog.is(':ui-dialog'))
6628     + $dialog.dialog('close');
6629     +
6630     + if (!list)
6631     + list = { name:'', editable:true, showalarms:true };
6632     +
6633     + // fill edit form
6634     + var name = $('#taskedit-tasklistame').prop('disabled', !list.editable).val(list.editname || list.name),
6635     + alarms = $('#taskedit-showalarms').prop('checked', list.showalarms).get(0),
6636     + parent = $('#taskedit-parentfolder').val(list.parentfolder);
6637     +
6638     + // dialog buttons
6639     + var buttons = {};
6640     +
6641     + buttons[rcmail.gettext('save','tasklist')] = function() {
6642     + // do some input validation
6643     + if (!name.val() || name.val().length < 2) {
6644     + alert(rcmail.gettext('invalidlistproperties', 'tasklist'));
6645     + name.select();
6646     + return;
6647     + }
6648     +
6649     + // post data to server
6650     + var data = editform.serializeJSON();
6651     + if (list.id)
6652     + data.id = list.id;
6653     + if (alarms)
6654     + data.showalarms = alarms.checked ? 1 : 0;
6655     + if (parent.length)
6656     + data.parentfolder = $('option:selected', parent).val();
6657     +
6658     + saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
6659     + rcmail.http_post('tasklist', { action:(list.id ? 'edit' : 'new'), l:data });
6660     + $dialog.dialog('close');
6661     + };
6662     +
6663     + buttons[rcmail.gettext('cancel','tasklist')] = function() {
6664     + $dialog.dialog('close');
6665     + };
6666     +
6667     + // open jquery UI dialog
6668     + $dialog.dialog({
6669     + modal: true,
6670     + resizable: true,
6671     + closeOnEscape: false,
6672     + title: rcmail.gettext((list.id ? 'editlist' : 'createlist'), 'tasklist'),
6673     + close: function() { $dialog.dialog('destroy').hide(); },
6674     + buttons: buttons,
6675     + minWidth: 400,
6676     + width: 420
6677     + }).show();
6678     + }
6679     +
6680     + /**
6681     + *
6682     + */
6683     + function list_remove(id)
6684     + {
6685     + var list = me.tasklists[id];
6686     + if (list && list.editable && confirm(rcmail.gettext('deletelistconfirm', 'tasklist'))) {
6687     + saving_lock = rcmail.set_busy(true, 'tasklist.savingdata');
6688     + rcmail.http_post('tasklist', { action:'remove', l:{ id:list.id } });
6689     + return true;
6690     + }
6691     + return false;
6692     + }
6693     +
6694     + /**
6695     + * Callback from server to finally remove the given list
6696     + */
6697     + function destroy_list(prop)
6698     + {
6699     + var list = me.tasklists[prop.id],
6700     + li = rcmail.get_folder_li(prop.id, 'rcmlitasklist');
6701     +
6702     + if (li) {
6703     + $(li).remove();
6704     + }
6705     + if (list) {
6706     + list.active = false;
6707     + // delete me.tasklists[prop.id];
6708     + unlock_saving();
6709     + remove_tasks(list.id);
6710     + }
6711     + }
6712     +
6713     + /**
6714     + *
6715     + */
6716     + function insert_list(prop)
6717     + {
6718     + var li = $('<li>').attr('id', 'rcmlitasklist'+prop.id)
6719     + .append('<input type="checkbox" name="_list[]" value="'+prop.id+'" checked="checked" />')
6720     + .append('<span class="handle">&nbsp;</span>')
6721     + .append('<span class="listname">'+Q(prop.name)+'</span>');
6722     + $(rcmail.gui_objects.folderlist).append(li);
6723     + me.tasklists[prop.id] = prop;
6724     + init_tasklist_li(li.get(0), prop.id);
6725     + }
6726     +
6727     + /**
6728     + *
6729     + */
6730     + function update_list(prop)
6731     + {
6732     + var id = prop.oldid || prop.id,
6733     + li = rcmail.get_folder_li(id, 'rcmlitasklist');
6734     +
6735     + if (me.tasklists[id] && li) {
6736     + delete me.tasklists[id];
6737     + me.tasklists[prop.id] = prop;
6738     + $(li).data('id', prop.id);
6739     + $('#'+li.id+' input').data('id', prop.id);
6740     + $('.listname', li).html(Q(prop.name));
6741     + }
6742     + }
6743     +
6744     + /**
6745     + * Execute search
6746     + */
6747     + function quicksearch()
6748     + {
6749     + var q;
6750     + if (rcmail.gui_objects.qsearchbox && (q = rcmail.gui_objects.qsearchbox.value)) {
6751     + var id = 'search-'+q;
6752     + var resources = [];
6753     +
6754     + for (var rid in me.tasklists) {
6755     + if (me.tasklists[rid].active) {
6756     + resources.push(rid);
6757     + }
6758     + }
6759     + id += '@'+resources.join(',');
6760     +
6761     + // ignore if query didn't change
6762     + if (search_request == id)
6763     + return;
6764     +
6765     + search_request = id;
6766     + search_query = q;
6767     +
6768     + list_tasks('all');
6769     + }
6770     + else // empty search input equals reset
6771     + this.reset_search();
6772     + }
6773     +
6774     + /**
6775     + * Reset search and get back to normal listing
6776     + */
6777     + function reset_search()
6778     + {
6779     + $(rcmail.gui_objects.qsearchbox).val('');
6780     +
6781     + if (search_request) {
6782     + search_request = search_query = null;
6783     + list_tasks();
6784     + }
6785     + }
6786     +
6787     +
6788     + /**** Utility functions ****/
6789     +
6790     + /**
6791     + * Clear any text selection
6792     + * (text is probably selected when double-clicking somewhere)
6793     + */
6794     + function clearSelection()
6795     + {
6796     + if (document.selection && document.selection.empty) {
6797     + document.selection.empty() ;
6798     + }
6799     + else if (window.getSelection) {
6800     + var sel = window.getSelection();
6801     + if (sel && sel.removeAllRanges)
6802     + sel.removeAllRanges();
6803     + }
6804     + }
6805     +
6806     + /**
6807     + * Hide all open popup menus
6808     + */
6809     + function clear_popups(e)
6810     + {
6811     + var count = 0, target = e.target;
6812     + if (target && target.className == 'inner')
6813     + target = e.target.parentNode;
6814     +
6815     + $('.popupmenu:visible').each(function(i, elem){
6816     + var menu = $(elem), id = elem.id;
6817     + if (target.id != id+'link' && (!menu.data('sticky') || !target_overlaps(e.target, elem))) {
6818     + menu.hide();
6819     + count++;
6820     + }
6821     + });
6822     + return count;
6823     + }
6824     +
6825     + /**
6826     + * Check whether the event target is a descentand of the given element
6827     + */
6828     + function target_overlaps(target, elem)
6829     + {
6830     + while (target.parentNode) {
6831     + if (target.parentNode == elem)
6832     + return true;
6833     + target = target.parentNode;
6834     + }
6835     + return false;
6836     + }
6837     +
6838     + /**
6839     + *
6840     + */
6841     + function active_lists()
6842     + {
6843     + var active = [];
6844     + for (var id in me.tasklists) {
6845     + if (me.tasklists[id].active)
6846     + active.push(id);
6847     + }
6848     + return active;
6849     + }
6850     +
6851     + // resize and reposition (center) the dialog window
6852     + this.dialog_resize = function(id, height, width)
6853     + {
6854     + var win = $(window), w = win.width(), h = win.height();
6855     + $(id).dialog('option', { height: Math.min(h-20, height+130), width: Math.min(w-20, width+50) })
6856     + .dialog('option', 'position', ['center', 'center']); // only works in a separate call (!?)
6857     + };
6858     +
6859     + /**
6860     + * Register event handlers on a tasklist (folder) item
6861     + */
6862     + function init_tasklist_li(li, id)
6863     + {
6864     + $('#'+li.id+' input').click(function(e){
6865     + var id = $(this).data('id');
6866     + if (me.tasklists[id]) { // add or remove event source on click
6867     + me.tasklists[id].active = this.checked;
6868     + fetch_counts();
6869     + if (!this.checked) remove_tasks(id);
6870     + else list_tasks(null);
6871     + rcmail.http_post('tasklist', { action:'subscribe', l:{ id:id, active:me.tasklists[id].active?1:0 } });
6872     + }
6873     + }).data('id', id).get(0).checked = me.tasklists[id].active || false;
6874     +
6875     + $(li).click(function(e){
6876     + var id = $(this).data('id');
6877     + rcmail.select_folder(id, 'rcmlitasklist');
6878     + rcmail.enable_command('list-edit', 'list-remove', 'list-import', me.tasklists[id].editable);
6879     + me.selected_list = id;
6880     + })
6881     + .dblclick(function(e){
6882     + list_edit_dialog($(this).data('id'));
6883     + })
6884     + .data('id', id)
6885     + .data('type', 'tasklist')
6886     + .addClass(me.tasklists[id].editable ? null : 'readonly');
6887     + }
6888     +
6889     +
6890     + // init dialog by default
6891     + init_taskedit();
6892     +}
6893     +
6894     +
6895     +// extend jQuery
6896     +// from http://james.padolsey.com/javascript/sorting-elements-with-jquery/
6897     +jQuery.fn.sortElements = (function(){
6898     + var sort = [].sort;
6899     +
6900     + return function(comparator, getSortable) {
6901     + getSortable = getSortable || function(){ return this };
6902     +
6903     + var last = null;
6904     + return sort.call(this, comparator).each(function(i){
6905     + // at this point the array is sorted, so we can just detach each one from wherever it is, and add it after the last
6906     + var node = $(getSortable.call(this));
6907     + var parent = node.parent();
6908     + if (last) last.after(node);
6909     + else parent.prepend(node);
6910     + last = node;
6911     + });
6912     + };
6913     +})();
6914     +
6915     +
6916     +/* tasklist plugin UI initialization */
6917     +var rctasks;
6918     +window.rcmail && rcmail.addEventListener('init', function(evt) {
6919     +
6920     + rctasks = new rcube_tasklist_ui(rcmail.env.libcal_settings);
6921     +
6922     + // register button commands
6923     + rcmail.register_command('newtask', function(){ rctasks.edit_task(null, 'new', {}); }, true);
6924     + //rcmail.register_command('print', function(){ rctasks.print_list(); }, true);
6925     +
6926     + rcmail.register_command('list-create', function(){ rctasks.list_edit_dialog(null); }, true);
6927     + rcmail.register_command('list-edit', function(){ rctasks.list_edit_dialog(rctasks.selected_list); }, false);
6928     + rcmail.register_command('list-remove', function(){ rctasks.list_remove(rctasks.selected_list); }, false);
6929     +
6930     + rcmail.register_command('search', function(){ rctasks.quicksearch(); }, true);
6931     + rcmail.register_command('reset-search', function(){ rctasks.reset_search(); }, true);
6932     +
6933     + rctasks.init();
6934     +});
6935     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist.php roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist.php
6936     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist.php 1970-01-01 01:00:00.000000000 +0100
6937     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist.php 2013-11-24 00:38:51.000000000 +0100
6938     @@ -0,0 +1,881 @@
6939     +<?php
6940     +
6941     +/**
6942     + * Tasks plugin for Roundcube webmail
6943     + *
6944     + * @version @package_version@
6945     + * @author Thomas Bruederli <bruederli@kolabsys.com>
6946     + *
6947     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
6948     + *
6949     + * This program is free software: you can redistribute it and/or modify
6950     + * it under the terms of the GNU Affero General Public License as
6951     + * published by the Free Software Foundation, either version 3 of the
6952     + * License, or (at your option) any later version.
6953     + *
6954     + * This program is distributed in the hope that it will be useful,
6955     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
6956     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
6957     + * GNU Affero General Public License for more details.
6958     + *
6959     + * You should have received a copy of the GNU Affero General Public License
6960     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
6961     + */
6962     +
6963     +class tasklist extends rcube_plugin
6964     +{
6965     + const FILTER_MASK_TODAY = 1;
6966     + const FILTER_MASK_TOMORROW = 2;
6967     + const FILTER_MASK_WEEK = 4;
6968     + const FILTER_MASK_LATER = 8;
6969     + const FILTER_MASK_NODATE = 16;
6970     + const FILTER_MASK_OVERDUE = 32;
6971     + const FILTER_MASK_FLAGGED = 64;
6972     + const FILTER_MASK_COMPLETE = 128;
6973     +
6974     + const SESSION_KEY = 'tasklist_temp';
6975     +
6976     + public static $filter_masks = array(
6977     + 'today' => self::FILTER_MASK_TODAY,
6978     + 'tomorrow' => self::FILTER_MASK_TOMORROW,
6979     + 'week' => self::FILTER_MASK_WEEK,
6980     + 'later' => self::FILTER_MASK_LATER,
6981     + 'nodate' => self::FILTER_MASK_NODATE,
6982     + 'overdue' => self::FILTER_MASK_OVERDUE,
6983     + 'flagged' => self::FILTER_MASK_FLAGGED,
6984     + 'complete' => self::FILTER_MASK_COMPLETE,
6985     + );
6986     +
6987     + public $task = '?(?!login|logout).*';
6988     + public $rc;
6989     + public $lib;
6990     + public $driver;
6991     + public $timezone;
6992     + public $ui;
6993     +
6994     + private $collapsed_tasks = array();
6995     +
6996     +
6997     + /**
6998     + * Plugin initialization.
6999     + */
7000     + function init()
7001     + {
7002     + $this->require_plugin('libcalendaring');
7003     +
7004     + $this->rc = rcube::get_instance();
7005     + $this->lib = libcalendaring::get_instance();
7006     +
7007     + $this->register_task('tasks', 'tasklist');
7008     +
7009     + // load plugin configuration
7010     + $this->load_config();
7011     +
7012     + // load localizations
7013     + $this->add_texts('localization/', $this->rc->task == 'tasks' && (!$this->rc->action || $this->rc->action == 'print'));
7014     + $this->rc->load_language($_SESSION['language'], array('tasks.tasks' => $this->gettext('navtitle'))); // add label for task title
7015     +
7016     + $this->timezone = $this->lib->timezone;
7017     +
7018     + if ($this->rc->task == 'tasks' && $this->rc->action != 'save-pref') {
7019     + $this->load_driver();
7020     +
7021     + // register calendar actions
7022     + $this->register_action('index', array($this, 'tasklist_view'));
7023     + $this->register_action('task', array($this, 'task_action'));
7024     + $this->register_action('tasklist', array($this, 'tasklist_action'));
7025     + $this->register_action('counts', array($this, 'fetch_counts'));
7026     + $this->register_action('fetch', array($this, 'fetch_tasks'));
7027     + $this->register_action('inlineui', array($this, 'get_inline_ui'));
7028     + $this->register_action('mail2task', array($this, 'mail_message2task'));
7029     + $this->register_action('get-attachment', array($this, 'attachment_get'));
7030     + $this->register_action('upload', array($this, 'attachment_upload'));
7031     +
7032     + $this->collapsed_tasks = array_filter(explode(',', $this->rc->config->get('tasklist_collapsed_tasks', '')));
7033     + }
7034     + else if ($this->rc->task == 'mail') {
7035     + // TODO: register hooks to catch ical/vtodo email attachments
7036     + if ($this->rc->action == 'show' || $this->rc->action == 'preview') {
7037     + // $this->add_hook('message_load', array($this, 'mail_message_load'));
7038     + // $this->add_hook('template_object_messagebody', array($this, 'mail_messagebody_html'));
7039     + }
7040     +
7041     + // add 'Create event' item to message menu
7042     + if ($this->api->output->type == 'html') {
7043     + $this->api->add_content(html::tag('li', null,
7044     + $this->api->output->button(array(
7045     + 'command' => 'tasklist-create-from-mail',
7046     + 'label' => 'tasklist.createfrommail',
7047     + 'type' => 'link',
7048     + 'classact' => 'icon taskaddlink active',
7049     + 'class' => 'icon taskaddlink',
7050     + 'innerclass' => 'icon taskadd',
7051     + ))),
7052     + 'messagemenu');
7053     + }
7054     + }
7055     +
7056     + if (!$this->rc->output->ajax_call && !$this->rc->output->env['framed']) {
7057     + require_once($this->home . '/tasklist_ui.php');
7058     + $this->ui = new tasklist_ui($this);
7059     + $this->ui->init();
7060     + }
7061     +
7062     + // add hooks for alarms handling
7063     + $this->add_hook('pending_alarms', array($this, 'pending_alarms'));
7064     + $this->add_hook('dismiss_alarms', array($this, 'dismiss_alarms'));
7065     + }
7066     +
7067     +
7068     + /**
7069     + * Helper method to load the backend driver according to local config
7070     + */
7071     + private function load_driver()
7072     + {
7073     + if (is_object($this->driver))
7074     + return;
7075     +
7076     + $driver_name = $this->rc->config->get('tasklist_driver', 'database');
7077     + $driver_class = 'tasklist_' . $driver_name . '_driver';
7078     +
7079     + require_once($this->home . '/drivers/tasklist_driver.php');
7080     + require_once($this->home . '/drivers/' . $driver_name . '/' . $driver_class . '.php');
7081     +
7082     + switch ($driver_name) {
7083     + case "kolab":
7084     + $this->require_plugin('libkolab');
7085     + default:
7086     + $this->driver = new $driver_class($this);
7087     + break;
7088     + }
7089     +
7090     + $this->rc->output->set_env('tasklist_driver', $driver_name);
7091     + }
7092     +
7093     +
7094     + /**
7095     + * Dispatcher for task-related actions initiated by the client
7096     + */
7097     + public function task_action()
7098     + {
7099     + $filter = intval(get_input_value('filter', RCUBE_INPUT_GPC));
7100     + $action = get_input_value('action', RCUBE_INPUT_GPC);
7101     + $rec = get_input_value('t', RCUBE_INPUT_POST, true);
7102     + $oldrec = $rec;
7103     + $success = $refresh = false;
7104     +
7105     + switch ($action) {
7106     + case 'new':
7107     + $oldrec = null;
7108     + $rec = $this->prepare_task($rec);
7109     + $rec['uid'] = $this->generate_uid();
7110     + $temp_id = $rec['tempid'];
7111     + if ($success = $this->driver->create_task($rec)) {
7112     + $refresh = $this->driver->get_task($rec);
7113     + if ($temp_id) $refresh['tempid'] = $temp_id;
7114     + $this->cleanup_task($rec);
7115     + }
7116     + break;
7117     +
7118     + case 'edit':
7119     + $rec = $this->prepare_task($rec);
7120     + if ($success = $this->driver->edit_task($rec)) {
7121     + $refresh = $this->driver->get_task($rec);
7122     + $this->cleanup_task($rec);
7123     + }
7124     + break;
7125     +
7126     + case 'move':
7127     + foreach ((array)$rec['id'] as $id) {
7128     + $r = $rec;
7129     + $r['id'] = $id;
7130     + if ($this->driver->move_task($r)) {
7131     + $refresh[] = $this->driver->get_task($r);
7132     + $success = true;
7133     +
7134     + // move all childs, too
7135     + foreach ($this->driver->get_childs(array('id' => $rec['id'], 'list' => $rec['_fromlist'])) as $cid) {
7136     + $child = $rec;
7137     + $child['id'] = $cid;
7138     + if ($this->driver->move_task($child)) {
7139     + $r = $this->driver->get_task($child);
7140     + if ((bool)($filter & self::FILTER_MASK_COMPLETE) == ($r['complete'] == 1.0)) {
7141     + $refresh[] = $r;
7142     + }
7143     + }
7144     + }
7145     + }
7146     + }
7147     + break;
7148     +
7149     + case 'delete':
7150     + $mode = intval(get_input_value('mode', RCUBE_INPUT_POST));
7151     + $oldrec = $this->driver->get_task($rec);
7152     + if ($success = $this->driver->delete_task($rec, false)) {
7153     + // delete/modify all childs
7154     + foreach ($this->driver->get_childs($rec, $mode) as $cid) {
7155     + $child = array('id' => $cid, 'list' => $rec['list']);
7156     +
7157     + if ($mode == 1) { // delete all childs
7158     + if ($this->driver->delete_task($child, false)) {
7159     + if ($this->driver->undelete)
7160     + $_SESSION['tasklist_undelete'][$rec['id']][] = $cid;
7161     + }
7162     + else
7163     + $success = false;
7164     + }
7165     + else {
7166     + $child['parent_id'] = strval($oldrec['parent_id']);
7167     + $this->driver->edit_task($child);
7168     + }
7169     + }
7170     + }
7171     +
7172     + if (!$success)
7173     + $this->rc->output->command('plugin.reload_data');
7174     + break;
7175     +
7176     + case 'undelete':
7177     + if ($success = $this->driver->undelete_task($rec)) {
7178     + $refresh[] = $this->driver->get_task($rec);
7179     + foreach ((array)$_SESSION['tasklist_undelete'][$rec['id']] as $cid) {
7180     + if ($this->driver->undelete_task($rec)) {
7181     + $refresh[] = $this->driver->get_task($rec);
7182     + }
7183     + }
7184     + }
7185     + break;
7186     +
7187     + case 'collapse':
7188     + if (intval(get_input_value('collapsed', RCUBE_INPUT_GPC))) {
7189     + $this->collapsed_tasks[] = $rec['id'];
7190     + }
7191     + else {
7192     + $i = array_search($rec['id'], $this->collapsed_tasks);
7193     + if ($i !== false)
7194     + unset($this->collapsed_tasks[$i]);
7195     + }
7196     +
7197     + $this->rc->user->save_prefs(array('tasklist_collapsed_tasks' => join(',', array_unique($this->collapsed_tasks))));
7198     + return; // avoid further actions
7199     + }
7200     +
7201     + if ($success) {
7202     + $this->rc->output->show_message('successfullysaved', 'confirmation');
7203     + $this->update_counts($oldrec, $refresh);
7204     + }
7205     + else
7206     + $this->rc->output->show_message('tasklist.errorsaving', 'error');
7207     +
7208     + // unlock client
7209     + $this->rc->output->command('plugin.unlock_saving');
7210     +
7211     + if ($refresh) {
7212     + if ($refresh['id']) {
7213     + $this->encode_task($refresh);
7214     + }
7215     + else if (is_array($refresh)) {
7216     + foreach ($refresh as $i => $r)
7217     + $this->encode_task($refresh[$i]);
7218     + }
7219     + $this->rc->output->command('plugin.refresh_task', $refresh);
7220     + }
7221     + }
7222     +
7223     + /**
7224     + * repares new/edited task properties before save
7225     + */
7226     + private function prepare_task($rec)
7227     + {
7228     + // try to be smart and extract date from raw input
7229     + if ($rec['raw']) {
7230     + foreach (array('today','tomorrow','sunday','monday','tuesday','wednesday','thursday','friday','saturday','sun','mon','tue','wed','thu','fri','sat') as $word) {
7231     + $locwords[] = '/^' . preg_quote(mb_strtolower($this->gettext($word))) . '\b/i';
7232     + $normwords[] = $word;
7233     + $datewords[] = $word;
7234     + }
7235     + foreach (array('jan','feb','mar','apr','may','jun','jul','aug','sep','oct','now','dec') as $month) {
7236     + $locwords[] = '/(' . preg_quote(mb_strtolower($this->gettext('long'.$month))) . '|' . preg_quote(mb_strtolower($this->gettext($month))) . ')\b/i';
7237     + $normwords[] = $month;
7238     + $datewords[] = $month;
7239     + }
7240     + foreach (array('on','this','next','at') as $word) {
7241     + $fillwords[] = preg_quote(mb_strtolower($this->gettext($word)));
7242     + $fillwords[] = $word;
7243     + }
7244     +
7245     + $raw = trim($rec['raw']);
7246     + $date_str = '';
7247     +
7248     + // translate localized keywords
7249     + $raw = preg_replace('/^(' . join('|', $fillwords) . ')\s*/i', '', $raw);
7250     + $raw = preg_replace($locwords, $normwords, $raw);
7251     +
7252     + // find date pattern
7253     + $date_pattern = '!^(\d+[./-]\s*)?((?:\d+[./-])|' . join('|', $datewords) . ')\.?(\s+\d{4})?[:;,]?\s+!i';
7254     + if (preg_match($date_pattern, $raw, $m)) {
7255     + $date_str .= $m[1] . $m[2] . $m[3];
7256     + $raw = preg_replace(array($date_pattern, '/^(' . join('|', $fillwords) . ')\s*/i'), '', $raw);
7257     + // add year to date string
7258     + if ($m[1] && !$m[3])
7259     + $date_str .= date('Y');
7260     + }
7261     +
7262     + // find time pattern
7263     + $time_pattern = '/^(\d+([:.]\d+)?(\s*[hapm.]+)?),?\s+/i';
7264     + if (preg_match($time_pattern, $raw, $m)) {
7265     + $has_time = true;
7266     + $date_str .= ($date_str ? ' ' : 'today ') . $m[1];
7267     + $raw = preg_replace($time_pattern, '', $raw);
7268     + }
7269     +
7270     + // yes, raw input matched a (valid) date
7271     + if (strlen($date_str) && strtotime($date_str) && ($date = new DateTime($date_str, $this->timezone))) {
7272     + $rec['date'] = $date->format('Y-m-d');
7273     + if ($has_time)
7274     + $rec['time'] = $date->format('H:i');
7275     + $rec['title'] = $raw;
7276     + }
7277     + else
7278     + $rec['title'] = $rec['raw'];
7279     + }
7280     +
7281     + // normalize input from client
7282     + if (isset($rec['complete'])) {
7283     + $rec['complete'] = floatval($rec['complete']);
7284     + if ($rec['complete'] > 1)
7285     + $rec['complete'] /= 100;
7286     + }
7287     + if (isset($rec['flagged']))
7288     + $rec['flagged'] = intval($rec['flagged']);
7289     +
7290     + // fix for garbage input
7291     + if ($rec['description'] == 'null')
7292     + $rec['description'] = '';
7293     +
7294     + foreach ($rec as $key => $val) {
7295     + if ($val === 'null')
7296     + $rec[$key] = null;
7297     + }
7298     +
7299     + if (!empty($rec['date'])) {
7300     + try {
7301     + $date = new DateTime($rec['date'] . ' ' . $rec['time'], $this->timezone);
7302     + $rec['date'] = $date->format('Y-m-d');
7303     + if (!empty($rec['time']))
7304     + $rec['time'] = $date->format('H:i');
7305     + }
7306     + catch (Exception $e) {
7307     + $rec['date'] = $rec['time'] = null;
7308     + }
7309     + }
7310     +
7311     + if (!empty($rec['startdate'])) {
7312     + try {
7313     + $date = new DateTime($rec['startdate'] . ' ' . $rec['starttime'], $this->timezone);
7314     + $rec['startdate'] = $date->format('Y-m-d');
7315     + if (!empty($rec['starttime']))
7316     + $rec['starttime'] = $date->format('H:i');
7317     + }
7318     + catch (Exception $e) {
7319     + $rec['startdate'] = $rec['starttime'] = null;
7320     + }
7321     + }
7322     +
7323     + // alarms cannot work without a date
7324     + if ($rec['alarms'] && !$rec['date'] && !$rec['startdate'] && strpos($rec['alarms'], '@') === false)
7325     + $rec['alarms'] = '';
7326     +
7327     + $attachments = array();
7328     + $taskid = $rec['id'];
7329     + if (is_array($_SESSION[self::SESSION_KEY]) && $_SESSION[self::SESSION_KEY]['id'] == $taskid) {
7330     + if (!empty($_SESSION[self::SESSION_KEY]['attachments'])) {
7331     + foreach ($_SESSION[self::SESSION_KEY]['attachments'] as $id => $attachment) {
7332     + if (is_array($rec['attachments']) && in_array($id, $rec['attachments'])) {
7333     + $attachments[$id] = $this->rc->plugins->exec_hook('attachment_get', $attachment);
7334     + unset($attachments[$id]['abort'], $attachments[$id]['group']);
7335     + }
7336     + }
7337     + }
7338     + }
7339     +
7340     + $rec['attachments'] = $attachments;
7341     +
7342     + if (is_numeric($rec['id']) && $rec['id'] < 0)
7343     + unset($rec['id']);
7344     +
7345     + return $rec;
7346     + }
7347     +
7348     +
7349     + /**
7350     + * Releases some resources after successful save
7351     + */
7352     + private function cleanup_task(&$rec)
7353     + {
7354     + // remove temp. attachment files
7355     + if (!empty($_SESSION[self::SESSION_KEY]) && ($taskid = $_SESSION[self::SESSION_KEY]['id'])) {
7356     + $this->rc->plugins->exec_hook('attachments_cleanup', array('group' => $taskid));
7357     + $this->rc->session->remove(self::SESSION_KEY);
7358     + }
7359     + }
7360     +
7361     +
7362     + /**
7363     + * Dispatcher for tasklist actions initiated by the client
7364     + */
7365     + public function tasklist_action()
7366     + {
7367     + $action = get_input_value('action', RCUBE_INPUT_GPC);
7368     + $list = get_input_value('l', RCUBE_INPUT_POST, true);
7369     + $success = false;
7370     +
7371     + if (isset($list['showalarms']))
7372     + $list['showalarms'] = intval($list['showalarms']);
7373     +
7374     + switch ($action) {
7375     + case 'new':
7376     + $list += array('showalarms' => true, 'active' => true, 'editable' => true);
7377     + if ($insert_id = $this->driver->create_list($list)) {
7378     + $list['id'] = $insert_id;
7379     + $this->rc->output->command('plugin.insert_tasklist', $list);
7380     + $success = true;
7381     + }
7382     + break;
7383     +
7384     + case 'edit':
7385     + if ($newid = $this->driver->edit_list($list)) {
7386     + $list['oldid'] = $list['id'];
7387     + $list['id'] = $newid;
7388     + $this->rc->output->command('plugin.update_tasklist', $list);
7389     + $success = true;
7390     + }
7391     + break;
7392     +
7393     + case 'subscribe':
7394     + $success = $this->driver->subscribe_list($list);
7395     + break;
7396     +
7397     + case 'remove':
7398     + if (($success = $this->driver->remove_list($list)))
7399     + $this->rc->output->command('plugin.destroy_tasklist', $list);
7400     + break;
7401     + }
7402     +
7403     + if ($success)
7404     + $this->rc->output->show_message('successfullysaved', 'confirmation');
7405     + else
7406     + $this->rc->output->show_message('tasklist.errorsaving', 'error');
7407     +
7408     + $this->rc->output->command('plugin.unlock_saving');
7409     + }
7410     +
7411     + /**
7412     + * Get counts for active tasks divided into different selectors
7413     + */
7414     + public function fetch_counts()
7415     + {
7416     + if (isset($_REQUEST['lists'])) {
7417     + $lists = get_input_value('lists', RCUBE_INPUT_GPC);
7418     + }
7419     + else {
7420     + foreach ($this->driver->get_lists() as $list) {
7421     + if ($list['active'])
7422     + $lists[] = $list['id'];
7423     + }
7424     + }
7425     + $counts = $this->driver->count_tasks($lists);
7426     + $this->rc->output->command('plugin.update_counts', $counts);
7427     + }
7428     +
7429     + /**
7430     + * Adjust the cached counts after changing a task
7431     + */
7432     + public function update_counts($oldrec, $newrec)
7433     + {
7434     + // rebuild counts until this function is finally implemented
7435     + $this->fetch_counts();
7436     +
7437     + // $this->rc->output->command('plugin.update_counts', $counts);
7438     + }
7439     +
7440     + /**
7441     + *
7442     + */
7443     + public function fetch_tasks()
7444     + {
7445     + $f = intval(get_input_value('filter', RCUBE_INPUT_GPC));
7446     + $search = get_input_value('q', RCUBE_INPUT_GPC);
7447     + $filter = array('mask' => $f, 'search' => $search);
7448     + $lists = get_input_value('lists', RCUBE_INPUT_GPC);;
7449     +/*
7450     + // convert magic date filters into a real date range
7451     + switch ($f) {
7452     + case self::FILTER_MASK_TODAY:
7453     + $today = new DateTime('now', $this->timezone);
7454     + $filter['from'] = $filter['to'] = $today->format('Y-m-d');
7455     + break;
7456     +
7457     + case self::FILTER_MASK_TOMORROW:
7458     + $tomorrow = new DateTime('now + 1 day', $this->timezone);
7459     + $filter['from'] = $filter['to'] = $tomorrow->format('Y-m-d');
7460     + break;
7461     +
7462     + case self::FILTER_MASK_OVERDUE:
7463     + $yesterday = new DateTime('yesterday', $this->timezone);
7464     + $filter['to'] = $yesterday->format('Y-m-d');
7465     + break;
7466     +
7467     + case self::FILTER_MASK_WEEK:
7468     + $today = new DateTime('now', $this->timezone);
7469     + $filter['from'] = $today->format('Y-m-d');
7470     + $weekend = new DateTime('now + 7 days', $this->timezone);
7471     + $filter['to'] = $weekend->format('Y-m-d');
7472     + break;
7473     +
7474     + case self::FILTER_MASK_LATER:
7475     + $date = new DateTime('now + 8 days', $this->timezone);
7476     + $filter['from'] = $date->format('Y-m-d');
7477     + break;
7478     +
7479     + }
7480     +*/
7481     + $data = $tags = $this->task_tree = $this->task_titles = array();
7482     + foreach ($this->driver->list_tasks($filter, $lists) as $rec) {
7483     + if ($rec['parent_id']) {
7484     + $this->task_tree[$rec['id']] = $rec['parent_id'];
7485     + }
7486     + $this->encode_task($rec);
7487     + if (!empty($rec['tags']))
7488     + $tags = array_merge($tags, (array)$rec['tags']);
7489     +
7490     + // apply filter; don't trust the driver on this :-)
7491     + if ((!$f && $rec['complete'] < 1.0) || ($rec['mask'] & $f))
7492     + $data[] = $rec;
7493     + }
7494     +
7495     + // sort tasks according to their hierarchy level and due date
7496     + array_walk($data, array($this, 'task_walk_tree'));
7497     + usort($data, array($this, 'task_sort_cmp'));
7498     +
7499     + $this->rc->output->command('plugin.data_ready', array('filter' => $f, 'lists' => $lists, 'search' => $search, 'data' => $data, 'tags' => array_values(array_unique($tags))));
7500     + }
7501     +
7502     + /**
7503     + * Prepare the given task record before sending it to the client
7504     + */
7505     + private function encode_task(&$rec)
7506     + {
7507     + $rec['mask'] = $this->filter_mask($rec);
7508     + $rec['flagged'] = intval($rec['flagged']);
7509     + $rec['complete'] = floatval($rec['complete']);
7510     + $rec['changed'] = is_object($rec['changed']) ? $rec['changed']->format('U') : null;
7511     +
7512     + if ($rec['date']) {
7513     + try {
7514     + $date = new DateTime($rec['date'] . ' ' . $rec['time'], $this->timezone);
7515     + $rec['datetime'] = intval($date->format('U'));
7516     + $rec['date'] = $date->format($this->rc->config->get('date_format', 'Y-m-d'));
7517     + $rec['_hasdate'] = 1;
7518     + }
7519     + catch (Exception $e) {
7520     + $rec['date'] = $rec['datetime'] = null;
7521     + }
7522     + }
7523     + else {
7524     + $rec['date'] = $rec['datetime'] = null;
7525     + $rec['_hasdate'] = 0;
7526     + }
7527     +
7528     + if ($rec['startdate']) {
7529     + try {
7530     + $date = new DateTime($rec['startdate'] . ' ' . $rec['starttime'], $this->timezone);
7531     + $rec['startdatetime'] = intval($date->format('U'));
7532     + $rec['startdate'] = $date->format($this->rc->config->get('date_format', 'Y-m-d'));
7533     + }
7534     + catch (Exception $e) {
7535     + $rec['startdate'] = $rec['startdatetime'] = null;
7536     + }
7537     + }
7538     +
7539     + if ($rec['alarms'])
7540     + $rec['alarms_text'] = libcalendaring::alarms_text($rec['alarms']);
7541     +
7542     + foreach ((array)$rec['attachments'] as $k => $attachment) {
7543     + $rec['attachments'][$k]['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']);
7544     + }
7545     +
7546     + if (in_array($rec['id'], $this->collapsed_tasks))
7547     + $rec['collapsed'] = true;
7548     +
7549     + $this->task_titles[$rec['id']] = $rec['title'];
7550     + }
7551     +
7552     + /**
7553     + * Callback function for array_walk over all tasks.
7554     + * Sets tree depth and parent titles
7555     + */
7556     + private function task_walk_tree(&$rec)
7557     + {
7558     + $rec['_depth'] = 0;
7559     + $parent_id = $this->task_tree[$rec['id']];
7560     + while ($parent_id) {
7561     + $rec['_depth']++;
7562     + $rec['parent_title'] = $this->task_titles[$parent_id];
7563     + $parent_id = $this->task_tree[$parent_id];
7564     + }
7565     + }
7566     +
7567     + /**
7568     + * Compare function for task list sorting.
7569     + * Nested tasks need to be sorted to the end.
7570     + */
7571     + private function task_sort_cmp($a, $b)
7572     + {
7573     + $d = $a['_depth'] - $b['_depth'];
7574     + if (!$d) $d = $b['_hasdate'] - $a['_hasdate'];
7575     + if (!$d) $d = $a['datetime'] - $b['datetime'];
7576     + return $d;
7577     + }
7578     +
7579     + /**
7580     + * Compute the filter mask of the given task
7581     + *
7582     + * @param array Hash array with Task record properties
7583     + * @return int Filter mask
7584     + */
7585     + public function filter_mask($rec)
7586     + {
7587     + static $today, $tomorrow, $weeklimit;
7588     +
7589     + if (!$today) {
7590     + $today_date = new DateTime('now', $this->timezone);
7591     + $today = $today_date->format('Y-m-d');
7592     + $tomorrow_date = new DateTime('now + 1 day', $this->timezone);
7593     + $tomorrow = $tomorrow_date->format('Y-m-d');
7594     + $week_date = new DateTime('now + 7 days', $this->timezone);
7595     + $weeklimit = $week_date->format('Y-m-d');
7596     + }
7597     +
7598     + $mask = 0;
7599     + $start = $rec['startdate'] ?: '1900-00-00';
7600     + $duedate = $rec['date'] ?: '3000-00-00';
7601     +
7602     + if ($rec['flagged'])
7603     + $mask |= self::FILTER_MASK_FLAGGED;
7604     + if ($rec['complete'] == 1.0)
7605     + $mask |= self::FILTER_MASK_COMPLETE;
7606     +
7607     + if (empty($rec['date']))
7608     + $mask |= self::FILTER_MASK_NODATE;
7609     + else if ($rec['date'] < $today)
7610     + $mask |= self::FILTER_MASK_OVERDUE;
7611     +
7612     + if ($duedate <= $today || ($rec['startdate'] && $start <= $today))
7613     + $mask |= self::FILTER_MASK_TODAY;
7614     + if ($duedate <= $tomorrow || ($rec['startdate'] && $start <= $tomorrow))
7615     + $mask |= self::FILTER_MASK_TOMORROW;
7616     + if ($start > $tomorrow || ($duedate > $tomorrow && $duedate <= $weeklimit))
7617     + $mask |= self::FILTER_MASK_WEEK;
7618     + if ($start > $weeklimit || ($rec['date'] && $duedate > $weeklimit))
7619     + $mask |= self::FILTER_MASK_LATER;
7620     +
7621     + return $mask;
7622     + }
7623     +
7624     +
7625     + /******* UI functions ********/
7626     +
7627     + /**
7628     + * Render main view of the tasklist task
7629     + */
7630     + public function tasklist_view()
7631     + {
7632     + $this->ui->init();
7633     + $this->ui->init_templates();
7634     + $this->rc->output->set_pagetitle($this->gettext('navtitle'));
7635     + $this->rc->output->send('tasklist.mainview');
7636     + }
7637     +
7638     +
7639     + /**
7640     + *
7641     + */
7642     + public function get_inline_ui()
7643     + {
7644     + foreach (array('save','cancel','savingdata') as $label)
7645     + $texts['tasklist.'.$label] = $this->gettext($label);
7646     +
7647     + $texts['tasklist.newtask'] = $this->gettext('createfrommail');
7648     +
7649     + $this->ui->init_templates();
7650     + echo $this->api->output->parse('tasklist.taskedit', false, false);
7651     + echo html::tag('script', array('type' => 'text/javascript'),
7652     + "rcmail.set_env('tasklists', " . json_encode($this->api->output->env['tasklists']) . ");\n".
7653     +// "rcmail.set_env('deleteicon', '" . $this->api->output->env['deleteicon'] . "');\n".
7654     +// "rcmail.set_env('cancelicon', '" . $this->api->output->env['cancelicon'] . "');\n".
7655     +// "rcmail.set_env('loadingicon', '" . $this->api->output->env['loadingicon'] . "');\n".
7656     + "rcmail.add_label(" . json_encode($texts) . ");\n"
7657     + );
7658     + exit;
7659     + }
7660     +
7661     +
7662     + /**
7663     + * Handler for pending_alarms plugin hook triggered by the calendar module on keep-alive requests.
7664     + * This will check for pending notifications and pass them to the client
7665     + */
7666     + public function pending_alarms($p)
7667     + {
7668     + $this->load_driver();
7669     + if ($alarms = $this->driver->pending_alarms($p['time'] ?: time())) {
7670     + foreach ($alarms as $alarm) {
7671     + // encode alarm object to suit the expectations of the calendaring code
7672     + if ($alarm['date'])
7673     + $alarm['start'] = new DateTime($alarm['date'].' '.$alarm['time'], $this->timezone);
7674     +
7675     + $alarm['id'] = 'task:' . $alarm['id']; // prefix ID with task:
7676     + $alarm['allday'] = empty($alarm['time']) ? 1 : 0;
7677     + $p['alarms'][] = $alarm;
7678     + }
7679     + }
7680     +
7681     + return $p;
7682     + }
7683     +
7684     + /**
7685     + * Handler for alarm dismiss hook triggered by the calendar module
7686     + */
7687     + public function dismiss_alarms($p)
7688     + {
7689     + $this->load_driver();
7690     + foreach ((array)$p['ids'] as $id) {
7691     + if (strpos($id, 'task:') === 0)
7692     + $p['success'] |= $this->driver->dismiss_alarm(substr($id, 5), $p['snooze']);
7693     + }
7694     +
7695     + return $p;
7696     + }
7697     +
7698     +
7699     + /******* Attachment handling *******/
7700     +
7701     + /**
7702     + * Handler for attachments upload
7703     + */
7704     + public function attachment_upload()
7705     + {
7706     + $this->lib->attachment_upload(self::SESSION_KEY);
7707     + }
7708     +
7709     + /**
7710     + * Handler for attachments download/displaying
7711     + */
7712     + public function attachment_get()
7713     + {
7714     + // show loading page
7715     + if (!empty($_GET['_preload'])) {
7716     + return $this->lib->attachment_loading_page();
7717     + }
7718     +
7719     + $task = get_input_value('_t', RCUBE_INPUT_GPC);
7720     + $list = get_input_value('_list', RCUBE_INPUT_GPC);
7721     + $id = get_input_value('_id', RCUBE_INPUT_GPC);
7722     +
7723     + $task = array('id' => $task, 'list' => $list);
7724     + $attachment = $this->driver->get_attachment($id, $task);
7725     +
7726     + // show part page
7727     + if (!empty($_GET['_frame'])) {
7728     + $this->lib->attachment = $attachment;
7729     + $this->register_handler('plugin.attachmentframe', array($this->lib, 'attachment_frame'));
7730     + $this->register_handler('plugin.attachmentcontrols', array($this->lib, 'attachment_header'));
7731     + $this->rc->output->send('tasklist.attachment');
7732     + }
7733     + // deliver attachment content
7734     + else if ($attachment) {
7735     + $attachment['body'] = $this->driver->get_attachment_body($id, $task);
7736     + $this->lib->attachment_get($attachment);
7737     + }
7738     +
7739     + // if we arrive here, the requested part was not found
7740     + header('HTTP/1.1 404 Not Found');
7741     + exit;
7742     + }
7743     +
7744     +
7745     + /******* Email related function *******/
7746     +
7747     + public function mail_message2task()
7748     + {
7749     + $uid = get_input_value('_uid', RCUBE_INPUT_POST);
7750     + $mbox = get_input_value('_mbox', RCUBE_INPUT_POST);
7751     + $task = array();
7752     +
7753     + // establish imap connection
7754     + $imap = $this->rc->get_storage();
7755     + $imap->set_mailbox($mbox);
7756     + $message = new rcube_message($uid);
7757     +
7758     + if ($message->headers) {
7759     + $task['title'] = trim($message->subject);
7760     + $task['description'] = trim($message->first_text_part());
7761     + $task['id'] = -$uid;
7762     +
7763     + $this->load_driver();
7764     +
7765     + // copy mail attachments to task
7766     + if ($message->attachments && $this->driver->attachments) {
7767     + if (!is_array($_SESSION[self::SESSION_KEY]) || $_SESSION[self::SESSION_KEY]['id'] != $task['id']) {
7768     + $_SESSION[self::SESSION_KEY] = array();
7769     + $_SESSION[self::SESSION_KEY]['id'] = $task['id'];
7770     + $_SESSION[self::SESSION_KEY]['attachments'] = array();
7771     + }
7772     +
7773     + foreach ((array)$message->attachments as $part) {
7774     + $attachment = array(
7775     + 'data' => $imap->get_message_part($uid, $part->mime_id, $part),
7776     + 'size' => $part->size,
7777     + 'name' => $part->filename,
7778     + 'mimetype' => $part->mimetype,
7779     + 'group' => $task['id'],
7780     + );
7781     +
7782     + $attachment = $this->rc->plugins->exec_hook('attachment_save', $attachment);
7783     +
7784     + if ($attachment['status'] && !$attachment['abort']) {
7785     + $id = $attachment['id'];
7786     + $attachment['classname'] = rcube_utils::file2class($attachment['mimetype'], $attachment['name']);
7787     +
7788     + // store new attachment in session
7789     + unset($attachment['status'], $attachment['abort'], $attachment['data']);
7790     + $_SESSION[self::SESSION_KEY]['attachments'][$id] = $attachment;
7791     +
7792     + $attachment['id'] = 'rcmfile' . $attachment['id']; // add prefix to consider it 'new'
7793     + $task['attachments'][] = $attachment;
7794     + }
7795     + }
7796     + }
7797     +
7798     + $this->rc->output->command('plugin.mail2taskdialog', $task);
7799     + }
7800     + else {
7801     + $this->rc->output->command('display_message', $this->gettext('messageopenerror'), 'error');
7802     + }
7803     +
7804     + $this->rc->output->send();
7805     + }
7806     +
7807     +
7808     + /******* Utility functions *******/
7809     +
7810     + /**
7811     + * Generate a unique identifier for an event
7812     + */
7813     + public function generate_uid()
7814     + {
7815     + return strtoupper(md5(time() . uniqid(rand())) . '-' . substr(md5($this->rc->user->get_username()), 0, 16));
7816     + }
7817     +
7818     +}
7819     +
7820     diff -Nur roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist_ui.php roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist_ui.php
7821     --- roundcube-0.9.5-old/root/opt/roundcube/plugins/tasklist/tasklist_ui.php 1970-01-01 01:00:00.000000000 +0100
7822     +++ roundcube-0.9.5/root/opt/roundcube/plugins/tasklist/tasklist_ui.php 2013-11-24 00:38:51.000000000 +0100
7823     @@ -0,0 +1,282 @@
7824     +<?php
7825     +/**
7826     + * User Interface class for the Tasklist plugin
7827     + *
7828     + * @version @package_version@
7829     + * @author Thomas Bruederli <bruederli@kolabsys.com>
7830     + *
7831     + * Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
7832     + *
7833     + * This program is free software: you can redistribute it and/or modify
7834     + * it under the terms of the GNU Affero General Public License as
7835     + * published by the Free Software Foundation, either version 3 of the
7836     + * License, or (at your option) any later version.
7837     + *
7838     + * This program is distributed in the hope that it will be useful,
7839     + * but WITHOUT ANY WARRANTY; without even the implied warranty of
7840     + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
7841     + * GNU Affero General Public License for more details.
7842     + *
7843     + * You should have received a copy of the GNU Affero General Public License
7844     + * along with this program. If not, see <http://www.gnu.org/licenses/>.
7845     + */
7846     +
7847     +
7848     +class tasklist_ui
7849     +{
7850     + private $rc;
7851     + private $plugin;
7852     + private $ready = false;
7853     +
7854     + function __construct($plugin)
7855     + {
7856     + $this->plugin = $plugin;
7857     + $this->rc = $plugin->rc;
7858     + }
7859     +
7860     + /**
7861     + * Calendar UI initialization and requests handlers
7862     + */
7863     + public function init()
7864     + {
7865     + if ($this->ready) // already done
7866     + return;
7867     +
7868     + // add taskbar button
7869     + $this->plugin->add_button(array(
7870     + 'command' => 'tasks',
7871     + 'class' => 'button-tasklist',
7872     + 'classsel' => 'button-tasklist button-selected',
7873     + 'innerclass' => 'button-inner',
7874     + 'label' => 'tasklist.navtitle',
7875     + ), 'taskbar');
7876     +
7877     + $this->plugin->include_stylesheet($this->plugin->local_skin_path() . '/tasklist.css');
7878     + $this->plugin->include_script('tasklist_base.js');
7879     +
7880     + // copy config to client
7881     + // $this->rc->output->set_env('tasklist_settings', $settings);
7882     +
7883     + $this->ready = true;
7884     + }
7885     +
7886     + /**
7887     + * Register handler methods for the template engine
7888     + */
7889     + public function init_templates()
7890     + {
7891     + $this->plugin->register_handler('plugin.tasklists', array($this, 'tasklists'));
7892     + $this->plugin->register_handler('plugin.tasklist_select', array($this, 'tasklist_select'));
7893     + $this->plugin->register_handler('plugin.category_select', array($this, 'category_select'));
7894     + $this->plugin->register_handler('plugin.searchform', array($this->rc->output, 'search_form'));
7895     + $this->plugin->register_handler('plugin.quickaddform', array($this, 'quickadd_form'));
7896     + $this->plugin->register_handler('plugin.tasklist_editform', array($this, 'tasklist_editform'));
7897     + $this->plugin->register_handler('plugin.tasks', array($this, 'tasks_resultview'));
7898     + $this->plugin->register_handler('plugin.tagslist', array($this, 'tagslist'));
7899     + $this->plugin->register_handler('plugin.tags_editline', array($this, 'tags_editline'));
7900     + $this->plugin->register_handler('plugin.alarm_select', array($this, 'alarm_select'));
7901     + $this->plugin->register_handler('plugin.attachments_form', array($this, 'attachments_form'));
7902     + $this->plugin->register_handler('plugin.attachments_list', array($this, 'attachments_list'));
7903     + $this->plugin->register_handler('plugin.filedroparea', array($this, 'file_drop_area'));
7904     +
7905     + $this->plugin->include_script('jquery.tagedit.js');
7906     + $this->plugin->include_script('tasklist.js');
7907     + }
7908     +
7909     + /**
7910     + *
7911     + */
7912     + function tasklists($attrib = array())
7913     + {
7914     + $lists = $this->plugin->driver->get_lists();
7915     +
7916     + $li = '';
7917     + foreach ((array)$lists as $id => $prop) {
7918     + if ($attrib['activeonly'] && !$prop['active'])
7919     + continue;
7920     +
7921     + unset($prop['user_id']);
7922     + $prop['alarms'] = $this->plugin->driver->alarms;
7923     + $prop['undelete'] = $this->plugin->driver->undelete;
7924     + $prop['sortable'] = $this->plugin->driver->sortable;
7925     + $prop['attachments'] = $this->plugin->driver->attachments;
7926     + $jsenv[$id] = $prop;
7927     +
7928     + $html_id = html_identifier($id);
7929     + $class = 'tasks-' . asciiwords($id, true);
7930     +
7931     + if (!$prop['editable'])
7932     + $class .= ' readonly';
7933     + if ($prop['class_name'])
7934     + $class .= ' '.$prop['class_name'];
7935     +
7936     + $li .= html::tag('li', array('id' => 'rcmlitasklist' . $html_id, 'class' => $class),
7937     + html::tag('input', array('type' => 'checkbox', 'name' => '_list[]', 'value' => $id, 'checked' => $prop['active'])) .
7938     + html::span('handle', '&nbsp;') .
7939     + html::span('listname', $prop['name']));
7940     + }
7941     +
7942     + $this->rc->output->set_env('tasklists', $jsenv);
7943     + $this->rc->output->add_gui_object('folderlist', $attrib['id']);
7944     +
7945     + return html::tag('ul', $attrib, $li, html::$common_attrib);
7946     + }
7947     +
7948     +
7949     + /**
7950     + * Render a HTML select box for list selection
7951     + */
7952     + function tasklist_select($attrib = array())
7953     + {
7954     + $attrib['name'] = 'list';
7955     + $attrib['is_escaped'] = true;
7956     + $select = new html_select($attrib);
7957     +
7958     + foreach ((array)$this->plugin->driver->get_lists() as $id => $prop) {
7959     + if ($prop['editable'])
7960     + $select->add($prop['name'], $id);
7961     + }
7962     +
7963     + return $select->show(null);
7964     + }
7965     +
7966     +
7967     + function tasklist_editform($attrib = array())
7968     + {
7969     + $fields = array(
7970     + 'name' => array(
7971     + 'id' => 'taskedit-tasklistame',
7972     + 'label' => $this->plugin->gettext('listname'),
7973     + 'value' => html::tag('input', array('id' => 'taskedit-tasklistame', 'name' => 'name', 'type' => 'text', 'class' => 'text', 'size' => 40)),
7974     + ),
7975     +/*
7976     + 'color' => array(
7977     + 'id' => 'taskedit-color',
7978     + 'label' => $this->plugin->gettext('color'),
7979     + 'value' => html::tag('input', array('id' => 'taskedit-color', 'name' => 'color', 'type' => 'text', 'class' => 'text colorpicker', 'size' => 6)),
7980     + ),
7981     +*/
7982     + 'showalarms' => array(
7983     + 'id' => 'taskedit-showalarms',
7984     + 'label' => $this->plugin->gettext('showalarms'),
7985     + 'value' => html::tag('input', array('id' => 'taskedit-showalarms', 'name' => 'color', 'type' => 'checkbox')),
7986     + ),
7987     + );
7988     +
7989     + return html::tag('form', array('action' => "#", 'method' => "post", 'id' => 'tasklisteditform'),
7990     + $this->plugin->driver->tasklist_edit_form($fields)
7991     + );
7992     + }
7993     +
7994     + /**
7995     + * Render HTML form for alarm configuration
7996     + */
7997     + function alarm_select($attrib = array())
7998     + {
7999     + return $this->plugin->lib->alarm_select($attrib, $this->plugin->driver->alarm_types, $this->plugin->driver->alarm_absolute);
8000     + }
8001     +
8002     + /**
8003     + *
8004     + */
8005     + function quickadd_form($attrib)
8006     + {
8007     + $attrib += array('action' => $this->rc->url('add'), 'method' => 'post', 'id' => 'quickaddform');
8008     +
8009     + $input = new html_inputfield(array('name' => 'text', 'id' => 'quickaddinput'));
8010     + $button = html::tag('input', array('type' => 'submit', 'value' => '+', 'class' => 'button mainaction'));
8011     +
8012     + $this->rc->output->add_gui_object('quickaddform', $attrib['id']);
8013     + return html::tag('form', $attrib, $input->show() . $button);
8014     + }
8015     +
8016     + /**
8017     + * The result view
8018     + */
8019     + function tasks_resultview($attrib)
8020     + {
8021     + $attrib += array('id' => 'rcmtaskslist');
8022     +
8023     + $this->rc->output->add_gui_object('resultlist', $attrib['id']);
8024     +
8025     + unset($attrib['name']);
8026     + return html::tag('ul', $attrib, '');
8027     + }
8028     +
8029     + /**
8030     + * Container for a tags cloud
8031     + */
8032     + function tagslist($attrib)
8033     + {
8034     + $attrib += array('id' => 'rcmtasktagslist');
8035     + unset($attrib['name']);
8036     +
8037     + $this->rc->output->add_gui_object('tagslist', $attrib['id']);
8038     + return html::tag('ul', $attrib, '');
8039     + }
8040     +
8041     + /**
8042     + * Interactive UI element to add/remove tags
8043     + */
8044     + function tags_editline($attrib)
8045     + {
8046     + $attrib += array('id' => 'rcmtasktagsedit');
8047     + $this->rc->output->add_gui_object('edittagline', $attrib['id']);
8048     +
8049     + $input = new html_inputfield(array('name' => 'tags[]', 'class' => 'tag', 'size' => $attrib['size'], 'tabindex' => $attrib['tabindex']));
8050     + return html::div($attrib, $input->show(''));
8051     + }
8052     +
8053     + /**
8054     + * Generate HTML element for attachments list
8055     + */
8056     + function attachments_list($attrib = array())
8057     + {
8058     + if (!$attrib['id'])
8059     + $attrib['id'] = 'rcmtaskattachmentlist';
8060     +
8061     + $this->rc->output->add_gui_object('attachmentlist', $attrib['id']);
8062     +
8063     + return html::tag('ul', $attrib, '', html::$common_attrib);
8064     + }
8065     +
8066     + /**
8067     + * Generate the form for event attachments upload
8068     + */
8069     + function attachments_form($attrib = array())
8070     + {
8071     + // add ID if not given
8072     + if (!$attrib['id'])
8073     + $attrib['id'] = 'rcmtaskuploadform';
8074     +
8075     + // Get max filesize, enable upload progress bar
8076     + $max_filesize = rcube_upload_init();
8077     +
8078     + $button = new html_inputfield(array('type' => 'button'));
8079     + $input = new html_inputfield(array(
8080     + 'type' => 'file',
8081     + 'name' => '_attachments[]',
8082     + 'multiple' => 'multiple',
8083     + 'size' => $attrib['attachmentfieldsize'],
8084     + ));
8085     +
8086     + return html::div($attrib,
8087     + html::div(null, $input->show()) .
8088     + html::div('formbuttons', $button->show(rcube_label('upload'), array('class' => 'button mainaction',
8089     + 'onclick' => JS_OBJECT_NAME . ".upload_file(this.form)"))) .
8090     + html::div('hint', rcube_label(array('name' => 'maxuploadsize', 'vars' => array('size' => $max_filesize))))
8091     + );
8092     + }
8093     +
8094     + /**
8095     + * Register UI object for HTML5 drag & drop file upload
8096     + */
8097     + function file_drop_area($attrib = array())
8098     + {
8099     + if ($attrib['id']) {
8100     + $this->rc->output->add_gui_object('filedrop', $attrib['id']);
8101     + $this->rc->output->set_env('filedrop', array('action' => 'upload', 'fieldname' => '_attachments'));
8102     + }
8103     + }
8104     +
8105     +}

admin@koozali.org
ViewVC Help
Powered by ViewVC 1.2.1 RSS 2.0 feed