2012-06-08 14:57:16 +02:00
|
|
|
<?php
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Kolab Groupware driver for the Tasklist plugin
|
|
|
|
*
|
|
|
|
* @version @package_version@
|
|
|
|
* @author Thomas Bruederli <bruederli@kolabsys.com>
|
|
|
|
*
|
|
|
|
* Copyright (C) 2012, Kolab Systems AG <contact@kolabsys.com>
|
|
|
|
*
|
|
|
|
* This program is free software: you can redistribute it and/or modify
|
|
|
|
* it under the terms of the GNU Affero General Public License as
|
|
|
|
* published by the Free Software Foundation, either version 3 of the
|
|
|
|
* License, or (at your option) any later version.
|
|
|
|
*
|
|
|
|
* This program is distributed in the hope that it will be useful,
|
|
|
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
|
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
|
|
* GNU Affero General Public License for more details.
|
|
|
|
*
|
|
|
|
* You should have received a copy of the GNU Affero General Public License
|
|
|
|
* along with this program. If not, see <http://www.gnu.org/licenses/>.
|
|
|
|
*/
|
|
|
|
|
|
|
|
class tasklist_kolab_driver extends tasklist_driver
|
|
|
|
{
|
|
|
|
// features supported by the backend
|
|
|
|
public $alarms = false;
|
2012-08-01 15:52:28 +02:00
|
|
|
public $attachments = true;
|
2012-06-08 14:57:16 +02:00
|
|
|
public $undelete = false; // task undelete action
|
2012-08-02 11:15:48 +02:00
|
|
|
public $alarm_types = array('DISPLAY');
|
2012-06-08 14:57:16 +02:00
|
|
|
|
|
|
|
private $rc;
|
|
|
|
private $plugin;
|
|
|
|
private $lists;
|
|
|
|
private $folders = array();
|
|
|
|
private $tasks = array();
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Default constructor
|
|
|
|
*/
|
|
|
|
public function __construct($plugin)
|
|
|
|
{
|
|
|
|
$this->rc = $plugin->rc;
|
|
|
|
$this->plugin = $plugin;
|
|
|
|
|
|
|
|
$this->_read_lists();
|
2012-11-15 15:03:00 +01:00
|
|
|
|
2012-11-22 15:19:17 +01:00
|
|
|
if (kolab_storage::$version == '2.0') {
|
2012-11-15 15:03:00 +01:00
|
|
|
$this->alarm_absolute = false;
|
|
|
|
}
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Read available calendars for the current user and store them internally
|
|
|
|
*/
|
2012-11-21 12:25:14 +01:00
|
|
|
private function _read_lists($force = false)
|
2012-06-08 14:57:16 +02:00
|
|
|
{
|
|
|
|
// already read sources
|
2012-11-21 12:25:14 +01:00
|
|
|
if (isset($this->lists) && !$force)
|
2012-06-08 14:57:16 +02:00
|
|
|
return $this->lists;
|
|
|
|
|
|
|
|
// get all folders that have type "task"
|
2013-07-18 17:47:49 +02:00
|
|
|
$folders = kolab_storage::sort_folders(kolab_storage::get_folders('task'));
|
|
|
|
$this->lists = $this->folders = array();
|
|
|
|
|
|
|
|
// find default folder
|
|
|
|
$default_index = 0;
|
|
|
|
foreach ($folders as $i => $folder) {
|
2012-09-20 15:35:47 +02:00
|
|
|
if ($folder->default)
|
2013-07-18 17:47:49 +02:00
|
|
|
$default_index = $i;
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
2012-09-20 09:38:43 +02:00
|
|
|
// put default folder (aka INBOX) on top of the list
|
2013-07-18 17:47:49 +02:00
|
|
|
if ($default_index > 0) {
|
|
|
|
$default_folder = $folders[$default_index];
|
|
|
|
unset($folders[$default_index]);
|
|
|
|
array_unshift($folders, $default_folder);
|
2012-09-20 09:38:43 +02:00
|
|
|
}
|
|
|
|
|
2012-06-21 21:59:47 +02:00
|
|
|
$delim = $this->rc->get_storage()->get_hierarchy_delimiter();
|
2013-10-10 17:27:24 +02:00
|
|
|
$prefs = $this->rc->config->get('kolab_tasklists', array());
|
2012-06-21 21:59:47 +02:00
|
|
|
$listnames = array();
|
|
|
|
|
2013-10-10 17:27:24 +02:00
|
|
|
// include virtual folders for a full folder tree
|
|
|
|
if (!$this->rc->output->ajax_call)
|
|
|
|
$folders = $this->_folder_hierarchy($folders, $delim);
|
2012-08-04 17:19:03 +02:00
|
|
|
|
2013-07-18 17:47:49 +02:00
|
|
|
foreach ($folders as $folder) {
|
|
|
|
$utf7name = $folder->name;
|
2012-06-21 21:59:47 +02:00
|
|
|
|
2013-07-18 17:47:49 +02:00
|
|
|
$path_imap = explode($delim, $utf7name);
|
|
|
|
$editname = rcube_charset::convert(array_pop($path_imap), 'UTF7-IMAP'); // pop off raw name part
|
2012-06-21 21:59:47 +02:00
|
|
|
$path_imap = join($delim, $path_imap);
|
|
|
|
|
2013-10-10 17:27:24 +02:00
|
|
|
$fullname = kolab_storage::object_name($utf7name);
|
|
|
|
$name = kolab_storage::folder_displayname($fullname, $listnames);
|
|
|
|
|
|
|
|
// special handling for virtual folders
|
|
|
|
if ($folder->virtual) {
|
|
|
|
$list_id = kolab_storage::folder_id($utf7name);
|
|
|
|
$this->lists[$list_id] = array(
|
|
|
|
'id' => $list_id,
|
|
|
|
'name' => $name,
|
|
|
|
'virtual' => true,
|
|
|
|
);
|
|
|
|
continue;
|
|
|
|
}
|
2012-06-21 21:59:47 +02:00
|
|
|
|
2012-10-03 11:53:02 +02:00
|
|
|
if ($folder->get_namespace() == 'personal') {
|
2013-10-10 17:27:24 +02:00
|
|
|
$norename = false;
|
2012-09-06 17:52:14 +02:00
|
|
|
$readonly = false;
|
|
|
|
$alarms = true;
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
$alarms = false;
|
|
|
|
$readonly = true;
|
|
|
|
if (($rights = $folder->get_myrights()) && !PEAR::isError($rights)) {
|
|
|
|
if (strpos($rights, 'i') !== false)
|
|
|
|
$readonly = false;
|
|
|
|
}
|
2013-10-10 17:27:24 +02:00
|
|
|
$info = $folder->get_folder_info();
|
|
|
|
$norename = $readonly || $info['norename'] || $info['protected'];
|
2012-09-06 17:52:14 +02:00
|
|
|
}
|
|
|
|
|
2012-08-04 17:19:03 +02:00
|
|
|
$list_id = kolab_storage::folder_id($utf7name);
|
2012-06-08 14:57:16 +02:00
|
|
|
$tasklist = array(
|
2012-08-04 17:19:03 +02:00
|
|
|
'id' => $list_id,
|
2012-06-21 21:59:47 +02:00
|
|
|
'name' => $name,
|
2013-10-10 17:27:24 +02:00
|
|
|
'altname' => $fullname,
|
2012-06-21 21:59:47 +02:00
|
|
|
'editname' => $editname,
|
2012-11-21 12:30:27 +01:00
|
|
|
'color' => $folder->get_color('0000CC'),
|
2012-09-06 17:52:14 +02:00
|
|
|
'showalarms' => isset($prefs[$list_id]['showalarms']) ? $prefs[$list_id]['showalarms'] : $alarms,
|
2013-10-10 17:27:24 +02:00
|
|
|
'editable' => !$readionly,
|
|
|
|
'norename' => $norename,
|
2012-12-10 12:17:41 +01:00
|
|
|
'active' => $folder->is_active(),
|
2012-06-21 21:59:47 +02:00
|
|
|
'parentfolder' => $path_imap,
|
2012-09-20 15:35:47 +02:00
|
|
|
'default' => $folder->default,
|
2013-10-04 10:07:21 +02:00
|
|
|
'children' => true, // TODO: determine if that folder indeed has child folders
|
2012-09-20 15:35:47 +02:00
|
|
|
'class_name' => trim($folder->get_namespace() . ($folder->default ? ' default' : '')),
|
2012-06-08 14:57:16 +02:00
|
|
|
);
|
|
|
|
$this->lists[$tasklist['id']] = $tasklist;
|
|
|
|
$this->folders[$tasklist['id']] = $folder;
|
2013-10-10 17:27:24 +02:00
|
|
|
$this->folders[$folder->name] = $folder;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Check the folder tree and add the missing parents as virtual folders
|
|
|
|
*/
|
|
|
|
private function _folder_hierarchy($folders, $delim)
|
|
|
|
{
|
|
|
|
$parents = array();
|
|
|
|
$existing = array_map(function($folder){ return $folder->name; }, $folders);
|
|
|
|
foreach ($folders as $id => $folder) {
|
|
|
|
$path = explode($delim, $folder->name);
|
|
|
|
array_pop($path);
|
|
|
|
|
|
|
|
// skip top folders or ones with a custom displayname
|
|
|
|
if (count($path) <= 1 || kolab_storage::custom_displayname($folder->name))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
while (count($path) > 1 && ($parent = join($delim, $path))) {
|
|
|
|
if (!in_array($parent, $existing) && !$parents[$parent]) {
|
|
|
|
$parents[$parent] = new virtual_kolab_storage_folder($parent, $folder->get_namespace());
|
|
|
|
}
|
|
|
|
array_pop($path);
|
|
|
|
}
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
2013-10-10 17:27:24 +02:00
|
|
|
|
|
|
|
// add virtual parents to the list and sort again
|
|
|
|
if (count($parents)) {
|
|
|
|
$folders = kolab_storage::sort_folders(array_merge($folders, array_values($parents)));
|
|
|
|
}
|
|
|
|
|
|
|
|
return $folders;
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
2013-10-10 17:27:24 +02:00
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
/**
|
|
|
|
* Get a list of available task lists from this source
|
|
|
|
*/
|
|
|
|
public function get_lists()
|
|
|
|
{
|
|
|
|
// attempt to create a default list for this user
|
|
|
|
if (empty($this->lists)) {
|
2012-11-21 12:30:27 +01:00
|
|
|
if ($this->create_list(array('name' => 'Tasks', 'color' => '0000CC', 'default' => true)))
|
|
|
|
$this->_read_lists(true);
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $this->lists;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Create a new list assigned to the current user
|
|
|
|
*
|
|
|
|
* @param array Hash array with list properties
|
|
|
|
* name: List name
|
|
|
|
* color: The color of the list
|
|
|
|
* showalarms: True if alarms are enabled
|
|
|
|
* @return mixed ID of the new list on success, False on error
|
|
|
|
*/
|
|
|
|
public function create_list($prop)
|
|
|
|
{
|
2012-11-21 12:25:14 +01:00
|
|
|
$prop['type'] = 'task' . ($prop['default'] ? '.default' : '');
|
2012-12-10 12:17:41 +01:00
|
|
|
$prop['active'] = true; // activate folder by default
|
2012-06-21 21:59:47 +02:00
|
|
|
$folder = kolab_storage::folder_update($prop);
|
|
|
|
|
|
|
|
if ($folder === false) {
|
|
|
|
$this->last_error = kolab_storage::$last_error;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create ID
|
2012-08-04 17:19:03 +02:00
|
|
|
$id = kolab_storage::folder_id($folder);
|
|
|
|
|
|
|
|
$prefs['kolab_tasklists'] = $this->rc->config->get('kolab_tasklists', array());
|
|
|
|
|
|
|
|
if (isset($prop['showalarms']))
|
|
|
|
$prefs['kolab_tasklists'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
|
|
|
|
|
|
|
|
if ($prefs['kolab_tasklists'][$id])
|
|
|
|
$this->rc->user->save_prefs($prefs);
|
|
|
|
|
|
|
|
return $id;
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update properties of an existing tasklist
|
|
|
|
*
|
|
|
|
* @param array Hash array with list properties
|
|
|
|
* id: List Identifier
|
|
|
|
* name: List name
|
|
|
|
* color: The color of the list
|
|
|
|
* showalarms: True if alarms are enabled (if supported)
|
|
|
|
* @return boolean True on success, Fales on failure
|
|
|
|
*/
|
|
|
|
public function edit_list($prop)
|
|
|
|
{
|
2012-06-21 21:59:47 +02:00
|
|
|
if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
|
|
|
|
$prop['oldname'] = $folder->name;
|
|
|
|
$prop['type'] = 'task';
|
|
|
|
$newfolder = kolab_storage::folder_update($prop);
|
|
|
|
|
|
|
|
if ($newfolder === false) {
|
|
|
|
$this->last_error = kolab_storage::$last_error;
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
// create ID
|
2012-08-04 17:19:03 +02:00
|
|
|
$id = kolab_storage::folder_id($newfolder);
|
|
|
|
|
|
|
|
// fallback to local prefs
|
|
|
|
$prefs['kolab_tasklists'] = $this->rc->config->get('kolab_tasklists', array());
|
|
|
|
unset($prefs['kolab_tasklists'][$prop['id']]);
|
|
|
|
|
|
|
|
if (isset($prop['showalarms']))
|
|
|
|
$prefs['kolab_tasklists'][$id]['showalarms'] = $prop['showalarms'] ? true : false;
|
|
|
|
|
|
|
|
if ($prefs['kolab_tasklists'][$id])
|
|
|
|
$this->rc->user->save_prefs($prefs);
|
|
|
|
|
|
|
|
return $id;
|
2012-06-21 21:59:47 +02:00
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Set active/subscribed state of a list
|
|
|
|
*
|
|
|
|
* @param array Hash array with list properties
|
|
|
|
* id: List Identifier
|
|
|
|
* active: True if list is active, false if not
|
|
|
|
* @return boolean True on success, Fales on failure
|
|
|
|
*/
|
|
|
|
public function subscribe_list($prop)
|
|
|
|
{
|
2012-06-21 21:59:47 +02:00
|
|
|
if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
|
2012-12-10 12:17:41 +01:00
|
|
|
return $folder->activate($prop['active']);
|
2012-06-21 21:59:47 +02:00
|
|
|
}
|
2012-06-08 14:57:16 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Delete the given list with all its contents
|
|
|
|
*
|
|
|
|
* @param array Hash array with list properties
|
|
|
|
* id: list Identifier
|
|
|
|
* @return boolean True on success, Fales on failure
|
|
|
|
*/
|
|
|
|
public function remove_list($prop)
|
|
|
|
{
|
2012-06-21 21:59:47 +02:00
|
|
|
if ($prop['id'] && ($folder = $this->folders[$prop['id']])) {
|
|
|
|
if (kolab_storage::folder_delete($folder->name))
|
|
|
|
return true;
|
|
|
|
else
|
|
|
|
$this->last_error = kolab_storage::$last_error;
|
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get number of tasks matching the given filter
|
|
|
|
*
|
|
|
|
* @param array List of lists to count tasks of
|
|
|
|
* @return array Hash array with counts grouped by status (all|flagged|completed|today|tomorrow|nodate)
|
|
|
|
*/
|
|
|
|
public function count_tasks($lists = null)
|
|
|
|
{
|
|
|
|
if (empty($lists))
|
|
|
|
$lists = array_keys($this->lists);
|
|
|
|
else if (is_string($lists))
|
|
|
|
$lists = explode(',', $lists);
|
|
|
|
|
|
|
|
$today_date = new DateTime('now', $this->plugin->timezone);
|
|
|
|
$today = $today_date->format('Y-m-d');
|
|
|
|
$tomorrow_date = new DateTime('now + 1 day', $this->plugin->timezone);
|
|
|
|
$tomorrow = $tomorrow_date->format('Y-m-d');
|
|
|
|
|
|
|
|
$counts = array('all' => 0, 'flagged' => 0, 'today' => 0, 'tomorrow' => 0, 'overdue' => 0, 'nodate' => 0);
|
|
|
|
foreach ($lists as $list_id) {
|
|
|
|
$folder = $this->folders[$list_id];
|
2012-07-13 11:21:50 +02:00
|
|
|
foreach ((array)$folder->select(array(array('tags','!~','x-complete'))) as $record) {
|
2012-06-08 14:57:16 +02:00
|
|
|
$rec = $this->_to_rcube_task($record);
|
|
|
|
|
2012-07-13 11:21:50 +02:00
|
|
|
if ($rec['complete'] >= 1.0) // don't count complete tasks
|
2012-06-08 14:57:16 +02:00
|
|
|
continue;
|
|
|
|
|
|
|
|
$counts['all']++;
|
|
|
|
if ($rec['flagged'])
|
|
|
|
$counts['flagged']++;
|
|
|
|
if (empty($rec['date']))
|
|
|
|
$counts['nodate']++;
|
|
|
|
else if ($rec['date'] == $today)
|
|
|
|
$counts['today']++;
|
|
|
|
else if ($rec['date'] == $tomorrow)
|
|
|
|
$counts['tomorrow']++;
|
|
|
|
else if ($rec['date'] < $today)
|
|
|
|
$counts['overdue']++;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $counts;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get all taks records matching the given filter
|
|
|
|
*
|
|
|
|
* @param array Hash array with filter criterias:
|
|
|
|
* - mask: Bitmask representing the filter selection (check against tasklist::FILTER_MASK_* constants)
|
|
|
|
* - from: Date range start as string (Y-m-d)
|
|
|
|
* - to: Date range end as string (Y-m-d)
|
|
|
|
* - search: Search query string
|
|
|
|
* @param array List of lists to get tasks from
|
|
|
|
* @return array List of tasks records matchin the criteria
|
|
|
|
*/
|
|
|
|
public function list_tasks($filter, $lists = null)
|
|
|
|
{
|
|
|
|
if (empty($lists))
|
|
|
|
$lists = array_keys($this->lists);
|
|
|
|
else if (is_string($lists))
|
|
|
|
$lists = explode(',', $lists);
|
|
|
|
|
|
|
|
$results = array();
|
|
|
|
|
|
|
|
// query Kolab storage
|
|
|
|
$query = array();
|
|
|
|
if ($filter['mask'] & tasklist::FILTER_MASK_COMPLETE)
|
2012-07-13 11:24:10 +02:00
|
|
|
$query[] = array('tags','~','x-complete');
|
2012-06-08 14:57:16 +02:00
|
|
|
else
|
2012-07-13 11:24:10 +02:00
|
|
|
$query[] = array('tags','!~','x-complete');
|
2012-06-08 14:57:16 +02:00
|
|
|
|
|
|
|
// full text search (only works with cache enabled)
|
|
|
|
if ($filter['search']) {
|
|
|
|
$search = mb_strtolower($filter['search']);
|
|
|
|
foreach (rcube_utils::normalize_string($search, true) as $word) {
|
|
|
|
$query[] = array('words', '~', $word);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
foreach ($lists as $list_id) {
|
|
|
|
$folder = $this->folders[$list_id];
|
|
|
|
foreach ((array)$folder->select($query) as $record) {
|
|
|
|
$task = $this->_to_rcube_task($record);
|
|
|
|
$task['list'] = $list_id;
|
|
|
|
|
|
|
|
// TODO: post-filter tasks returned from storage
|
|
|
|
|
|
|
|
$results[] = $task;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $results;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Return data of a specific task
|
|
|
|
*
|
|
|
|
* @param mixed Hash array with task properties or task UID
|
|
|
|
* @return array Hash array with task properties or false if not found
|
|
|
|
*/
|
|
|
|
public function get_task($prop)
|
|
|
|
{
|
2012-09-26 12:14:42 +02:00
|
|
|
$id = is_array($prop) ? ($prop['uid'] ?: $prop['id']) : $prop;
|
2012-06-08 14:57:16 +02:00
|
|
|
$list_id = is_array($prop) ? $prop['list'] : null;
|
|
|
|
$folders = $list_id ? array($list_id => $this->folders[$list_id]) : $this->folders;
|
|
|
|
|
|
|
|
// find task in the available folders
|
2012-09-26 12:14:42 +02:00
|
|
|
foreach ($folders as $list_id => $folder) {
|
|
|
|
if (is_numeric($list_id))
|
|
|
|
continue;
|
2012-06-08 14:57:16 +02:00
|
|
|
if (!$this->tasks[$id] && ($object = $folder->get_object($id))) {
|
|
|
|
$this->tasks[$id] = $this->_to_rcube_task($object);
|
2012-09-26 12:14:42 +02:00
|
|
|
$this->tasks[$id]['list'] = $list_id;
|
2012-06-08 14:57:16 +02:00
|
|
|
break;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return $this->tasks[$id];
|
|
|
|
}
|
|
|
|
|
2012-09-26 12:14:42 +02:00
|
|
|
/**
|
|
|
|
* Get all decendents of the given task record
|
|
|
|
*
|
|
|
|
* @param mixed Hash array with task properties or task UID
|
|
|
|
* @param boolean True if all childrens children should be fetched
|
|
|
|
* @return array List of all child task IDs
|
|
|
|
*/
|
|
|
|
public function get_childs($prop, $recursive = false)
|
|
|
|
{
|
|
|
|
if (is_string($prop)) {
|
|
|
|
$task = $this->get_task($prop);
|
|
|
|
$prop = array('id' => $task['id'], 'list' => $task['list']);
|
|
|
|
}
|
|
|
|
|
|
|
|
$childs = array();
|
|
|
|
$list_id = $prop['list'];
|
|
|
|
$task_ids = array($prop['id']);
|
|
|
|
$folder = $this->folders[$list_id];
|
|
|
|
|
|
|
|
// query for childs (recursively)
|
|
|
|
while ($folder && !empty($task_ids)) {
|
|
|
|
$query_ids = array();
|
|
|
|
foreach ($task_ids as $task_id) {
|
|
|
|
$query = array(array('tags','=','x-parent:' . $task_id));
|
|
|
|
foreach ((array)$folder->select($query) as $record) {
|
|
|
|
// don't rely on kolab_storage_folder filtering
|
|
|
|
if ($record['parent_id'] == $task_id) {
|
|
|
|
$childs[] = $record['uid'];
|
|
|
|
$query_ids[] = $record['uid'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
if (!$recursive)
|
|
|
|
break;
|
|
|
|
|
|
|
|
$task_ids = $query_ids;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $childs;
|
|
|
|
}
|
|
|
|
|
2012-08-03 21:34:00 +02:00
|
|
|
/**
|
|
|
|
* Get a list of pending alarms to be displayed to the user
|
|
|
|
*
|
|
|
|
* @param integer Current time (unix timestamp)
|
|
|
|
* @param mixed List of list IDs to show alarms for (either as array or comma-separated string)
|
|
|
|
* @return array A list of alarms, each encoded as hash array with task properties
|
|
|
|
* @see tasklist_driver::pending_alarms()
|
|
|
|
*/
|
|
|
|
public function pending_alarms($time, $lists = null)
|
|
|
|
{
|
2012-08-04 17:19:03 +02:00
|
|
|
$interval = 300;
|
|
|
|
$time -= $time % 60;
|
|
|
|
|
|
|
|
$slot = $time;
|
|
|
|
$slot -= $slot % $interval;
|
|
|
|
|
2012-11-17 09:49:57 +01:00
|
|
|
$last = $time - max(60, $this->rc->config->get('refresh_interval', 0));
|
2012-08-04 17:19:03 +02:00
|
|
|
$last -= $last % $interval;
|
|
|
|
|
|
|
|
// only check for alerts once in 5 minutes
|
|
|
|
if ($last == $slot)
|
|
|
|
return array();
|
|
|
|
|
|
|
|
if ($lists && is_string($lists))
|
|
|
|
$lists = explode(',', $lists);
|
|
|
|
|
|
|
|
$time = $slot + $interval;
|
|
|
|
|
|
|
|
$tasks = array();
|
2012-08-16 08:57:25 +02:00
|
|
|
$query = array(array('tags', '=', 'x-has-alarms'), array('tags', '!=', 'x-complete'));
|
2012-08-04 17:19:03 +02:00
|
|
|
foreach ($this->lists as $lid => $list) {
|
|
|
|
// skip lists with alarms disabled
|
|
|
|
if (!$list['showalarms'] || ($lists && !in_array($lid, $lists)))
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$folder = $this->folders[$lid];
|
|
|
|
foreach ((array)$folder->select($query) as $record) {
|
|
|
|
if (!$record['alarms']) // don't trust query :-)
|
|
|
|
continue;
|
|
|
|
|
|
|
|
$task = $this->_to_rcube_task($record);
|
|
|
|
|
|
|
|
// add to list if alarm is set
|
2012-08-16 08:57:25 +02:00
|
|
|
$alarm = libcalendaring::get_next_alarm($task, 'task');
|
2012-08-04 17:19:03 +02:00
|
|
|
if ($alarm && $alarm['time'] && $alarm['time'] <= $time && $alarm['action'] == 'DISPLAY') {
|
|
|
|
$id = $task['id'];
|
|
|
|
$tasks[$id] = $task;
|
|
|
|
$tasks[$id]['notifyat'] = $alarm['time'];
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// get alarm information stored in local database
|
|
|
|
if (!empty($tasks)) {
|
|
|
|
$task_ids = array_map(array($this->rc->db, 'quote'), array_keys($tasks));
|
|
|
|
$result = $this->rc->db->query(sprintf(
|
|
|
|
"SELECT * FROM kolab_alarms
|
|
|
|
WHERE event_id IN (%s) AND user_id=?",
|
|
|
|
join(',', $task_ids),
|
|
|
|
$this->rc->db->now()
|
|
|
|
),
|
|
|
|
$this->rc->user->ID
|
|
|
|
);
|
|
|
|
|
|
|
|
while ($result && ($rec = $this->rc->db->fetch_assoc($result))) {
|
|
|
|
$dbdata[$rec['event_id']] = $rec;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$alarms = array();
|
|
|
|
foreach ($tasks as $id => $task) {
|
|
|
|
// skip dismissed
|
|
|
|
if ($dbdata[$id]['dismissed'])
|
|
|
|
continue;
|
|
|
|
|
|
|
|
// snooze function may have shifted alarm time
|
|
|
|
$notifyat = $dbdata[$id]['notifyat'] ? strtotime($dbdata[$id]['notifyat']) : $task['notifyat'];
|
|
|
|
if ($notifyat <= $time)
|
|
|
|
$alarms[] = $task;
|
|
|
|
}
|
|
|
|
|
|
|
|
return $alarms;
|
2012-08-03 21:34:00 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* (User) feedback after showing an alarm notification
|
|
|
|
* This should mark the alarm as 'shown' or snooze it for the given amount of time
|
|
|
|
*
|
|
|
|
* @param string Task identifier
|
|
|
|
* @param integer Suspend the alarm for this number of seconds
|
|
|
|
*/
|
|
|
|
public function dismiss_alarm($id, $snooze = 0)
|
|
|
|
{
|
2012-08-04 17:19:03 +02:00
|
|
|
// delete old alarm entry
|
|
|
|
$this->rc->db->query(
|
|
|
|
"DELETE FROM kolab_alarms
|
|
|
|
WHERE event_id=? AND user_id=?",
|
|
|
|
$id,
|
|
|
|
$this->rc->user->ID
|
|
|
|
);
|
|
|
|
|
|
|
|
// set new notifyat time or unset if not snoozed
|
|
|
|
$notifyat = $snooze > 0 ? date('Y-m-d H:i:s', time() + $snooze) : null;
|
|
|
|
|
|
|
|
$query = $this->rc->db->query(
|
|
|
|
"INSERT INTO kolab_alarms
|
|
|
|
(event_id, user_id, dismissed, notifyat)
|
|
|
|
VALUES(?, ?, ?, ?)",
|
|
|
|
$id,
|
|
|
|
$this->rc->user->ID,
|
|
|
|
$snooze > 0 ? 0 : 1,
|
|
|
|
$notifyat
|
|
|
|
);
|
|
|
|
|
|
|
|
return $this->rc->db->affected_rows($query);
|
2012-08-03 21:34:00 +02:00
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
/**
|
|
|
|
* Convert from Kolab_Format to internal representation
|
|
|
|
*/
|
|
|
|
private function _to_rcube_task($record)
|
|
|
|
{
|
|
|
|
$task = array(
|
|
|
|
'id' => $record['uid'],
|
|
|
|
'uid' => $record['uid'],
|
|
|
|
'title' => $record['title'],
|
|
|
|
# 'location' => $record['location'],
|
|
|
|
'description' => $record['description'],
|
2012-07-12 22:31:53 +02:00
|
|
|
'tags' => (array)$record['categories'],
|
2012-06-08 14:57:16 +02:00
|
|
|
'flagged' => $record['priority'] == 1,
|
|
|
|
'complete' => $record['status'] == 'COMPLETED' ? 1 : floatval($record['complete'] / 100),
|
|
|
|
'parent_id' => $record['parent_id'],
|
|
|
|
);
|
|
|
|
|
|
|
|
// convert from DateTime to internal date format
|
|
|
|
if (is_a($record['due'], 'DateTime')) {
|
|
|
|
$task['date'] = $record['due']->format('Y-m-d');
|
2012-11-08 16:18:28 +01:00
|
|
|
if (!$record['due']->_dateonly)
|
2012-11-15 14:48:49 +01:00
|
|
|
$task['time'] = $record['due']->format('H:i');
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
2012-07-29 13:36:16 +02:00
|
|
|
// convert from DateTime to internal date format
|
|
|
|
if (is_a($record['start'], 'DateTime')) {
|
|
|
|
$task['startdate'] = $record['start']->format('Y-m-d');
|
2012-09-26 16:45:11 +02:00
|
|
|
if (!$record['start']->_dateonly)
|
2012-11-15 14:48:49 +01:00
|
|
|
$task['starttime'] = $record['start']->format('H:i');
|
2012-07-29 13:36:16 +02:00
|
|
|
}
|
2012-06-08 14:57:16 +02:00
|
|
|
if (is_a($record['dtstamp'], 'DateTime')) {
|
2012-07-29 13:36:16 +02:00
|
|
|
$task['changed'] = $record['dtstamp'];
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
2012-08-03 14:07:58 +02:00
|
|
|
if ($record['alarms']) {
|
|
|
|
$task['alarms'] = $record['alarms'];
|
|
|
|
}
|
|
|
|
|
2012-08-01 15:52:28 +02:00
|
|
|
if (!empty($record['_attachments'])) {
|
|
|
|
foreach ($record['_attachments'] as $key => $attachment) {
|
|
|
|
if ($attachment !== false) {
|
|
|
|
if (!$attachment['name'])
|
|
|
|
$attachment['name'] = $key;
|
|
|
|
$attachments[] = $attachment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
$task['attachments'] = $attachments;
|
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
return $task;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Convert the given task record into a data structure that can be passed to kolab_storage backend for saving
|
|
|
|
* (opposite of self::_to_rcube_event())
|
|
|
|
*/
|
|
|
|
private function _from_rcube_task($task, $old = array())
|
|
|
|
{
|
|
|
|
$object = $task;
|
2012-07-12 22:31:53 +02:00
|
|
|
$object['categories'] = (array)$task['tags'];
|
2012-06-08 14:57:16 +02:00
|
|
|
|
|
|
|
if (!empty($task['date'])) {
|
|
|
|
$object['due'] = new DateTime($task['date'].' '.$task['time'], $this->plugin->timezone);
|
|
|
|
if (empty($task['time']))
|
|
|
|
$object['due']->_dateonly = true;
|
|
|
|
unset($object['date']);
|
|
|
|
}
|
|
|
|
|
2012-07-29 13:36:16 +02:00
|
|
|
if (!empty($task['startdate'])) {
|
|
|
|
$object['start'] = new DateTime($task['startdate'].' '.$task['starttime'], $this->plugin->timezone);
|
|
|
|
if (empty($task['starttime']))
|
|
|
|
$object['start']->_dateonly = true;
|
|
|
|
unset($object['startdate']);
|
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
$object['complete'] = $task['complete'] * 100;
|
|
|
|
if ($task['complete'] == 1.0)
|
|
|
|
$object['status'] = 'COMPLETED';
|
|
|
|
|
|
|
|
if ($task['flagged'])
|
|
|
|
$object['priority'] = 1;
|
|
|
|
else
|
|
|
|
$object['priority'] = $old['priority'] > 1 ? $old['priority'] : 0;
|
|
|
|
|
|
|
|
// copy meta data (starting with _) from old object
|
|
|
|
foreach ((array)$old as $key => $val) {
|
|
|
|
if (!isset($object[$key]) && $key[0] == '_')
|
|
|
|
$object[$key] = $val;
|
|
|
|
}
|
|
|
|
|
2012-08-01 15:52:28 +02:00
|
|
|
// delete existing attachment(s)
|
|
|
|
if (!empty($task['deleted_attachments'])) {
|
|
|
|
foreach ($task['deleted_attachments'] as $attachment) {
|
|
|
|
if (is_array($object['_attachments'])) {
|
|
|
|
foreach ($object['_attachments'] as $idx => $att) {
|
|
|
|
if ($att['id'] == $attachment)
|
|
|
|
$object['_attachments'][$idx] = false;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
unset($task['deleted_attachments']);
|
|
|
|
}
|
|
|
|
|
|
|
|
// in kolab_storage attachments are indexed by content-id
|
|
|
|
if (is_array($task['attachments'])) {
|
|
|
|
foreach ($task['attachments'] as $idx => $attachment) {
|
|
|
|
$key = null;
|
|
|
|
// Roundcube ID has nothing to do with the storage ID, remove it
|
|
|
|
if ($attachment['content']) {
|
|
|
|
unset($attachment['id']);
|
|
|
|
}
|
|
|
|
else {
|
|
|
|
foreach ((array)$old['_attachments'] as $cid => $oldatt) {
|
|
|
|
if ($oldatt && $attachment['id'] == $oldatt['id'])
|
|
|
|
$key = $cid;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
// replace existing entry
|
|
|
|
if ($key) {
|
|
|
|
$object['_attachments'][$key] = $attachment;
|
|
|
|
}
|
|
|
|
// append as new attachment
|
|
|
|
else {
|
|
|
|
$object['_attachments'][] = $attachment;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2012-08-02 11:15:48 +02:00
|
|
|
unset($object['attachments']);
|
2012-08-01 15:52:28 +02:00
|
|
|
}
|
|
|
|
|
2012-08-02 11:15:48 +02:00
|
|
|
unset($object['tempid'], $object['raw'], $object['list'], $object['flagged'], $object['tags']);
|
2012-06-08 14:57:16 +02:00
|
|
|
return $object;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Add a single task to the database
|
|
|
|
*
|
|
|
|
* @param array Hash array with task properties (see header of tasklist_driver.php)
|
|
|
|
* @return mixed New task ID on success, False on error
|
|
|
|
*/
|
|
|
|
public function create_task($task)
|
|
|
|
{
|
|
|
|
return $this->edit_task($task);
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Update an task entry with the given data
|
|
|
|
*
|
|
|
|
* @param array Hash array with task properties (see header of tasklist_driver.php)
|
|
|
|
* @return boolean True on success, False on error
|
|
|
|
*/
|
|
|
|
public function edit_task($task)
|
|
|
|
{
|
|
|
|
$list_id = $task['list'];
|
|
|
|
if (!$list_id || !($folder = $this->folders[$list_id]))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// moved from another folder
|
|
|
|
if ($task['_fromlist'] && ($fromfolder = $this->folders[$task['_fromlist']])) {
|
2012-09-26 12:14:42 +02:00
|
|
|
if (!$fromfolder->move($task['id'], $folder->name))
|
2012-06-08 14:57:16 +02:00
|
|
|
return false;
|
|
|
|
|
|
|
|
unset($task['_fromlist']);
|
|
|
|
}
|
|
|
|
|
|
|
|
// load previous version of this task to merge
|
|
|
|
if ($task['id']) {
|
2012-09-26 12:14:42 +02:00
|
|
|
$old = $folder->get_object($task['id']);
|
2012-06-08 14:57:16 +02:00
|
|
|
if (!$old || PEAR::isError($old))
|
|
|
|
return false;
|
2012-09-26 12:14:42 +02:00
|
|
|
|
|
|
|
// merge existing properties if the update isn't complete
|
|
|
|
if (!isset($task['title']) || !isset($task['complete']))
|
|
|
|
$task += $this->_to_rcube_task($old);
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
// generate new task object from RC input
|
|
|
|
$object = $this->_from_rcube_task($task, $old);
|
|
|
|
$saved = $folder->save($object, 'task', $task['id']);
|
|
|
|
|
|
|
|
if (!$saved) {
|
|
|
|
raise_error(array(
|
|
|
|
'code' => 600, 'type' => 'php',
|
|
|
|
'file' => __FILE__, 'line' => __LINE__,
|
|
|
|
'message' => "Error saving task object to Kolab server"),
|
|
|
|
true, false);
|
|
|
|
$saved = false;
|
|
|
|
}
|
|
|
|
else {
|
2012-08-01 15:52:28 +02:00
|
|
|
$task = $this->_to_rcube_task($object);
|
|
|
|
$task['list'] = $list_id;
|
2012-09-26 12:14:42 +02:00
|
|
|
$this->tasks[$task['id']] = $task;
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return $saved;
|
|
|
|
}
|
|
|
|
|
2012-09-18 08:53:24 +02:00
|
|
|
/**
|
|
|
|
* Move a single task to another list
|
|
|
|
*
|
|
|
|
* @param array Hash array with task properties:
|
|
|
|
* @return boolean True on success, False on error
|
|
|
|
* @see tasklist_driver::move_task()
|
|
|
|
*/
|
|
|
|
public function move_task($task)
|
|
|
|
{
|
|
|
|
$list_id = $task['list'];
|
|
|
|
if (!$list_id || !($folder = $this->folders[$list_id]))
|
|
|
|
return false;
|
|
|
|
|
|
|
|
// execute move command
|
|
|
|
if ($task['_fromlist'] && ($fromfolder = $this->folders[$task['_fromlist']])) {
|
2012-09-18 18:43:32 +02:00
|
|
|
return $fromfolder->move($task['id'], $folder->name);
|
2012-09-18 08:53:24 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
/**
|
|
|
|
* Remove a single task from the database
|
|
|
|
*
|
|
|
|
* @param array Hash array with task properties:
|
|
|
|
* id: Task identifier
|
|
|
|
* @param boolean Remove record irreversible (mark as deleted otherwise, if supported by the backend)
|
|
|
|
* @return boolean True on success, False on error
|
|
|
|
*/
|
|
|
|
public function delete_task($task, $force = true)
|
|
|
|
{
|
|
|
|
$list_id = $task['list'];
|
|
|
|
if (!$list_id || !($folder = $this->folders[$list_id]))
|
|
|
|
return false;
|
|
|
|
|
2012-09-26 12:14:42 +02:00
|
|
|
return $folder->delete($task['id']);
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Restores a single deleted task (if supported)
|
|
|
|
*
|
|
|
|
* @param array Hash array with task properties:
|
|
|
|
* id: Task identifier
|
|
|
|
* @return boolean True on success, False on error
|
|
|
|
*/
|
|
|
|
public function undelete_task($prop)
|
|
|
|
{
|
|
|
|
// TODO: implement this
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
|
|
|
|
2012-08-01 15:52:28 +02:00
|
|
|
/**
|
|
|
|
* Get attachment properties
|
|
|
|
*
|
|
|
|
* @param string $id Attachment identifier
|
|
|
|
* @param array $task Hash array with event properties:
|
|
|
|
* id: Task identifier
|
|
|
|
* list: List identifier
|
|
|
|
*
|
|
|
|
* @return array Hash array with attachment properties:
|
|
|
|
* id: Attachment identifier
|
|
|
|
* name: Attachment name
|
|
|
|
* mimetype: MIME content type of the attachment
|
|
|
|
* size: Attachment size
|
|
|
|
*/
|
|
|
|
public function get_attachment($id, $task)
|
|
|
|
{
|
|
|
|
$task['uid'] = $task['id'];
|
|
|
|
$task = $this->get_task($task);
|
|
|
|
|
|
|
|
if ($task && !empty($task['attachments'])) {
|
|
|
|
foreach ($task['attachments'] as $att) {
|
|
|
|
if ($att['id'] == $id)
|
|
|
|
return $att;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return null;
|
|
|
|
}
|
|
|
|
|
|
|
|
/**
|
|
|
|
* Get attachment body
|
|
|
|
*
|
|
|
|
* @param string $id Attachment identifier
|
|
|
|
* @param array $task Hash array with event properties:
|
|
|
|
* id: Task identifier
|
|
|
|
* list: List identifier
|
|
|
|
*
|
|
|
|
* @return string Attachment body
|
|
|
|
*/
|
|
|
|
public function get_attachment_body($id, $task)
|
|
|
|
{
|
|
|
|
if ($storage = $this->folders[$task['list']]) {
|
|
|
|
return $storage->get_attachment($task['id'], $id);
|
|
|
|
}
|
|
|
|
|
|
|
|
return false;
|
|
|
|
}
|
|
|
|
|
2012-06-21 21:59:47 +02:00
|
|
|
/**
|
|
|
|
*
|
|
|
|
*/
|
2012-08-04 17:19:03 +02:00
|
|
|
public function tasklist_edit_form($fieldprop)
|
2012-06-21 21:59:47 +02:00
|
|
|
{
|
2012-08-04 17:19:03 +02:00
|
|
|
$select = kolab_storage::folder_selector('task', array('name' => 'parent', 'id' => 'taskedit-parentfolder'), null);
|
|
|
|
$fieldprop['parent'] = array(
|
|
|
|
'id' => 'taskedit-parentfolder',
|
2012-06-21 21:59:47 +02:00
|
|
|
'label' => $this->plugin->gettext('parentfolder'),
|
|
|
|
'value' => $select->show(''),
|
|
|
|
);
|
2012-08-04 17:19:03 +02:00
|
|
|
|
|
|
|
$formfields = array();
|
|
|
|
foreach (array('name','parent','showalarms') as $f) {
|
|
|
|
$formfields[$f] = $fieldprop[$f];
|
|
|
|
}
|
|
|
|
|
2012-06-21 21:59:47 +02:00
|
|
|
return parent::tasklist_edit_form($formfields);
|
|
|
|
}
|
|
|
|
|
2012-06-08 14:57:16 +02:00
|
|
|
}
|
2013-10-10 17:27:24 +02:00
|
|
|
|
|
|
|
/**
|
|
|
|
* Helper class that represents a virtual IMAP folder
|
|
|
|
* with a subset of the kolab_storage_folder API.
|
|
|
|
*/
|
|
|
|
class virtual_kolab_storage_folder
|
|
|
|
{
|
|
|
|
public $name;
|
|
|
|
public $namespace;
|
|
|
|
public $virtual = true;
|
|
|
|
|
|
|
|
public function __construct($name, $ns)
|
|
|
|
{
|
|
|
|
$this->name = $name;
|
|
|
|
$this->namespace = $ns;
|
|
|
|
}
|
|
|
|
|
|
|
|
public function get_namespace()
|
|
|
|
{
|
|
|
|
return $this->namespace;
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|