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

Contents 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 - (show 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 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