Files
server/usr/share/psa-horde/nag/lib/Driver.php
2026-01-07 20:52:11 +01:00

551 lines
19 KiB
PHP

<?php
/**
* Nag_Driver:: defines an API for implementing storage backends for Nag.
*
* See the enclosed file COPYING for license information (GPL). If you
* did not receive this file, see http://www.horde.org/licenses/gpl.
*
* @author Jon Parise <jon@horde.org>
* @author Jan Schneider <jan@horde.org>
* @package Nag
*/
abstract class Nag_Driver
{
/**
* A Nag_Task instance holding the current task list.
*
* @var Nag_Task
*/
public $tasks;
/**
* String containing the current tasklist.
*
* @var string
*/
protected $_tasklist = '';
/**
* Hash containing connection parameters.
*
* @var array
*/
protected $_params = array();
/**
* An error message to throw when something is wrong.
*
* @var string
*/
protected $_errormsg;
/**
* Constructor - just store the $params in our newly-created
* object. All other work is done by initialize().
*
* @param array $params Any parameters needed for this driver.
* @param string $errormsg Custom error message
*
* @return Nag_Driver
*/
public function __construct(array $params = array(), $errormsg = null)
{
$this->tasks = new Nag_Task();
$this->_params = $params;
if (is_null($errormsg)) {
$this->_errormsg = _("The Tasks backend is not currently available.");
} else {
$this->_errormsg = $errormsg;
}
}
/**
* List all alarms near $date.
*
* @param integer $date The unix epoch time to check for alarms.
*
* @return array An array of tasks that have alarms that match.
*/
public function listAlarms($date)
{
if (!$this->tasks->count()) {
$result = $this->retrieve(0);
}
$alarms = array();
$this->tasks->reset();
while ($task = $this->tasks->each()) {
if ($task->alarm &&
($due = $task->getNextDue()) &&
($due->timestamp() - ($task->alarm * 60)) <= $date) {
$alarms[$task_id] = $task;
}
}
return $alarms;
}
/**
* Adds a task and handles notification.
*
* @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 comma delimited list of tags.
* - 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 array(ID,UID) of new task
*/
public function add(array $task)
{
$task = array_merge(
array('start' => 0,
'due' => 0,
'priority' => 3,
'estimate' => 0.0,
'completed' => 0,
'tags' => '',
'alarm' => 0,
'methods' => null,
'uid' => strval(new Horde_Support_Guid()),
'parent' => '',
'private' => false,
'owner' => $GLOBALS['registry']->getAuth(),
'assignee' => null,
'completed_date' => 0,
'recurrence' => null),
$task
);
$taskId = $this->_add($task);
$task = $this->get($taskId);
$task->process();
/* Log the creation of this item in the history log. */
$history = $GLOBALS['injector']->getInstance('Horde_History');
try {
$history->log('nag:' . $this->_tasklist . ':' . $task->uid,
array('action' => 'add'),
true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
/* Log completion status changes. */
if ($task->completed) {
try {
$history->log('nag:' . $this->_tasklist . ':' . $task->uid,
array('action' => 'complete'),
true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
}
/* Notify users about the new event. */
$result = Nag::sendNotification('add', $task);
/* Add an alarm if necessary. */
if (!empty($task->due) && !empty($task->alarm) &&
($alarm = $task->toAlarm())) {
$GLOBALS['injector']->getInstance('Horde_Alarm')->set($alarm);
}
return array($taskId, $task->uid);
}
/**
* @see add()
*/
abstract protected function _add(array $task);
/**
* Modifies an existing task and handles notification.
*
* @param string $taskId The task to modify.
* @param array $properties 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.
* - tasklist: (OPTIONAL, string) The new tasklist.
* - recurrence: (OPTIONAL, Horde_Date_Recurrence|array) Recurrence
* information.
*
* @throws Nag_Exception
*/
public function modify($taskId, array $properties)
{
/* Retrieve unmodified task. */
$task = $this->get($taskId);
/* Avoid circular reference. */
if (isset($properties['parent']) &&
$properties['parent'] == $taskId) {
unset($properties['parent']);
}
/* Toggle completion. We cannot simply set the completion flag
* because this might be a recurring task, and marking the
* task complete might only shift the due date to the next
* recurrence. */
$task_completed = $task->completed;
$completed_changed = isset($properties['completed']) &&
$properties['completed'] != $task->completed;
$new_completed = !empty($properties['completed']);
if ($completed_changed) {
if (isset($properties['recurrence'])) {
if ($task->recurs()) {
$completions = $task->recurrence->completions;
$exceptions = $task->recurrence->exceptions;
} else {
$completions = $exceptions = array();
}
$task->recurrence = $properties['recurrence'];
$task->recurrence->completions = $completions;
$task->recurrence->exceptions = $exceptions;
unset($properties['recurrence']);
}
$task->toggleComplete();
unset($properties['completed']);
}
$this->_modify($taskId, array_merge($task->toHash(), $properties));
// Must persist the original completed value so we can detect the
// change when notifications are sent.
$task->completed = $task_completed;
$new_task = $this->get($task->id);
$log_tasklist = $this->_tasklist;
if (isset($properties['tasklist']) &&
$task->tasklist != $properties['tasklist']) {
/* Moving the task to another tasklist. */
try {
$share = $GLOBALS['nag_shares']->getShare($task->tasklist);
} catch (Horde_Share_Exception $e) {
Horde::log($e->getMessage(), 'ERR');
throw new Nag_Exception($e);
}
if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::DELETE)) {
$GLOBALS['notification']->push(_("Access denied removing task from this task list."), 'horde.error');
return false;
}
try {
$share = $GLOBALS['nag_shares']->getShare($properties['tasklist']);
} catch (Horde_Share_Exception $e) {
Horde::log($e->getMessage(), 'ERR');
throw new Nag_Exception($e);
}
if (!$share->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::EDIT)) {
$GLOBALS['notification']->push(_("Access denied moving the task to this task list."), 'horde.error');
}
$moved = $this->_move($task->id, $properties['tasklist']);
$new_storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($properties['tasklist']);
$new_task = $new_storage->get($task->id);
/* Log the moving of this item in the history log. */
if (!empty($task->uid)) {
$history = $GLOBALS['injector']->getInstance('Horde_History');
try {
$history->log('nag:' . $task->tasklist . ':' . $task->uid, array('action' => 'delete'), true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
try {
$history->log('nag:' . $properties['tasklist'] . ':' . $task->uid, array('action' => 'add'), true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
$log_tasklist = $properties['tasklist'];
}
$task->loadChildren();
if ($task->hasSubTasks()) {
foreach ($task->children as $child_task) {
$child_task = $child_task->toHash();
$child_task['tasklist'] = $properties['tasklist'];
$this->modify($child_task['task_id'], $child_task);
}
}
}
/* Update alarm if necessary. */
$horde_alarm = $GLOBALS['injector']->getInstance('Horde_Alarm');
if ((isset($properties['alarm']) && empty($properties['alarm'])) ||
$new_completed || empty($task->due)) {
$horde_alarm->delete($task->uid);
} else {
$task = $this->get($taskId);
$task->process();
if (!$task->recurs()) {
$alarm = $task->toAlarm();
} else {
$alarm = null;
/* Get current occurrence (task due date) */
$current = $task->recurrence->nextActiveRecurrence(new Horde_Date($task->due));
if ($current) {
/* Advance this occurence by a day to indicate that we
* want the following occurence (Recurrence uses days
* as minimal time duration between occurrences). */
$current->mday++;
/* Only mark this due date completed if there is another
* occurence. */
if ($next = $task->recurrence->nextActiveRecurrence($current)) {
$alarm = $task->toAlarm();
if ($alarm) {
$alarm['start'] = new Horde_Date($next);
$horde_alarm->set($alarm);
}
}
}
}
if ($alarm) {
$alarm['start'] = new Horde_Date($alarm['start']);
$horde_alarm->set($alarm);
}
}
/* Log the modification of this item in the history log. */
if (!empty($task->uid)) {
try {
$GLOBALS['injector']->getInstance('Horde_History')
->log('nag:' . $log_tasklist . ':' . $task->uid,
array('action' => 'modify'),
true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
}
/* Log completion status changes. */
if ($completed_changed) {
$attributes = array('action' => 'complete');
if (!$new_completed) {
$attributes['ts'] = 0;
}
try {
$GLOBALS['injector']->getInstance('Horde_History')
->log('nag:' . $log_tasklist . ':' . $task->uid,
$attributes,
true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
}
/* Notify users about the changed event. */
try {
$result = Nag::sendNotification('edit', $new_task, $task);
} catch (Nag_Exception $e) {
Horde::log($e, 'ERR');
}
}
/**
* @see modify()
*/
abstract protected function _modify($taskId, array $task);
/**
* Deletes a task and handles notification.
*
* @param string $taskId The task to delete.
*/
public function delete($taskId)
{
/* Get the task's details for use later. */
$task = $this->get($taskId);
$task->loadChildren();
if ($task->hasSubTasks()) {
throw new Nag_Exception(_("Sub tasks exist, delete them first"));
}
$delete = $this->_delete($taskId);
/* Remove tags */
$task->tags = array();
$this->_updateTags($task->toHash());
/* Tell content we removed the object */
$GLOBALS['injector']->getInstance('Content_Objects_Manager')
->delete(array($task->uid), 'task');
/* Log the deletion of this item in the history log. */
if (!empty($task->uid)) {
try {
$GLOBALS['injector']->getInstance('Horde_History')->log('nag:' . $this->_tasklist . ':' . $task->uid, array('action' => 'delete'), true);
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
}
/* Notify users about the deleted event. */
try {
$result = Nag::sendNotification('delete', $task);
} catch (Nag_Exception $e) {
Horde::log($e, 'ERR');
}
/* Delete alarm if necessary. */
if (!empty($task->alarm)) {
$GLOBALS['injector']->getInstance('Horde_Alarm')->delete($task->uid);
}
/* Remove any CalDAV mappings. */
try {
$davStorage = $GLOBALS['injector']
->getInstance('Horde_Dav_Storage');
try {
$davStorage
->deleteInternalObjectId($task->id, $task->tasklist);
} catch (Horde_Exception $e) {
Horde::log($e);
}
} catch (Horde_Exception $e) {
}
}
/**
* Deletes all tasks for the current task list.
*
* @throws Nag_Exception
*/
public function deleteAll()
{
$ids = $this->_deleteAll();
// Update History and Tagger
$history = $GLOBALS['injector']->getInstance('Horde_History');
try {
foreach ($ids as $uid) {
$history->log(
'nag:' . $this->_tasklist . ':' . $uid,
array('action' => 'delete'),
true);
$GLOBALS['injector']->getInstance('Nag_Tagger')
->replaceTags($uid, array(), $GLOBALS['registry']->getAuth(), 'task');
/* Tell content we removed the object */
$GLOBALS['injector']->getInstance('Content_Objects_Manager')
->delete(array($uid), 'task');
}
} catch (Exception $e) {
Horde::log($e, 'ERR');
}
}
/**
* Retrieves tasks from the database.
*
* @throws Nag_Exception
*/
public function retrieve()
{
throw new Nag_Exception($this->_errormsg);
}
/**
* 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?
*
* @return array List of sub-tasks.
* @throws Nag_Exception
*/
public function getChildren($parentId, $include_history = true)
{
throw new Nag_Exception($this->_errormsg);
}
/**
* Retrieves one task from the database.
*
* @param string $taskId The id of the task to retrieve.
*
* @return Nag_Task A Nag_Task object.
* @throws Nag_Exception
*/
public function get($taskId)
{
throw new Nag_Exception($this->_errormsg);
}
/**
* Retrieves one task from the database by UID.
*
* @param string $uid The UID of the task to retrieve.
*
* @return Nag_Task A Nag_Task object.
* @throws Nag_Exception
*/
public function getByUID($uid)
{
throw new Nag_Exception($this->_errormsg);
}
/**
* Helper function to update an existing event's tags to tagger storage.
*
* @param array $task The task to update
*/
protected function _updateTags(array $task)
{
$GLOBALS['injector']->getInstance('Nag_Tagger')->replaceTags(
$task['uid'],
$task['tags'],
$task['owner'],
'task'
);
}
/**
* Helper function to add tags from a newly created event to the tagger.
*
* @param array $task The task to save tags to storage for.
*/
protected function _addTags(array $task)
{
$GLOBALS['injector']->getInstance('Nag_Tagger')->tag(
$task['uid'],
$task['tags'],
$task['owner'],
'task'
);
}
}