1734 lines
58 KiB
PHP
1734 lines
58 KiB
PHP
<?php
|
|
/**
|
|
* Nag Base Class.
|
|
*
|
|
* 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 Chuck Hagenbuch <chuck@horde.org>
|
|
* @author Jan Schneider <jan@horde.org>
|
|
* @package Nag
|
|
*/
|
|
class Nag
|
|
{
|
|
/**
|
|
* Sort by task name.
|
|
*/
|
|
const SORT_NAME = 'name';
|
|
|
|
/**
|
|
* Sort by priority.
|
|
*/
|
|
const SORT_PRIORITY = 'priority';
|
|
|
|
/**
|
|
* Sort by due date.
|
|
*/
|
|
const SORT_DUE = 'due';
|
|
|
|
/**
|
|
* Sort by start date.
|
|
*/
|
|
const SORT_START = 'start';
|
|
|
|
/**
|
|
* Sort by completion.
|
|
*/
|
|
const SORT_COMPLETION = 'completed';
|
|
|
|
/**
|
|
* Sort by owner.
|
|
*/
|
|
const SORT_OWNER = 'tasklist';
|
|
|
|
/**
|
|
* Sort by estimate.
|
|
*/
|
|
const SORT_ESTIMATE = 'estimate';
|
|
|
|
/**
|
|
* Sort by assignee.
|
|
*/
|
|
const SORT_ASSIGNEE = 'assignee';
|
|
|
|
/**
|
|
* Sort in ascending order.
|
|
*/
|
|
const SORT_ASCEND = 0;
|
|
|
|
/**
|
|
* Sort in descending order.
|
|
*/
|
|
const SORT_DESCEND = 1;
|
|
|
|
/**
|
|
* Incomplete tasks
|
|
*/
|
|
const VIEW_INCOMPLETE = 0;
|
|
|
|
/**
|
|
* All tasks
|
|
*/
|
|
const VIEW_ALL = 1;
|
|
|
|
/**
|
|
* Complete tasks
|
|
*/
|
|
const VIEW_COMPLETE = 2;
|
|
|
|
/**
|
|
* Future tasks
|
|
*/
|
|
const VIEW_FUTURE = 3;
|
|
|
|
/**
|
|
* Future and incompleted tasks
|
|
*/
|
|
const VIEW_FUTURE_INCOMPLETE = 4;
|
|
|
|
/**
|
|
* WebDAV task list.
|
|
*/
|
|
const DAV_WEBDAV = 1;
|
|
|
|
/**
|
|
* CalDAV task list.
|
|
*/
|
|
const DAV_CALDAV = 2;
|
|
|
|
/**
|
|
* CalDAV principal.
|
|
*/
|
|
const DAV_ACCOUNT = 3;
|
|
|
|
/**
|
|
*
|
|
* @param integer $seconds
|
|
*
|
|
* @return string
|
|
*/
|
|
static public function secondsToString($seconds)
|
|
{
|
|
$hours = floor($seconds / 3600);
|
|
$minutes = ($seconds / 60) % 60;
|
|
|
|
if ($hours > 1) {
|
|
if ($minutes == 0) {
|
|
return sprintf(_("%d hours"), $hours);
|
|
} elseif ($minutes == 1) {
|
|
return sprintf(_("%d hours, %d minute"), $hours, $minutes);
|
|
} else {
|
|
return sprintf(_("%d hours, %d minutes"), $hours, $minutes);
|
|
}
|
|
} elseif ($hours == 1) {
|
|
if ($minutes == 0) {
|
|
return sprintf(_("%d hour"), $hours);
|
|
} elseif ($minutes == 1) {
|
|
return sprintf(_("%d hour, %d minute"), $hours, $minutes);
|
|
} else {
|
|
return sprintf(_("%d hour, %d minutes"), $hours, $minutes);
|
|
}
|
|
} else {
|
|
if ($minutes == 0) {
|
|
return _("no time");
|
|
} elseif ($minutes == 1) {
|
|
return sprintf(_("%d minute"), $minutes);
|
|
} else {
|
|
return sprintf(_("%d minutes"), $minutes);
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Parses a complete date-time string into a Horde_Date object.
|
|
*
|
|
* @param string $date The date-time string to parse.
|
|
* @param boolean $withtime Whether time is included in the string.
|
|
*
|
|
* @return Horde_Date The parsed date.
|
|
* @throws Horde_Date_Exception
|
|
*/
|
|
static public function parseDate($date, $withtime = true)
|
|
{
|
|
// strptime() is not available on Windows.
|
|
if (!function_exists('strptime')) {
|
|
return new Horde_Date($date);
|
|
}
|
|
|
|
// strptime() is locale dependent, i.e. %p is not always matching
|
|
// AM/PM. Set the locale to C to workaround this, but grab the
|
|
// locale's D_FMT before that.
|
|
$format = $GLOBALS['prefs']->getValue('date_format_mini');
|
|
if ($withtime) {
|
|
$format .= ' '
|
|
. ($GLOBALS['prefs']->getValue('twentyFour') ? '%H:%M' : '%I:%M %p');
|
|
}
|
|
$old_locale = setlocale(LC_TIME, 0);
|
|
setlocale(LC_TIME, 'C');
|
|
|
|
// Try exact format match first.
|
|
$date_arr = strptime($date, $format);
|
|
setlocale(LC_TIME, $old_locale);
|
|
|
|
if (!$date_arr) {
|
|
// Try with locale dependent parsing next.
|
|
$date_arr = strptime($date, $format);
|
|
if (!$date_arr) {
|
|
// Try throwing at Horde_Date finally.
|
|
return new Horde_Date($date);
|
|
}
|
|
}
|
|
|
|
return new Horde_Date(
|
|
array('year' => $date_arr['tm_year'] + 1900,
|
|
'month' => $date_arr['tm_mon'] + 1,
|
|
'mday' => $date_arr['tm_mday'],
|
|
'hour' => $date_arr['tm_hour'],
|
|
'min' => $date_arr['tm_min'],
|
|
'sec' => $date_arr['tm_sec']));
|
|
}
|
|
|
|
/**
|
|
* Retrieves the current user's task list from storage.
|
|
*
|
|
* This function will also sort the resulting list, if requested.
|
|
*
|
|
* @param arary $options Options array:
|
|
* - altsortby: (string) The secondary sort field. Same values as sortdir.
|
|
* DEFAULT: altsortby pref is used.
|
|
* - completed: (integer) Which task to retrieve. A Nag::VIEW_* constant.
|
|
* DEFAULT: show_completed pref is used.
|
|
* - external: (boolean) Whether to include tasks from other applications
|
|
* too.
|
|
* DEFAULT: true.
|
|
* - include_history: (boolean) Autoload created/modified data from
|
|
* Horde_History.
|
|
* DEFAULT: true (Automatically load history data).
|
|
* - include_tags: (boolean) Autoload all tags.
|
|
* DEFAULT: false (Tags are lazy loaded as needed.)
|
|
* - sortby: (string) A Nag::SORT_* constant for the field to sort by.
|
|
* DEFAULT: sortby pref is used.
|
|
* - sortdir: (string) Direction of sort. NAG::SORT_ASCEND or
|
|
* NAG::SORT_DESCEND.
|
|
* DEFAULT: sortdir pref is used.
|
|
* - tasklists: (array) An array of tasklists to include.
|
|
* DEFAULT: Use $GLOBALS['display_tasklists'];
|
|
*
|
|
* @return Nag_Task A list of the requested tasks.
|
|
*/
|
|
static public function listTasks(array $options = array())
|
|
{
|
|
global $prefs, $registry;
|
|
|
|
// Prevent null tasklists value from obscuring the default value.
|
|
if (array_key_exists('tasklists', $options) && empty($options['tasklists'])) {
|
|
unset($options['tasklists']);
|
|
}
|
|
|
|
$options = array_merge(
|
|
array(
|
|
'sortby' => $prefs->getValue('sortby'),
|
|
'sortdir' => $prefs->getValue('sortdir'),
|
|
'altsortby' => $prefs->getValue('altsortby'),
|
|
'tasklists' => $GLOBALS['display_tasklists'],
|
|
'completed' => $prefs->getValue('show_completed'),
|
|
'include_tags' => false,
|
|
'external' => true,
|
|
'include_history' => true
|
|
),
|
|
$options
|
|
);
|
|
|
|
if (!is_array($options['tasklists'])) {
|
|
$options['tasklists'] = array($options['tasklists']);
|
|
}
|
|
$tasks = new Nag_Task();
|
|
foreach ($options['tasklists'] as $tasklist) {
|
|
$storage = $GLOBALS['injector']
|
|
->getInstance('Nag_Factory_Driver')
|
|
->create($tasklist);
|
|
|
|
// Retrieve the tasklist from storage.
|
|
$storage->retrieve($options['completed'], $options['include_history']);
|
|
$tasks->mergeChildren($storage->tasks->children);
|
|
}
|
|
|
|
// Process all tasks.
|
|
$tasks->process();
|
|
|
|
if ($options['external'] &&
|
|
($apps = @unserialize($prefs->getValue('show_external'))) &&
|
|
is_array($apps)) {
|
|
foreach ($apps as $app) {
|
|
// We look for registered apis that support listAs(taskHash).
|
|
if ($app == 'nag' ||
|
|
!$registry->hasMethod('getListTypes', $app)) {
|
|
continue;
|
|
}
|
|
try {
|
|
$types = $registry->callByPackage($app, 'getListTypes');
|
|
} catch (Horde_Exception $e) {
|
|
continue;
|
|
}
|
|
if (empty($types['taskHash'])) {
|
|
continue;
|
|
}
|
|
|
|
try {
|
|
$newtasks = $registry->callByPackage($app, 'listAs', array('taskHash'));
|
|
foreach ($newtasks as $task) {
|
|
if (!isset($task['priority'])) {
|
|
$task['priority'] = 3;
|
|
}
|
|
$task['tasklist_id'] = '**EXTERNAL**';
|
|
$task['tasklist_name'] = $registry->get('name', $app);
|
|
$task = new Nag_Task(null, $task);
|
|
if (($options['completed'] == Nag::VIEW_INCOMPLETE &&
|
|
($task->completed ||
|
|
$task->start > $_SERVER['REQUEST_TIME'])) ||
|
|
($options['completed'] == Nag::VIEW_COMPLETE &&
|
|
!$task->completed) ||
|
|
($options['completed'] == Nag::VIEW_FUTURE &&
|
|
($task->completed ||
|
|
!$task->start ||
|
|
$task->start < $_SERVER['REQUEST_TIME'])) ||
|
|
($options['completed'] == Nag::VIEW_FUTURE_INCOMPLETE &&
|
|
$task->completed)) {
|
|
continue;
|
|
}
|
|
$tasks->add($task);
|
|
}
|
|
} catch (Horde_Exception $e) {
|
|
Horde::log($e);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Sort the array.
|
|
$tasks->sort($options['sortby'], $options['sortdir'], $options['altsortby']);
|
|
|
|
// Preload tags if requested.
|
|
if ($options['include_tags']) {
|
|
$tasks->loadTags();
|
|
}
|
|
|
|
return $tasks;
|
|
}
|
|
|
|
/**
|
|
* Returns a single task.
|
|
*
|
|
* @param string $tasklist A tasklist.
|
|
* @param string $task A task id.
|
|
*
|
|
* @return Nag_Task The task hash.
|
|
*/
|
|
static public function getTask($tasklist, $task)
|
|
{
|
|
$storage = $GLOBALS['injector']
|
|
->getInstance('Nag_Factory_Driver')
|
|
->create($tasklist);
|
|
$task = $storage->get($task);
|
|
$task->process();
|
|
return $task;
|
|
}
|
|
|
|
/**
|
|
* Returns the number of taks in task lists that the current user owns.
|
|
*
|
|
* @return integer The number of tasks that the user owns.
|
|
*/
|
|
static public function countTasks()
|
|
{
|
|
static $count;
|
|
if (isset($count)) {
|
|
return $count;
|
|
}
|
|
|
|
$tasklists = self::listTasklists(true, Horde_Perms::ALL);
|
|
|
|
$count = 0;
|
|
foreach (array_keys($tasklists) as $tasklist) {
|
|
/* Create a Nag storage instance. */
|
|
$storage = $GLOBALS['injector']
|
|
->getInstance('Nag_Factory_Driver')
|
|
->create($tasklist);
|
|
$storage->retrieve();
|
|
|
|
/* Retrieve the task list from storage. */
|
|
$count += $storage->tasks->count();
|
|
}
|
|
|
|
return $count;
|
|
}
|
|
|
|
/**
|
|
* Imports one or more tasks parsed from a string.
|
|
*
|
|
* @param string $text The text to parse into
|
|
* @param string $tasklist The tasklist into which the task will be
|
|
* imported. If 'null', the user's default
|
|
* tasklist will be used.
|
|
*
|
|
* @return array The UIDs of all tasks that were added.
|
|
*/
|
|
static public function createTasksFromText($text, $tasklist = null)
|
|
{
|
|
if ($tasklist === null) {
|
|
$tasklist = self::getDefaultTasklist(Horde_Perms::EDIT);
|
|
} elseif (!self::hasPermission($tasklist, Horde_Perms::EDIT)) {
|
|
throw new Horde_Exception_PermissionDenied();
|
|
}
|
|
|
|
$storage = $GLOBALS['injector']
|
|
->getInstance('Nag_Factory_Driver')
|
|
->create($tasklist);
|
|
$dateParser = Horde_Date_Parser::factory(
|
|
array('locale' => $GLOBALS['prefs']->getValue('language')) );
|
|
|
|
$quickParser = new Nag_QuickParser();
|
|
$tasks = $quickParser->parse($text);
|
|
|
|
$uids = array();
|
|
foreach ($tasks as &$task) {
|
|
if (!is_array($task)) {
|
|
$name = $task;
|
|
$task = array($name);
|
|
}
|
|
|
|
$r = $dateParser->parse($task[0], array('return' => 'result'));
|
|
if ($d = $r->guess()) {
|
|
$name = $r->untaggedText();
|
|
$due = $d->timestamp();
|
|
} else {
|
|
$name = $task[0];
|
|
$due = 0;
|
|
}
|
|
|
|
// Look for tags to be added in the text.
|
|
$pattern = '/#\w+/';
|
|
$tags = array();
|
|
if (preg_match_all($pattern, $name, $results)) {
|
|
$tags = $results[0];
|
|
$name = str_replace($tags, '', $name);
|
|
$tags = array_map(function($x) { return substr($x, -(strlen($x) - 1)); }, $tags);
|
|
} else {
|
|
$tags = '';
|
|
}
|
|
|
|
if (isset($task['parent'])) {
|
|
$newTask = $storage->add(array('name' => $name, 'due' => $due, 'parent' => $tasks[$task['parent']]['id'], 'tags' => $tags));
|
|
} else {
|
|
$newTask = $storage->add(array('name' => $name, 'due' => $due, 'tags' => $tags));
|
|
}
|
|
$uids[] = $newTask[1];
|
|
$task['id'] = $newTask[0];
|
|
}
|
|
|
|
return $uids;
|
|
}
|
|
|
|
/**
|
|
* Returns all the alarms active right on $date.
|
|
*
|
|
* @param integer $date The unix epoch time to check for alarms.
|
|
* @param array $tasklists An array of tasklists
|
|
*
|
|
* @return array An array of Nag_Task objects with alarms active on $date.
|
|
*/
|
|
static public function listAlarms($date, array $tasklists = null)
|
|
{
|
|
if (is_null($tasklists)) {
|
|
$tasklists = $GLOBALS['display_tasklists'];
|
|
}
|
|
|
|
$tasks = array();
|
|
foreach ($tasklists as $tasklist) {
|
|
/* Create a Nag storage instance. */
|
|
$storage = $GLOBALS['injector']
|
|
->getInstance('Nag_Factory_Driver')
|
|
->create($tasklist);
|
|
|
|
/* Retrieve the alarms for the task list. */
|
|
$newtasks = $storage->listAlarms($date);
|
|
|
|
/* Don't show an alarm for complete tasks. */
|
|
foreach ($newtasks as $taskID => $task) {
|
|
if (!empty($task->completed)) {
|
|
unset($newtasks[$taskID]);
|
|
}
|
|
}
|
|
|
|
$tasks = array_merge($tasks, $newtasks);
|
|
}
|
|
|
|
return $tasks;
|
|
}
|
|
|
|
/**
|
|
* Lists all task lists a user has access to.
|
|
*
|
|
* @param boolean $owneronly Only return task lists that this user owns?
|
|
* Defaults to false.
|
|
* @param integer $permission The permission to filter task lists by.
|
|
* @param boolean $smart Include SmartLists in the results.
|
|
*
|
|
* @return array The task lists.
|
|
*/
|
|
static public function listTasklists($owneronly = false,
|
|
$permission = Horde_Perms::SHOW,
|
|
$smart = true)
|
|
{
|
|
if ($owneronly && !$GLOBALS['registry']->getAuth()) {
|
|
return array();
|
|
}
|
|
$att = array();
|
|
if ($owneronly) {
|
|
$att = array('owner' => $GLOBALS['registry']->getAuth());
|
|
}
|
|
if (!$smart) {
|
|
$att['issmart'] = 0;
|
|
}
|
|
|
|
try {
|
|
$tasklists = $GLOBALS['nag_shares']->listShares(
|
|
$GLOBALS['registry']->getAuth(),
|
|
array('perm' => $permission,
|
|
'attributes' => $att,
|
|
'sort_by' => 'name'));
|
|
|
|
// Must explicitly add system shares if we are an admin since
|
|
// it's possible for an admin user to have no explicit perms
|
|
// added to the share.
|
|
if ($GLOBALS['registry']->isAdmin()) {
|
|
$tasklists = array_merge(
|
|
$tasklists,
|
|
$GLOBALS['nag_shares']->listSystemShares()
|
|
);
|
|
}
|
|
} catch (Horde_Share_Exception $e) {
|
|
Horde::log($e->getMessage(), 'ERR');
|
|
return array();
|
|
}
|
|
|
|
if ($owneronly) {
|
|
return $tasklists;
|
|
}
|
|
|
|
$display_tasklists = @unserialize($GLOBALS['prefs']->getValue('display_tasklists'));
|
|
if (is_array($display_tasklists)) {
|
|
foreach ($display_tasklists as $id) {
|
|
try {
|
|
$tasklist = $GLOBALS['nag_shares']->getShare($id);
|
|
if ($tasklist->hasPermission($GLOBALS['registry']->getAuth(), $permission)) {
|
|
$tasklists[$id] = $tasklist;
|
|
}
|
|
} catch (Horde_Exception_NotFound $e) {
|
|
} catch (Horde_Share_Exception $e) {
|
|
Horde::log($e);
|
|
return array();
|
|
}
|
|
}
|
|
}
|
|
|
|
return $tasklists;
|
|
}
|
|
|
|
/**
|
|
* Returns whether the current user has certain permissions on a tasklist.
|
|
*
|
|
* @param string $tasklist A tasklist id.
|
|
* @param integer $perm A Horde_Perms permission mask.
|
|
*
|
|
* @return boolean True if the current user has the requested permissions.
|
|
*/
|
|
static public function hasPermission($tasklist, $perm)
|
|
{
|
|
try {
|
|
$share = $GLOBALS['nag_shares']->getShare($tasklist);
|
|
if (!$share->hasPermission($GLOBALS['registry']->getAuth(), $perm)) {
|
|
throw new Horde_Exception_NotFound();
|
|
}
|
|
} catch (Horde_Exception_NotFound $e) {
|
|
return false;
|
|
}
|
|
return true;
|
|
}
|
|
|
|
/**
|
|
* Returns the default tasklist for the current user at the specified
|
|
* permissions level.
|
|
*
|
|
* @param integer $permission Horde_Perms constant for permission level
|
|
* required.
|
|
*
|
|
* @return string The default tasklist or null if none.
|
|
*/
|
|
static public function getDefaultTasklist($permission = Horde_Perms::SHOW)
|
|
{
|
|
$tasklists = self::listTasklists(false, $permission);
|
|
|
|
$default_tasklist = $GLOBALS['prefs']->getValue('default_tasklist');
|
|
if (isset($tasklists[$default_tasklist])) {
|
|
return $default_tasklist;
|
|
}
|
|
|
|
$default_tasklist = $GLOBALS['injector']
|
|
->getInstance('Nag_Factory_Tasklists')
|
|
->create()
|
|
->getDefaultShare();
|
|
|
|
if (!isset($tasklists[$default_tasklist])) {
|
|
reset($tasklists);
|
|
$default_tasklist = key($tasklists);
|
|
}
|
|
|
|
$GLOBALS['prefs']->setValue('default_tasklist', $default_tasklist);
|
|
return $default_tasklist;
|
|
}
|
|
|
|
/**
|
|
* Creates a new share.
|
|
*
|
|
* @param array $info Hash with tasklist information.
|
|
* @param boolean $display Add the new tasklist to display_tasklists
|
|
*
|
|
* @return Horde_Share The new share.
|
|
*/
|
|
static public function addTasklist(array $info, $display = true)
|
|
{
|
|
try {
|
|
$tasklist = $GLOBALS['nag_shares']->newShare(
|
|
$GLOBALS['registry']->getAuth(),
|
|
strval(new Horde_Support_Randomid()), $info['name']);
|
|
$tasklist->set('color', $info['color']);
|
|
$tasklist->set('desc', $info['description']);
|
|
if (!empty($info['system'])) {
|
|
$tasklist->set('owner', null);
|
|
}
|
|
|
|
// Smartlist
|
|
if (!empty($info['search'])) {
|
|
$tasklist->set('search', $info['search']);
|
|
$tasklist->set('issmart', 1);
|
|
}
|
|
|
|
$GLOBALS['nag_shares']->addShare($tasklist);
|
|
} catch (Horde_Share_Exception $e) {
|
|
throw new Nag_Exception($e);
|
|
}
|
|
|
|
if ($display) {
|
|
$GLOBALS['display_tasklists'][] = $tasklist->getName();
|
|
$GLOBALS['prefs']->setValue('display_tasklists', serialize($GLOBALS['display_tasklists']));
|
|
}
|
|
|
|
return $tasklist;
|
|
}
|
|
|
|
/**
|
|
* Updates an existing share.
|
|
*
|
|
* @param Horde_Share_Object $tasklist The share to update.
|
|
* @param array $info Hash with task list information.
|
|
*
|
|
* @throws Horde_Exception_PermissionDenied
|
|
* @throws Nag_Exception
|
|
*/
|
|
static public function updateTasklist(Horde_Share_Object $tasklist, array $info)
|
|
{
|
|
if (!$GLOBALS['registry']->getAuth() ||
|
|
($tasklist->get('owner') != $GLOBALS['registry']->getAuth() &&
|
|
(!is_null($tasklist->get('owner')) || !$GLOBALS['registry']->isAdmin()))) {
|
|
|
|
throw new Horde_Exception_PermissionDenied(_("You are not allowed to change this task list."));
|
|
}
|
|
|
|
$tasklist->set('name', $info['name']);
|
|
$tasklist->set('color', $info['color']);
|
|
$tasklist->set('desc', $info['description']);
|
|
$tasklist->set('owner', empty($info['system']) ? $GLOBALS['registry']->getAuth() : null);
|
|
if ($tasklist->get('issmart')) {
|
|
if (empty($info['search'])) {
|
|
throw new Nag_Exception(_("Missing valid search criteria"));
|
|
}
|
|
$tasklist->set('search', $info['search']);
|
|
}
|
|
try {
|
|
$tasklist->save();
|
|
} catch (Horde_Share_Exception $e) {
|
|
throw new Nag_Exception(sprintf(_("Unable to save task list \"%s\": %s"), $info['name'], $e->getMessage()));
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Deletes a task list.
|
|
*
|
|
* @param Horde_Share_Object $tasklist The task list to delete.
|
|
*
|
|
* @throws Nag_Exception
|
|
* @throws Horde_Exception_PermissionDenied
|
|
*/
|
|
static public function deleteTasklist(Horde_Share_Object $tasklist)
|
|
{
|
|
if (!$GLOBALS['registry']->getAuth() ||
|
|
($tasklist->get('owner') != $GLOBALS['registry']->getAuth() &&
|
|
(!is_null($tasklist->get('owner')) || !$GLOBALS['registry']->isAdmin()))) {
|
|
throw new Horde_Exception_PermissionDenied(_("You are not allowed to delete this task list."));
|
|
}
|
|
|
|
// Delete the task list.
|
|
$storage = &$GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklist->getName());
|
|
$result = $storage->deleteAll();
|
|
|
|
// Remove share and all groups/permissions.
|
|
try {
|
|
$GLOBALS['nag_shares']->removeShare($tasklist);
|
|
} catch (Horde_Share_Exception $e) {
|
|
throw new Nag_Exception($e);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Returns the label to be used for a task list.
|
|
*
|
|
* Attaches the owner name of shared task lists if necessary.
|
|
*
|
|
* @param Horde_Share_Object A task list.
|
|
*
|
|
* @return string The task list's label.
|
|
*/
|
|
public static function getLabel($tasklist)
|
|
{
|
|
$label = $tasklist->get('name');
|
|
if ($tasklist->get('owner') &&
|
|
$tasklist->get('owner') != $GLOBALS['registry']->getAuth()) {
|
|
$label .= ' [' . $GLOBALS['registry']->convertUsername($tasklist->get('owner'), false) . ']';
|
|
}
|
|
return $label;
|
|
}
|
|
|
|
/**
|
|
* Returns a DAV URL to be used for a task list.
|
|
*
|
|
* @param integer $type A Nag::DAV_* constant.
|
|
* @param Horde_Share_Object A task list.
|
|
*
|
|
* @return string The task list's URL.
|
|
* @throws Horde_Exception
|
|
*/
|
|
public static function getUrl($type, $tasklist)
|
|
{
|
|
global $conf, $injector, $registry;
|
|
|
|
$url = $registry->get('webroot', 'horde');
|
|
$rewrite = isset($conf['urls']['pretty']) &&
|
|
$conf['urls']['pretty'] == 'rewrite';
|
|
|
|
switch ($type) {
|
|
case Nag::DAV_WEBDAV:
|
|
if ($rewrite) {
|
|
$url .= '/rpc/nag/';
|
|
} else {
|
|
$url .= '/rpc.php/nag/';
|
|
}
|
|
$url = Horde::url($url, true, -1)
|
|
. ($tasklist->get('owner')
|
|
? $registry->convertUsername($tasklist->get('owner'), false)
|
|
: '-system-')
|
|
. '/' . $tasklist->getName() . '.ics';
|
|
break;
|
|
|
|
case Nag::DAV_CALDAV:
|
|
if ($rewrite) {
|
|
$url .= '/rpc/calendars/';
|
|
} else {
|
|
$url .= '/rpc.php/calendars/';
|
|
}
|
|
$url = Horde::url($url, true, -1)
|
|
. $registry->convertUsername($registry->getAuth(), false)
|
|
. '/'
|
|
. $injector->getInstance('Horde_Dav_Storage')
|
|
->getExternalCollectionId($tasklist->getName(), 'tasks')
|
|
. '/';
|
|
break;
|
|
|
|
case Nag::DAV_ACCOUNT:
|
|
if ($rewrite) {
|
|
$url .= '/rpc/';
|
|
} else {
|
|
$url .= '/rpc.php/';
|
|
}
|
|
$url = Horde::url($url, true, -1)
|
|
. 'principals/' . $registry->convertUsername($registry->getAuth(), false) . '/';
|
|
break;
|
|
}
|
|
|
|
return $url;
|
|
}
|
|
|
|
/**
|
|
* Returns a random CSS color.
|
|
*
|
|
* @return string A random CSS color string.
|
|
*/
|
|
static public function randomColor()
|
|
{
|
|
$color = '#';
|
|
for ($i = 0; $i < 3; $i++) {
|
|
$color .= sprintf('%02x', mt_rand(0, 255));
|
|
}
|
|
return $color;
|
|
}
|
|
|
|
/**
|
|
* Builds the HTML for a priority selection widget.
|
|
*
|
|
* @param string $name The name of the widget.
|
|
* @param integer $selected The default selected priority.
|
|
*
|
|
* @return string The HTML <select> widget.
|
|
*/
|
|
static public function buildPriorityWidget($name, $selected = -1)
|
|
{
|
|
$descs = array(1 => _("(highest)"), 5 => _("(lowest)"));
|
|
|
|
$html = "<select id=\"$name\" name=\"$name\">";
|
|
for ($priority = 1; $priority <= 5; $priority++) {
|
|
$html .= "<option value=\"$priority\"";
|
|
$html .= ($priority == $selected) ? ' selected="selected">' : '>';
|
|
$html .= $priority . ' ' . @$descs[$priority] . '</option>';
|
|
}
|
|
$html .= "</select>\n";
|
|
|
|
return $html;
|
|
}
|
|
|
|
/**
|
|
* Builds the HTML for a checkbox widget.
|
|
*
|
|
* @param string $name The name of the widget.
|
|
* @param integer $checked The default checkbox state.
|
|
*
|
|
* @return string HTML for a checkbox representing the completion state.
|
|
*/
|
|
static public function buildCheckboxWidget($name, $checked = 0)
|
|
{
|
|
$name = htmlspecialchars($name);
|
|
return "<input type=\"checkbox\" id=\"$name\" name=\"$name\"" .
|
|
($checked ? ' checked="checked"' : '') . ' />';
|
|
}
|
|
|
|
/**
|
|
* Formats the given Unix-style date string.
|
|
*
|
|
* @param string $unixdate The Unix-style date value to format.
|
|
* @param boolean $hours Whether to add hours.
|
|
*
|
|
* @return string The formatted due date string.
|
|
*/
|
|
static public function formatDate($unixdate = '', $hours = true)
|
|
{
|
|
global $prefs;
|
|
|
|
if (empty($unixdate)) {
|
|
return '';
|
|
}
|
|
|
|
$date = strftime($prefs->getValue('date_format'), $unixdate);
|
|
if (!$hours) {
|
|
return $date;
|
|
}
|
|
|
|
return sprintf(_("%s at %s"),
|
|
$date,
|
|
strftime($prefs->getValue('twentyFour') ? '%H:%M' : '%I:%M %p', $unixdate));
|
|
}
|
|
|
|
/**
|
|
* Returns the string representation of the given completion status.
|
|
*
|
|
* @param integer $completed The completion value.
|
|
*
|
|
* @return string The HTML representation of $completed.
|
|
*/
|
|
static public function formatCompletion($completed)
|
|
{
|
|
return $completed ?
|
|
Horde::img('checked.png', _("Completed")) :
|
|
Horde::img('unchecked.png', _("Not Completed"));
|
|
}
|
|
|
|
/**
|
|
* Returns a colored representation of a priority.
|
|
*
|
|
* @param integer $priority The priority level.
|
|
*
|
|
* @return string The HTML representation of $priority.
|
|
*/
|
|
static public function formatPriority($priority)
|
|
{
|
|
return '<span class="pri-' . (int)$priority . '">' . (int)$priority .
|
|
'</span>';
|
|
}
|
|
|
|
/**
|
|
* Returns the string matching the given alarm value.
|
|
*
|
|
* @param integer $value The alarm value in minutes.
|
|
*
|
|
* @return string The formatted alarm string.
|
|
*/
|
|
static public function formatAlarm($value)
|
|
{
|
|
if ($value) {
|
|
if ($value % 10080 == 0) {
|
|
$alarm_value = $value / 10080;
|
|
$alarm_unit = _("Week(s)");
|
|
} elseif ($value % 1440 == 0) {
|
|
$alarm_value = $value / 1440;
|
|
$alarm_unit = _("Day(s)");
|
|
} elseif ($value % 60 == 0) {
|
|
$alarm_value = $value / 60;
|
|
$alarm_unit = _("Hour(s)");
|
|
} else {
|
|
$alarm_value = $value;
|
|
$alarm_unit = _("Minute(s)");
|
|
}
|
|
$alarm_text = "$alarm_value $alarm_unit";
|
|
} else {
|
|
$alarm_text = _("None");
|
|
}
|
|
return $alarm_text;
|
|
}
|
|
|
|
/**
|
|
* Returns the full name and a compose to message an assignee.
|
|
*
|
|
* @param string $assignee The assignee's user name.
|
|
* @param boolean $link Whether to link to an email compose screen.
|
|
*
|
|
* @return string The formatted assignee name.
|
|
*/
|
|
static public function formatAssignee($assignee, $link = false)
|
|
{
|
|
if (!strlen($assignee)) {
|
|
return '';
|
|
}
|
|
|
|
$identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($assignee);
|
|
$fullname = $identity->getValue('fullname');
|
|
if (!strlen($fullname)) {
|
|
$fullname = $assignee;
|
|
}
|
|
$email = $identity->getValue('from_addr');
|
|
if ($link && !empty($email) &&
|
|
$GLOBALS['registry']->hasMethod('mail/compose')) {
|
|
return Horde::link($GLOBALS['registry']->call(
|
|
'mail/compose',
|
|
array(array('to' => $email))))
|
|
. htmlspecialchars($fullname . ' <' . $email . '>')
|
|
. '</a>';
|
|
}
|
|
|
|
return htmlspecialchars($fullname);
|
|
}
|
|
|
|
/**
|
|
* Initial app setup code.
|
|
*/
|
|
static public function initialize()
|
|
{
|
|
/* Store the request timestamp if it's not already present. */
|
|
if (!isset($_SERVER['REQUEST_TIME'])) {
|
|
$_SERVER['REQUEST_TIME'] = time();
|
|
}
|
|
|
|
// Update the preference for what task lists to display. If the user
|
|
// doesn't have any selected task lists for view then fall back to
|
|
// some available list.
|
|
$GLOBALS['display_tasklists'] = @unserialize($GLOBALS['prefs']->getValue('display_tasklists'));
|
|
if (!$GLOBALS['display_tasklists']) {
|
|
$GLOBALS['display_tasklists'] = array();
|
|
}
|
|
if (($actionID = Horde_Util::getFormData('actionID')) !== null) {
|
|
$tasklistId = Horde_Util::getFormData('display_tasklist');
|
|
switch ($actionID) {
|
|
case 'add_displaylist':
|
|
if (!in_array($tasklistId, $GLOBALS['display_tasklists'])) {
|
|
$GLOBALS['display_tasklists'][] = $tasklistId;
|
|
}
|
|
break;
|
|
case 'remove_displaylist':
|
|
if (in_array($tasklistId, $GLOBALS['display_tasklists'])) {
|
|
$key = array_search($tasklistId, $GLOBALS['display_tasklists']);
|
|
unset($GLOBALS['display_tasklists'][$key]);
|
|
}
|
|
}
|
|
}
|
|
|
|
// Make sure all task lists exist now, to save on checking later.
|
|
$_temp = $GLOBALS['display_tasklists'];
|
|
$GLOBALS['all_tasklists'] = self::listTasklists();
|
|
$GLOBALS['display_tasklists'] = array();
|
|
foreach ($_temp as $id) {
|
|
if (isset($GLOBALS['all_tasklists'][$id])) {
|
|
$GLOBALS['display_tasklists'][] = $id;
|
|
}
|
|
}
|
|
|
|
/* All tasklists for guests. */
|
|
if (!count($GLOBALS['display_tasklists']) &&
|
|
!$GLOBALS['registry']->getAuth()) {
|
|
$GLOBALS['display_tasklists'] = array_keys($GLOBALS['all_tasklists']);
|
|
}
|
|
|
|
$tasklists = $GLOBALS['injector']->getInstance('Nag_Factory_Tasklists')
|
|
->create();
|
|
if (($new_default = $tasklists->ensureDefaultShare()) !== null) {
|
|
$GLOBALS['display_tasklists'][] = $new_default;
|
|
$GLOBALS['prefs']->setValue('default_tasklist', $new_default);
|
|
}
|
|
|
|
$GLOBALS['prefs']->setValue('display_tasklists', serialize($GLOBALS['display_tasklists']));
|
|
}
|
|
|
|
/**
|
|
* Trigger notifications.
|
|
*/
|
|
static public function status()
|
|
{
|
|
global $notification;
|
|
|
|
if (empty($GLOBALS['conf']['alarms']['driver'])) {
|
|
// Get any alarms in the next hour.
|
|
try {
|
|
$alarmList = self::listAlarms($_SERVER['REQUEST_TIME']);
|
|
$messages = array();
|
|
foreach ($alarmList as $task) {
|
|
$differential = $task->due - $_SERVER['REQUEST_TIME'];
|
|
$key = $differential;
|
|
while (isset($messages[$key])) {
|
|
$key++;
|
|
}
|
|
if ($differential >= -60 && $differential < 60) {
|
|
$messages[$key] = array(sprintf(_("%s is due now."), $task->name), 'horde.alarm');
|
|
} elseif ($differential >= 60) {
|
|
$messages[$key] = array(sprintf(_("%s is due in %s"), $task->name,
|
|
self::secondsToString($differential)), 'horde.alarm');
|
|
}
|
|
}
|
|
|
|
ksort($messages);
|
|
foreach ($messages as $message) {
|
|
$notification->push($message[0], $message[1]);
|
|
}
|
|
} catch (Nag_Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
$notification->push($e->getMessage(), 'horde.error');
|
|
}
|
|
}
|
|
|
|
// Check here for guest task lists so that we don't get multiple
|
|
// messages after redirects, etc.
|
|
if (!$GLOBALS['registry']->getAuth() && !count(self::listTasklists())) {
|
|
$notification->push(_("No task lists are available to guests."));
|
|
}
|
|
|
|
// Display all notifications.
|
|
$notification->notify(array('listeners' => 'status'));
|
|
}
|
|
|
|
/**
|
|
* Sends email notifications that a task has been added, edited, or
|
|
* deleted to users that want such notifications.
|
|
*
|
|
* @param string $action The event action. One of "add", "edit", or
|
|
* "delete".
|
|
* @param Nag_Task $task The changed task.
|
|
* @param Nag_Task $old_task The original task if $action is "edit".
|
|
*
|
|
* @throws Nag_Exception
|
|
*/
|
|
static public function sendNotification($action, $task, $old_task = null)
|
|
{
|
|
if (!in_array($action, array('add', 'edit', 'delete'))) {
|
|
throw new Nag_Exception('Unknown event action: ' . $action);
|
|
}
|
|
|
|
try {
|
|
$share = $GLOBALS['nag_shares']->getShare($task->tasklist);
|
|
} catch (Horde_Share_Exception $e) {
|
|
Horde::log($e->getMessage(), 'ERR');
|
|
throw new Nag_Exception($e);
|
|
}
|
|
|
|
$groups = $GLOBALS['injector']->getInstance('Horde_Group');
|
|
$recipients = array();
|
|
$identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create();
|
|
$from = $identity->getDefaultFromAddress(true);
|
|
|
|
$owner = $share->get('owner');
|
|
if (strlen($owner)) {
|
|
$recipients[$owner] = self::_notificationPref($owner, 'owner');
|
|
}
|
|
|
|
foreach ($share->listUsers(Horde_Perms::READ) as $user) {
|
|
if (empty($recipients[$user])) {
|
|
$recipients[$user] = self::_notificationPref($user, 'read', $task->tasklist);
|
|
}
|
|
}
|
|
foreach ($share->listGroups(Horde_Perms::READ) as $group) {
|
|
try {
|
|
$group_users = $groups->listUsers($group);
|
|
} catch (Horde_Group_Exception $e) {
|
|
Horde::log($e, 'ERR');
|
|
continue;
|
|
}
|
|
|
|
foreach ($group_users as $user) {
|
|
if (empty($recipients[$user])) {
|
|
$recipients[$user] = self::_notificationPref($user, 'read', $task->tasklist);
|
|
}
|
|
}
|
|
}
|
|
|
|
$addresses = array();
|
|
foreach ($recipients as $user => $vals) {
|
|
if (!$vals) {
|
|
continue;
|
|
}
|
|
$identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($user);
|
|
$email = $identity->getValue('from_addr');
|
|
if (strpos($email, '@') === false) {
|
|
continue;
|
|
}
|
|
if (!isset($addresses[$vals['lang']][$vals['tf']][$vals['df']])) {
|
|
$addresses[$vals['lang']][$vals['tf']][$vals['df']] = array();
|
|
}
|
|
|
|
$tmp = new Horde_Mail_Rfc822_Address($email);
|
|
$tmp->personal = $identity->getValue('fullname');
|
|
$addresses[$vals['lang']][$vals['tf']][$vals['df']][] = strval($tmp);
|
|
}
|
|
|
|
if (!$addresses) {
|
|
return;
|
|
}
|
|
|
|
$mail = new Horde_Mime_Mail(array(
|
|
'User-Agent' => 'Nag ' . $GLOBALS['registry']->getVersion(),
|
|
'Precedence' => 'bulk',
|
|
'Auto-Submitted' => 'auto-generated',
|
|
'From' => $from));
|
|
|
|
foreach ($addresses as $lang => $twentyFour) {
|
|
$GLOBALS['registry']->setLanguageEnvironment($lang);
|
|
|
|
$view_link = Horde::url('view.php', true)->add(array(
|
|
'tasklist' => $task->tasklist,
|
|
'task' => $task->id
|
|
))->setRaw(true);
|
|
|
|
switch ($action) {
|
|
case 'add':
|
|
$subject = _("Task added:");
|
|
$notification_message = _("You requested to be notified when tasks are added to your task lists.")
|
|
. "\n\n"
|
|
. ($task->due
|
|
? _("The task \"%s\" has been added to task list \"%s\", with a due date of: %s.")
|
|
: _("The task \"%s\" has been added to task list \"%s\"."))
|
|
. "\n"
|
|
. str_replace('%', '%%', $view_link);
|
|
break;
|
|
|
|
case 'edit':
|
|
$subject = _("Task modified:");
|
|
$notification_message = _("You requested to be notified when tasks are edited on your task lists.")
|
|
. "\n\n"
|
|
. _("The task \"%s\" has been edited on task list \"%s\".")
|
|
. "\n"
|
|
. str_replace('%', '%%', $view_link)
|
|
. "\n\n"
|
|
. _("Changes made for this task:");
|
|
if ($old_task->name != $task->name) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed name from \"%s\" to \"%s\""),
|
|
$old_task->name, $task->name);
|
|
}
|
|
if ($old_task->tasklist != $task->tasklist) {
|
|
$old_share = $GLOBALS['nag_shares']->getShare($old_task->tasklist);
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed task list from \"%s\" to \"%s\""),
|
|
Nag::getLabel($old_share), Nag::getLabel($share));
|
|
}
|
|
if ($old_task->parent_id != $task->parent_id) {
|
|
$old_parent = $old_task->getParent();
|
|
try {
|
|
$parent = $task->getParent();
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed parent task from \"%s\" to \"%s\""),
|
|
$old_parent ? $old_parent->name : _("no parent"),
|
|
$parent ? $parent->name : _("no parent"));
|
|
} catch (Nag_Exception $e) {
|
|
}
|
|
}
|
|
if ($old_task->assignee != $task->assignee) {
|
|
$identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($old_task->assignee);
|
|
$old_name = $identity->getValue('fullname');
|
|
if (!strlen($old_name)) {
|
|
$old_name = $old_task->assignee;
|
|
}
|
|
$identity = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($task->assignee);
|
|
$new_name = $identity->getValue('fullname');
|
|
if (!strlen($new_name)) {
|
|
$new_name = $new_task->assignee;
|
|
}
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed assignee from \"%s\" to \"%s\""),
|
|
$old_name, $new_name);
|
|
}
|
|
if ($old_task->private != $task->private) {
|
|
$notification_message .= "\n - "
|
|
. ($task->private ? _("Turned privacy on") : _("Turned privacy off"));
|
|
}
|
|
if ($old_task->due != $task->due) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed due date from %s to %s"),
|
|
$old_task->due ? self::formatDate($old_task->due) : _("no due date"),
|
|
$task->due ? self::formatDate($task->due) : _("no due date"));
|
|
}
|
|
if ($old_task->start != $task->start) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed start date from %s to %s"),
|
|
$old_task->start ? self::formatDate($old_task->start) : _("no start date"),
|
|
$task->start ? self::formatDate($task->start) : _("no start date"));
|
|
}
|
|
if ($old_task->alarm != $task->alarm) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed alarm from %s to %s"),
|
|
self::formatAlarm($old_task->alarm), self::formatAlarm($task->alarm));
|
|
}
|
|
if ($old_task->priority != $task->priority) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed priority from %s to %s"),
|
|
$old_task->priority, $task->priority);
|
|
}
|
|
if ($old_task->estimate != $task->estimate) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed estimate from %s to %s"),
|
|
$old_task->estimate, $task->estimate);
|
|
}
|
|
if ($old_task->completed != $task->completed) {
|
|
$notification_message .= "\n - "
|
|
. sprintf(_("Changed completion from %s to %s"),
|
|
$old_task->completed ? _("completed") : _("not completed"),
|
|
$task->completed ? _("completed") : _("not completed"));
|
|
}
|
|
if ($old_task->desc != $task->desc) {
|
|
$notification_message .= "\n - " . _("Changed description");
|
|
}
|
|
break;
|
|
|
|
case 'delete':
|
|
$subject = _("Task deleted:");
|
|
$notification_message =
|
|
_("You requested to be notified when tasks are deleted from your task lists.")
|
|
. "\n\n"
|
|
. _("The task \"%s\" has been deleted from task list \"%s\".");
|
|
break;
|
|
}
|
|
|
|
$mail->addHeader('Subject', $subject . ' ' . $task->name);
|
|
|
|
foreach ($twentyFour as $tf => $dateFormat) {
|
|
foreach ($dateFormat as $df => $df_recipients) {
|
|
$message = sprintf($notification_message,
|
|
$task->name,
|
|
Nag::getLabel($share),
|
|
$task->due ? strftime($df, $task->due) . ' ' . date($tf ? 'H:i' : 'h:ia', $task->due) : '');
|
|
if (strlen(trim($task->desc))) {
|
|
$message .= "\n\n" . _("Task description:") . "\n\n" . $task->desc;
|
|
}
|
|
|
|
$mail->setBody($message);
|
|
$mail->clearRecipients();
|
|
$mail->addRecipients($df_recipients);
|
|
|
|
Horde::log(sprintf('Sending event notifications for %s to %s', $task->name, implode(', ', $df_recipients)), 'INFO');
|
|
$mail->send($GLOBALS['injector']->getInstance('Horde_Mail'));
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Builds the body MIME part of a multipart message.
|
|
*
|
|
* @param Horde_View $view A view to render the HTML and plain text
|
|
* templates for the messate.
|
|
* @param string $template The template base name for the view.
|
|
* @param Horde_Mime_Part $image The MIME part of a related image.
|
|
*
|
|
* @return Horde_Mime_Part A multipart/alternative MIME part.
|
|
*/
|
|
static public function buildMimeMessage(Horde_View $view, $template,
|
|
Horde_Mime_Part $image)
|
|
{
|
|
$multipart = new Horde_Mime_Part();
|
|
$multipart->setType('multipart/alternative');
|
|
$bodyText = new Horde_Mime_Part();
|
|
$bodyText->setType('text/plain');
|
|
$bodyText->setCharset('UTF-8');
|
|
$bodyText->setContents($view->render($template . '.plain.php'));
|
|
$bodyText->setDisposition('inline');
|
|
$multipart->addPart($bodyText);
|
|
$bodyHtml = new Horde_Mime_Part();
|
|
$bodyHtml->setType('text/html');
|
|
$bodyHtml->setCharset('UTF-8');
|
|
$bodyHtml->setContents($view->render($template . '.html.php'));
|
|
$bodyHtml->setDisposition('inline');
|
|
$related = new Horde_Mime_Part();
|
|
$related->setType('multipart/related');
|
|
$related->setContentTypeParameter('start', $bodyHtml->setContentId());
|
|
$related->addPart($bodyHtml);
|
|
$related->addPart($image);
|
|
$multipart->addPart($related);
|
|
return $multipart;
|
|
}
|
|
|
|
/**
|
|
* Returns a MIME part for an image to be embedded into a HTML document.
|
|
*
|
|
* @param string $file An image file name.
|
|
*
|
|
* @return Horde_Mime_Part A MIME part representing the image.
|
|
*/
|
|
static public function getImagePart($file)
|
|
{
|
|
$background = Horde_Themes::img($file);
|
|
$image = new Horde_Mime_Part();
|
|
$image->setType('image/png');
|
|
$image->setContents(file_get_contents($background->fs));
|
|
$image->setContentId();
|
|
$image->setDisposition('attachment');
|
|
return $image;
|
|
}
|
|
|
|
/**
|
|
* Returns the real name, if available, of a user.
|
|
*
|
|
* @param string $uid The userid of the user to retrieve
|
|
*
|
|
* @return string The fullname of the user.
|
|
*/
|
|
static public function getUserName($uid)
|
|
{
|
|
static $names = array();
|
|
|
|
if (!isset($names[$uid])) {
|
|
$ident = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Identity')->create($uid);
|
|
$ident->setDefault($ident->getDefault());
|
|
$names[$uid] = $ident->getValue('fullname');
|
|
if (empty($names[$uid])) {
|
|
$names[$uid] = $uid;
|
|
}
|
|
}
|
|
|
|
return $names[$uid];
|
|
}
|
|
|
|
/**
|
|
* Returns whether a user wants email notifications for a tasklist.
|
|
*
|
|
* @todo This method is causing a memory leak somewhere, noticeable if
|
|
* importing a large amount of events.
|
|
*
|
|
* @param string $user A user name.
|
|
* @param string $mode The check "mode". If "owner", the method checks
|
|
* if the user wants notifications only for
|
|
* tasklists he owns. If "read", the method checks
|
|
* if the user wants notifications for all
|
|
* tasklists he has read access to, or only for
|
|
* shown tasklists and the specified tasklist is
|
|
* currently shown.
|
|
* @param string $tasklist The name of the tasklist if mode is "read".
|
|
*
|
|
* @return boolean True if the user wants notifications for the tasklist.
|
|
*/
|
|
static protected function _notificationPref($user, $mode, $tasklist = null)
|
|
{
|
|
$prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('nag', array(
|
|
'cache' => false,
|
|
'user' => $user
|
|
));
|
|
$vals = array('lang' => $prefs->getValue('language'),
|
|
'tf' => $prefs->getValue('twentyFour'),
|
|
'df' => $prefs->getValue('date_format'));
|
|
|
|
if ($prefs->getValue('task_notification_exclude_self') &&
|
|
$user == $GLOBALS['registry']->getAuth()) {
|
|
return false;
|
|
}
|
|
|
|
$notification = $prefs->getValue('task_notification');
|
|
switch ($notification) {
|
|
case 'owner':
|
|
return $mode == 'owner' ? $vals : false;
|
|
case 'read':
|
|
return $mode == 'read' ? $vals : false;
|
|
case 'show':
|
|
if ($mode == 'read') {
|
|
$display_tasklists = unserialize($prefs->getValue('display_tasklists'));
|
|
return in_array($tasklist, $display_tasklists) ? $vals : false;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by create date (not currently used
|
|
* as it would require accessing Horde_History for each task) and id.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal (though no tasks should ever be
|
|
* equal in this comparison).
|
|
*/
|
|
static public function _sortByIdentity($a, $b)
|
|
{
|
|
return strcmp($a->id, $b->id);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by priority.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByPriority($a, $b)
|
|
{
|
|
if ($a->priority == $b->priority) {
|
|
return self::_sortByIdentity($a, $b);
|
|
}
|
|
return ($a->priority > $b->priority) ? 1 : -1;
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by priority.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByPriority($a, $b)
|
|
{
|
|
return self::_sortByPriority($b, $a);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by name.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByName($a, $b)
|
|
{
|
|
return strcasecmp($a->name, $b->name);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by name.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByName($a, $b)
|
|
{
|
|
return strcasecmp($b->name, $a->name);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by assignee.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByAssignee($a, $b)
|
|
{
|
|
return strcasecmp($a->assignee, $b->assignee);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by assignee.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByAssignee($a, $b)
|
|
{
|
|
return strcasecmp($b->assignee, $a->assignee);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by assignee.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByEstimate($a, $b)
|
|
{
|
|
$a_est = $a->estimation();
|
|
$b_est = $b->estimation();
|
|
if ($a_est == $b_est) {
|
|
return self::_sortByIdentity($a, $b);
|
|
}
|
|
return ($a_est > $b_est) ? 1 : -1;
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by name.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByEstimate($a, $b)
|
|
{
|
|
return self::_sortByEstimate($b, $a);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by due date.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByDue($a, $b)
|
|
{
|
|
$a_due = $a->getNextDue();
|
|
$b_due = $b->getNextDue();
|
|
|
|
if (!$a_due && !$b_due) {
|
|
return self::_sortByName($a, $b);
|
|
}
|
|
|
|
// Treat empty due dates as farthest into the future.
|
|
if (!$a_due) {
|
|
return 1;
|
|
}
|
|
if (!$b_due) {
|
|
return -1;
|
|
}
|
|
|
|
if ($a_due->equals($b_due)) {
|
|
return self::_sortByName($a, $b);
|
|
}
|
|
|
|
return ($a_due->after($b_due)) ? 1 : -1;
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by due date.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater,
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByDue($a, $b)
|
|
{
|
|
return self::_sortByDue($b, $a);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by start date.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByStart($a, $b)
|
|
{
|
|
if ($a->start == $b->start) {
|
|
return self::_sortByIdentity($a, $b);
|
|
}
|
|
|
|
// Treat empty start dates as farthest into the future.
|
|
if ($a->start == 0) {
|
|
return 1;
|
|
}
|
|
if ($b->start == 0) {
|
|
return -1;
|
|
}
|
|
|
|
return ($a->start > $b->start) ? 1 : -1;
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by start date.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater,
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByStart($a, $b)
|
|
{
|
|
return self::_sortByStart($b, $a);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by completion status.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByCompletion($a, $b)
|
|
{
|
|
if ($a->completed == $b->completed) {
|
|
return self::_sortByIdentity($a, $b);
|
|
}
|
|
return ($a->completed > $b->completed) ? -1 : 1;
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by completion status.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByCompletion($a, $b)
|
|
{
|
|
return self::_sortByCompletion($b, $a);
|
|
}
|
|
|
|
/**
|
|
* Comparison function for sorting tasks by owner.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer 1 if task one is greater, -1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _sortByOwner($a, $b)
|
|
{
|
|
$diff = strcasecmp(self::_getOwner($a), self::_getOwner($b));
|
|
if ($diff == 0) {
|
|
return self::_sortByIdentity($a, $b);
|
|
} else {
|
|
return $diff;
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Comparison function for reverse sorting tasks by owner.
|
|
*
|
|
* @param array $a Task one.
|
|
* @param array $b Task two.
|
|
*
|
|
* @return integer -1 if task one is greater, 1 if task two is greater;
|
|
* 0 if they are equal.
|
|
*/
|
|
static public function _rsortByOwner($a, $b)
|
|
{
|
|
return self::_sortByOwner($b, $a);
|
|
}
|
|
|
|
/**
|
|
* Returns the owner of a task.
|
|
*
|
|
* @param Nag_Task $task A task.
|
|
*
|
|
* @return string The task's owner.
|
|
*/
|
|
static protected function _getOwner($task)
|
|
{
|
|
if ($task->tasklist == '**EXTERNAL**') {
|
|
return $GLOBALS['registry']->getAuth();
|
|
}
|
|
$share = $GLOBALS['nag_shares']->getShare($task->tasklist);
|
|
$owner = $task->tasklist;
|
|
if ($owner != $share->get('owner')) {
|
|
$owner = $share->get('name');
|
|
}
|
|
return $owner;
|
|
}
|
|
|
|
/**
|
|
* Returns the tasklists that should be used for syncing.
|
|
*
|
|
* @return array An array of task list ids
|
|
*/
|
|
static public function getSyncLists()
|
|
{
|
|
$cs = unserialize($GLOBALS['prefs']->getValue('sync_lists'));
|
|
|
|
// Bug #14585 Filter out erroneous null values.
|
|
$cs = array_filter($cs);
|
|
|
|
if (!empty($cs)) {
|
|
// Have a pref, make sure it's still available
|
|
$lists = self::listTasklists(false, Horde_Perms::DELETE);
|
|
$cscopy = array_flip($cs);
|
|
foreach ($cs as $c) {
|
|
if (empty($lists[$c])) {
|
|
unset($cscopy[$c]);
|
|
}
|
|
}
|
|
|
|
// Have at least one
|
|
if (count($cscopy)) {
|
|
return array_flip($cscopy);
|
|
}
|
|
}
|
|
|
|
if ($cs = self::getDefaultTasklist(Horde_Perms::EDIT)) {
|
|
return array($cs);
|
|
}
|
|
|
|
return array();
|
|
}
|
|
|
|
}
|