600 lines
20 KiB
PHP
600 lines
20 KiB
PHP
<?php
|
|
/**
|
|
* Nag driver classes for the Kolab IMAP server.
|
|
*
|
|
* Copyright 2004-2017 Horde LLC (http://www.horde.org/)
|
|
*
|
|
* See the enclosed file COPYING for license information (GPL). If you
|
|
* did not receive this file, see http://www.horde.org/licenses/gpl.
|
|
*
|
|
* @author Gunnar Wrobel <wrobel@pardus.de>
|
|
* @author Thomas Jarosch <thomas.jarosch@intra2net.com>
|
|
* @author Stuart Binge <omicron@mighty.co.za>
|
|
* @package Nag
|
|
*/
|
|
class Nag_Driver_Kolab extends Nag_Driver
|
|
{
|
|
/**
|
|
* The Kolab_Storage backend.
|
|
*
|
|
* @var Horde_Kolab_Storage
|
|
*/
|
|
protected $_kolab;
|
|
|
|
/**
|
|
* The current tasklist.
|
|
*
|
|
* @var Horde_Kolab_Storage_Data
|
|
*/
|
|
protected $_data;
|
|
|
|
/**
|
|
* Constructs a new Kolab storage object.
|
|
*
|
|
* @param string $tasklist The tasklist to load.
|
|
* @param array $params A hash containing connection parameters.
|
|
*/
|
|
public function __construct($tasklist, $params = array())
|
|
{
|
|
$this->_tasklist = $tasklist;
|
|
$this->_kolab = $params['kolab'];
|
|
}
|
|
|
|
/**
|
|
* Return the Kolab data handler for the current tasklist.
|
|
*
|
|
* @return Horde_Kolab_Storage_Data The data handler.
|
|
*/
|
|
protected function _getData()
|
|
{
|
|
if (empty($this->_tasklist)) {
|
|
throw new Nag_Exception(
|
|
'The tasklist has been left undefined but is required!'
|
|
);
|
|
}
|
|
if ($this->_data === null) {
|
|
$this->_data = $this->_getDataForTasklist($this->_tasklist);
|
|
}
|
|
return $this->_data;
|
|
}
|
|
|
|
/**
|
|
* Return the Kolab data handler for the specified tasklist.
|
|
*
|
|
* @param string $tasklist The tasklist name.
|
|
*
|
|
* @return Horde_Kolab_Storage_Date The data handler.
|
|
*/
|
|
protected function _getDataForTasklist($tasklist)
|
|
{
|
|
return $this->_kolab->getData(
|
|
$GLOBALS['nag_shares']->getShare($tasklist)->get('folder'),
|
|
'task'
|
|
);
|
|
}
|
|
|
|
/**
|
|
* Retrieves one task from the backend.
|
|
*
|
|
* @param string $taskId The id of the task to retrieve.
|
|
*
|
|
* @return Nag_Task A Nag_Task object.
|
|
* @throws Horde_Exception_NotFound
|
|
* @throws Nag_Exception
|
|
*/
|
|
public function get($taskId)
|
|
{
|
|
$uid = Horde_Url::uriB64Decode($taskId);
|
|
if (!$this->_getData()->objectIdExists($uid)) {
|
|
throw new Horde_Exception_NotFound();
|
|
}
|
|
$task = $this->_getData()->getObject($uid)->getData();
|
|
$nag_task = $this->_buildTask($task);
|
|
$nag_task['tasklist_id'] = $this->_tasklist;
|
|
return new Nag_Task($this, $nag_task);
|
|
}
|
|
|
|
/**
|
|
* Detect if a string is already Horde_Url::uriB64Encode()'ed or not.
|
|
*
|
|
* @param string $s The string to test.
|
|
*
|
|
* @return boolean True if $s was base64 encoded.
|
|
* @todo This will be removed in Horde 6.
|
|
*/
|
|
public function _isBase64EncodedUid($s)
|
|
{
|
|
// Redo stuff possibly done by Horde_Url::uriB64Encode()
|
|
$data = str_replace(array('-', '_'), array('+', '/'), $s);
|
|
$mod4 = strlen($data) % 4;
|
|
if ($mod4) {
|
|
$data .= substr('====', $mod4);
|
|
}
|
|
if (!preg_match('/^[a-zA-Z0-9\/\r\n+]*={0,2}$/', $data)) {
|
|
return false;
|
|
}
|
|
|
|
// Decode the string in strict mode and check the results
|
|
$decoded = base64_decode($data, true);
|
|
if ($decoded === false) {
|
|
return false;
|
|
}
|
|
|
|
// Check if the decoded string resembles a uid hash...
|
|
if (!preg_match('/^[a-zA-Z0-9-]+$/', $decoded)) {
|
|
return false;
|
|
}
|
|
|
|
// Encode the string again for a final sanity check.
|
|
if (base64_encode($decoded) != $data) {
|
|
return false;
|
|
}
|
|
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Build a task based a data array
|
|
*
|
|
* @param array $task The data for the task
|
|
*
|
|
* @return array The converted data array representing the task
|
|
*/
|
|
protected function _buildTask($task)
|
|
{
|
|
// This decoding of parent ID hashes is required because of a
|
|
// previous bug in Nag_Driver_Kolab.
|
|
// See Bug: 14197
|
|
$parent_uid = $task['parent'];
|
|
if (!empty($parent_uid) && $this->_isBase64EncodedUid($parent_uid)) {
|
|
$parent_uid = Horde_Url::uriB64Decode($parent_uid);
|
|
}
|
|
|
|
$result = array(
|
|
'task_id' => Horde_Url::uriB64Encode($task['uid']),
|
|
'uid' => $task['uid'],
|
|
'name' => $task['summary'],
|
|
'desc' => $task['body'],
|
|
'priority' => $task['priority'],
|
|
// parent is keyed to internal id, not UID.
|
|
'parent' => Horde_Url::uriB64Encode($parent_uid),
|
|
'alarm' => $task['alarm'],
|
|
'completed' => !empty($task['completed']),
|
|
'completed_date' => $task['completed_date'],
|
|
'private' => $task['sensitivity'] != 'public',
|
|
'owner' => $GLOBALS['nag_shares']->getShare($this->_tasklist)->get('owner'),
|
|
);
|
|
|
|
if (isset($task['categories'])) {
|
|
$result['internaltags'] = $task['categories'];
|
|
}
|
|
|
|
if (!empty($task['start-date'])) {
|
|
$result['start'] = $task['start-date']->format('U');
|
|
}
|
|
if (!empty($task['due-date'])) {
|
|
$result['due'] = $task['due-date']->format('U');
|
|
}
|
|
if (!empty($task['creation-date'])) {
|
|
$result['created'] = new Horde_Date($task['creation-date']);
|
|
}
|
|
if (!empty($task['last-modification-date'])) {
|
|
$result['modified'] = new Horde_Date($task['last-modification-date']);
|
|
}
|
|
|
|
if (isset($task['recurrence']) && isset($task['due-date'])) {
|
|
$recurrence = new Horde_Date_Recurrence($task['due-date']);
|
|
$recurrence->fromKolab($task['recurrence']);
|
|
$result['recurrence'] = $recurrence;
|
|
}
|
|
|
|
if (isset($task['organizer'])) {
|
|
$result['assignee'] = $task['organizer']['smtp-address'];
|
|
}
|
|
|
|
if (isset($task['horde-estimate'])) {
|
|
$result['estimate'] = $task['horde-estimate'];
|
|
}
|
|
if (isset($task['horde-alarm-methods'])) {
|
|
$result['methods'] = @unserialize($task['horde-alarm-methods']);
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
|
|
/**
|
|
* Retrieves one task from the database by UID.
|
|
*
|
|
* @param string $uid The UID of the task to retrieve.
|
|
*
|
|
* @return array The array of task attributes.
|
|
*/
|
|
public function getByUID($uid)
|
|
{
|
|
foreach (array_keys(Nag::listTasklists(false, Horde_Perms::READ, false)) as $tasklist) {
|
|
$this->_tasklist = $tasklist;
|
|
try {
|
|
return $this->get(Horde_Url::uriB64Encode($uid));
|
|
} catch (Horde_Exception_NotFound $e) {
|
|
}
|
|
}
|
|
throw new Horde_Exception_NotFound();
|
|
}
|
|
|
|
/**
|
|
* Adds a task to the backend storage.
|
|
*
|
|
* @param array $task A hash with the following possible properties:
|
|
* - name: (string) The name (short) of the task.
|
|
* - desc: (string) The description (long) of the task.
|
|
* - start: (OPTIONAL, integer) The start date of the task.
|
|
* - due: (OPTIONAL, integer) The due date of the task.
|
|
* - priority: (OPTIONAL, integer) The priority of the task.
|
|
* - estimate: (OPTIONAL, float) The estimated time to complete the
|
|
* task.
|
|
* - completed: (OPTIONAL, integer) The completion state of the task.
|
|
* - tags: (OPTIONAL, string) The tags of the task.
|
|
* - alarm: (OPTIONAL, integer) The alarm associated with the task.
|
|
* - methods: (OPTIONAL, array) The overridden alarm notification
|
|
* methods.
|
|
* - uid: (OPTIONAL, string) A Unique Identifier for the task.
|
|
* - parent: (OPTIONAL, string) The parent task.
|
|
* - private: (OPTIONAL, boolean) Whether the task is private.
|
|
* - owner: (OPTIONAL, string) The owner of the event.
|
|
* - assignee: (OPTIONAL, string) The assignee of the event.
|
|
* - recurrence: (OPTIONAL, Horde_Date_Recurrence|array) Recurrence
|
|
* information.
|
|
*
|
|
* @return string The Nag ID of the new task.
|
|
* @throws Nag_Exception
|
|
*/
|
|
protected function _add(array $task)
|
|
{
|
|
$object = $this->_getObject($task);
|
|
$object['uid'] = $this->_getData()->generateUid();
|
|
try {
|
|
$this->_getData()->create($object);
|
|
$this->_addTags($task);
|
|
} catch (Horde_Kolab_Storage_Exception $e) {
|
|
throw new Nag_Exception($e);
|
|
}
|
|
return Horde_Url::uriB64Encode($object['uid']);
|
|
}
|
|
|
|
/**
|
|
* Modifies an existing task.
|
|
*
|
|
* @param string $taskId The task to modify.
|
|
* @param array $properties A hash with the following possible properties:
|
|
* - id: (string) The task to modify.
|
|
* - name: (string) The name (short) of the task.
|
|
* - desc: (string) The description (long) of the task.
|
|
* - start: (OPTIONAL, integer) The start date of the task.
|
|
* - due: (OPTIONAL, integer) The due date of the task.
|
|
* - priority: (OPTIONAL, integer) The priority of the task.
|
|
* - estimate: (OPTIONAL, float) The estimated time to complete the
|
|
* task.
|
|
* - completed: (OPTIONAL, integer) The completion state of the task.
|
|
* - tags: (OPTIONAL, string) The tags of the task.
|
|
* - alarm: (OPTIONAL, integer) The alarm associated with the task.
|
|
* - methods: (OPTIONAL, array) The overridden alarm notification
|
|
* methods.
|
|
* - uid: (OPTIONAL, string) A Unique Identifier for the task.
|
|
* - parent: (OPTIONAL, string) The parent task.
|
|
* - private: (OPTIONAL, boolean) Whether the task is private.
|
|
* - owner: (OPTIONAL, string) The owner of the event.
|
|
* - assignee: (OPTIONAL, string) The assignee of the event.
|
|
* - completed_date: (OPTIONAL, integer) The task's completion date.
|
|
* - recurrence: (OPTIONAL, Horde_Date_Recurrence|array) Recurrence
|
|
* information.
|
|
*/
|
|
protected function _modify($taskId, array $task)
|
|
{
|
|
$object = $this->_getObject($task);
|
|
$object['uid'] = Horde_Url::uriB64Decode($taskId);
|
|
try {
|
|
$this->_getData()->modify($object);
|
|
$this->_updateTags($task);
|
|
} catch (Horde_Kolab_Storage_Exception $e) {
|
|
throw new Nag_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Retrieve the Kolab object representations for the task.
|
|
*
|
|
* @param array $task A hash with the following possible properties:
|
|
* - name: (string) The name (short) of the task.
|
|
* - desc: (string) The description (long) of the task.
|
|
* - start: (OPTIONAL, integer) The start date of the task.
|
|
* - due: (OPTIONAL, integer) The due date of the task.
|
|
* - priority: (OPTIONAL, integer) The priority of the task.
|
|
* - estimate: (OPTIONAL, float) The estimated time to complete the
|
|
* task.
|
|
* - completed: (OPTIONAL, integer) The completion state of the task.
|
|
* - tags: (OPTIONAL, string) The tags of the task.
|
|
* - alarm: (OPTIONAL, integer) The alarm associated with the task.
|
|
* - methods: (OPTIONAL, array) The overridden alarm notification
|
|
* methods.
|
|
* - uid: (OPTIONAL, string) A Unique Identifier for the task.
|
|
* - parent: (OPTIONAL, string) The parent task.
|
|
* - private: (OPTIONAL, boolean) Whether the task is private.
|
|
* - owner: (OPTIONAL, string) The owner of the event.
|
|
* - assignee: (OPTIONAL, string) The assignee of the event.
|
|
* - completed_date: (OPTIONAL, integer) The task's completion date.
|
|
* - recurrence: (OPTIONAL, Horde_Date_Recurrence|array) Recurrence
|
|
* information.
|
|
*
|
|
* @return array The Kolab object.
|
|
*/
|
|
protected function _getObject($task)
|
|
{
|
|
$object = array(
|
|
'summary' => $task['name'],
|
|
'body' => $task['desc'],
|
|
//@todo: Match Horde/Kolab priority values
|
|
'priority' => $task['priority'],
|
|
// Kolab's parent field is UID, Nag's parent is id.
|
|
'parent' => Horde_Url::uriB64Decode($task['parent']),
|
|
);
|
|
if (!empty($task['start'])) {
|
|
$object['start-date'] = new DateTime('@' . $task['start']);
|
|
}
|
|
if (!empty($task['due'])) {
|
|
$object['due-date'] = new DateTime('@' . $task['due']);
|
|
}
|
|
if ($task['recurrence']) {
|
|
$object['recurrence'] = $task['recurrence']->toKolab();
|
|
}
|
|
if ($task['completed']) {
|
|
$object['completed'] = 100;
|
|
$object['status'] = 'completed';
|
|
} else {
|
|
$object['completed'] = 0;
|
|
$object['status'] = 'not-started';
|
|
}
|
|
if ($task['alarm'] !== 0) {
|
|
$object['alarm'] = (int)$task['alarm'];
|
|
}
|
|
if ($task['private']) {
|
|
$object['sensitivity'] = 'private';
|
|
} else {
|
|
$object['sensitivity'] = 'public';
|
|
}
|
|
if (isset($task['completed_date'])) {
|
|
$object['completed_date'] = $task['completed_date'];
|
|
}
|
|
if ($task['estimate'] !== 0.0) {
|
|
$object['horde-estimate'] = number_format((float)$task['estimate'], 2);
|
|
}
|
|
if ($task['methods'] !== null) {
|
|
$object['horde-alarm-methods'] = serialize($task['methods']);
|
|
}
|
|
if ($task['owner'] !== null) {
|
|
//@todo: Display name
|
|
$object['creator'] = array(
|
|
'smtp-address' => $task['owner'],
|
|
);
|
|
}
|
|
if ($task['assignee'] !== null) {
|
|
//@todo: Display name
|
|
$object['organizer'] = array(
|
|
'smtp-address' => $task['assignee'],
|
|
);
|
|
}
|
|
if ($task['tags'] && !is_array($task['tags'])) {
|
|
$object['categories'] = $GLOBALS['injector']->getInstance('Nag_Tagger')->split($task['tags']);
|
|
usort($object['categories'], 'strcoll');
|
|
}
|
|
return $object;
|
|
}
|
|
|
|
/**
|
|
* Moves a task to a different tasklist.
|
|
*
|
|
* @param string $taskId The task to move.
|
|
* @param string $newTasklist The new tasklist.
|
|
*/
|
|
protected function _move($taskId, $newTasklist)
|
|
{
|
|
$this->_getData()->move(
|
|
Horde_Url::uriB64Decode($taskId),
|
|
$GLOBALS['nag_shares']->getShare($newTasklist)->get('folder')
|
|
);
|
|
$this->_getDataForTasklist($newTasklist)->synchronize();
|
|
}
|
|
|
|
/**
|
|
* Deletes a task from the backend.
|
|
*
|
|
* @param string $taskId The task to delete.
|
|
*/
|
|
protected function _delete($taskId)
|
|
{
|
|
$this->_getData()->delete(Horde_Url::uriB64Decode($taskId));
|
|
}
|
|
|
|
/**
|
|
* Deletes all tasks from the backend.
|
|
*
|
|
* @return array An array of ids that have been deleted.
|
|
*/
|
|
protected function _deleteAll()
|
|
{
|
|
$this->retrieve();
|
|
$this->tasks->reset();
|
|
$ids = array();
|
|
while ($task = $this->tasks->each()) {
|
|
$ids[] = $task->id;
|
|
}
|
|
$this->_getData()->deleteAll();
|
|
|
|
return $ids;
|
|
}
|
|
|
|
/**
|
|
* Retrieves tasks from the Kolab server.
|
|
*
|
|
* @param integer $completed Which tasks to retrieve (1 = all tasks,
|
|
* 0 = incomplete tasks, 2 = complete tasks).
|
|
*/
|
|
public function retrieve($completed = Nag::VIEW_ALL)
|
|
{
|
|
$dict = array();
|
|
$this->tasks = new Nag_Task();
|
|
|
|
$task_list = $this->_getData()->getObjects();
|
|
if (empty($task_list)) {
|
|
return;
|
|
}
|
|
|
|
foreach ($task_list as $task) {
|
|
$tid = Horde_Url::uriB64Encode($task['uid']);
|
|
$nag_task = $this->_buildTask($task);
|
|
$nag_task['tasklist_id'] = $this->_tasklist;
|
|
$t = new Nag_Task($this, $nag_task);
|
|
$complete = $t->completed;
|
|
if (empty($t->start)) {
|
|
$start = null;
|
|
} else {
|
|
$start = $t->start;
|
|
}
|
|
|
|
switch ($completed) {
|
|
case Nag::VIEW_INCOMPLETE:
|
|
if ($complete) {
|
|
continue 2;
|
|
}
|
|
if ($start && $t->recurs() &&
|
|
($completions = $t->recurrence->getCompletions())) {
|
|
sort($completions);
|
|
list($year, $month, $mday) = sscanf(
|
|
end($completions),
|
|
'%04d%02d%02d'
|
|
);
|
|
$lastCompletion = new Horde_Date($year, $month, $mday);
|
|
$recurrence = clone $t->recurrence;
|
|
$recurrence->start = new Horde_Date($start);
|
|
$start = $recurrence
|
|
->nextRecurrence($lastCompletion)
|
|
->timestamp();
|
|
if ($start > $_SERVER['REQUEST_TIME']) {
|
|
continue 2;
|
|
}
|
|
}
|
|
break;
|
|
case Nag::VIEW_COMPLETE:
|
|
if (!$complete) {
|
|
continue 2;
|
|
}
|
|
break;
|
|
case Nag::VIEW_FUTURE:
|
|
if ($complete || $start == 0) {
|
|
continue 2;
|
|
}
|
|
if ($start && $t->recurs() &&
|
|
($completions = $t->recurrence->getCompletions())) {
|
|
sort($completions);
|
|
list($year, $month, $mday) = sscanf(
|
|
end($completions),
|
|
'%04d%02d%02d'
|
|
);
|
|
$lastCompletion = new Horde_Date($year, $month, $mday);
|
|
$recurrence = clone $t->recurrence;
|
|
$recurrence->start = new Horde_Date($start);
|
|
$start = $recurrence
|
|
->nextRecurrence($lastCompletion)
|
|
->timestamp();
|
|
if ($start < $_SERVER['REQUEST_TIME']) {
|
|
continue 2;
|
|
}
|
|
}
|
|
break;
|
|
case Nag::VIEW_FUTURE_INCOMPLETE:
|
|
if ($complete) {
|
|
continue 2;
|
|
}
|
|
break;
|
|
}
|
|
|
|
if (empty($t->parent_id)) {
|
|
$this->tasks->add($t);
|
|
} else {
|
|
$dict[$tid] = $t;
|
|
}
|
|
}
|
|
|
|
/* Build a tree from the subtasks. */
|
|
foreach (array_keys($dict) as $key) {
|
|
$task = $this->tasks->get($dict[$key]->parent_id);
|
|
if ($task) {
|
|
$task->add($dict[$key]);
|
|
} elseif (isset($dict[$dict[$key]->parent_id])) {
|
|
$dict[$dict[$key]->parent_id]->add($dict[$key]);
|
|
} else {
|
|
$this->tasks->add($dict[$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Lists all alarms near $date.
|
|
*
|
|
* @param integer $date The unix epoch time to check for alarms.
|
|
*
|
|
* @return array An array of Nag_Task objects that have alarms that match.
|
|
*/
|
|
public function listAlarms($date)
|
|
{
|
|
$task_list = $this->_getData()->getObjects();
|
|
if (empty($task_list)) {
|
|
return array();
|
|
}
|
|
|
|
$tasks = array();
|
|
foreach ($task_list as $task) {
|
|
$t = new Nag_Task($this, $this->_buildTask($task));
|
|
if ($t->alarm && $t->due &&
|
|
$t->due - $t->alarm * 60 < $date) {
|
|
$tasks[] = $t;
|
|
}
|
|
}
|
|
|
|
return $tasks;
|
|
}
|
|
|
|
/**
|
|
* Retrieves sub-tasks from the database.
|
|
*
|
|
* @param string $parentId The parent id for the sub-tasks to
|
|
* retrieve.
|
|
* @param boolean $include_history Include created/modified info? Not
|
|
* currently honored.
|
|
*
|
|
* @return array List of sub-tasks.
|
|
* @throws Nag_Exception
|
|
*/
|
|
public function getChildren($parentId, $include_history = true)
|
|
{
|
|
$task_list = $this->_getData()->getObjects();
|
|
if (empty($task_list)) {
|
|
return array();
|
|
}
|
|
|
|
$tasks = array();
|
|
|
|
foreach ($task_list as $task) {
|
|
if (Horde_Url::uriB64Encode($task['parent']) != $parentId) {
|
|
continue;
|
|
}
|
|
$t = new Nag_Task($this, $this->_buildTask($task));
|
|
$children = $this->getChildren($t->id);
|
|
$t->mergeChildren($children);
|
|
$tasks[] = $t;
|
|
}
|
|
|
|
return $tasks;
|
|
}
|
|
}
|