package.xml 0000664 0001750 0001750 00000210110 12233763367 011306 0 ustar jan jan
nagpear.horde.orgA web based task list managerNag is a web-based application built upon the Horde Application Framework which provides a simple, clean interface for managing online task lists (i.e., todo lists). It also includes strong integration with the other Horde applications and allows users to share task lists or enable light-weight project management.Chuck Hagenbuchchuckchuck@horde.orgyesJan Schneiderjanjan@horde.orgyesMichael J Rubinskymrubinskmrubinsk@horde.orgyes2013-10-294.1.34.1.0stablestableGPL-2.0
* [jan] SECURITY: Fix XSS vulnerabilities when deleting task lists.
* [jan] Fix updating alarm if completing a task recurrence.
* [jan] Fix editing tasks via CalDAV (Bug #12745).
5.3.01.7.0contentpear.horde.org2.0.33.0.0alpha13.0.0alpha1hordepear.horde.org5.0.06.0.0alpha16.0.0alpha1Horde_Authpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Corepear.horde.org2.6.13.0.0alpha13.0.0alpha1Horde_Datapear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Datepear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Date_Parserpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Exceptionpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Formpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Grouppear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Historypear.horde.org2.1.03.0.0alpha13.0.0alpha1Horde_Icalendarpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Mailpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Mimepear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Permspear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Prefspear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Routespear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Sharepear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Supportpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Text_Filterpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Urlpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Utilpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Viewpear.horde.org2.0.03.0.0alpha13.0.0alpha1gettextHorde_ActiveSyncpear.horde.org2.4.03.0.0alpha13.0.0alpha1Horde_Dbpear.horde.org2.0.03.0.0alpha13.0.0alpha1Horde_Testpear.horde.org2.0.03.0.0alpha13.0.0alpha1hordeRolepear.horde.org3.0.0alpha13.0.0alphaalpha2011-03-09GPL-2.0
* First alpha release for Horde 4.
3.0.0beta13.0.0betabeta2011-03-16GPL-2.0
* Fix saving tasks via PUT method.
* Fix deleting task lists.
* Fix hiding the External prefs group (Bug #9643).
3.0.0RC13.0.0betabeta2011-03-23GPL-2.0
* First release candidate for Horde 4.
* [gwr] Adapted the Kolab driver to Horde 4.
3.0.0RC23.0.0betabeta2011-03-30GPL-2.0
* Second release candidate for Horde 4.
3.0.03.0.0stablestable2011-04-06GPL-2.0
* First stable release for Horde 4.
* [jan] Fix application-specific permission checks (Bug #9786).
* [mjr] Datatree share to SQL upgrade script refactored for Horde 4.
* [jan] Fix creating new tasks through WebDAV (Bug #9675).
* [jan] Move all executable scripts to bin/ and prefix with nag-.
3.0.13.0.0stablestable2011-05-03GPL-2.0
* [jan] Fix reversed logic of displaying sub-task tree icons depending on the text direction (Bug #10033).
* [jan] Fix encoding of non-ascii characters in parent task drop-down.
* [mjr] Fix editing tasklists (Bug #9965).
3.0.23.0.0stablestable2011-07-05GPL-2.0
* [jan] Add Ukrainian translation (Andriy Kopystyansky <anri@polynet.lviv.ua>).
3.0.33.0.0stablestable2011-08-02GPL-2.0
* [jan] Don't load all shares with requested permissions from the backend if $conf['share']['hidden'] is enabled.
3.0.43.0.0stablestable2011-10-18GPL-2.0
* [jan] Add missing parameters when PUTing tasks (Bug #10545).
* [mjr] Fix removeUserData implementation.
* [gwr] Fix Kolab object attribute handling for tasks.
* [gwr] Avoid including the owner name into the default share.
3.0.53.0.0stablestable2011-11-02GPL-2.0
* [cjh] Nicer date/time input with fewer input fields and helper javascript.
* [mjr] Add ability to choose which tasklists to synchronize.
* [mjr] Fix task export to ActiveSync for tasks with no due dates or reminders.
3.0.63.0.0stablestable2011-11-22GPL-2.0
* [jan] Don't display task details of private tasks via the API (Bug #10712).
* [jan] Use correct locale when parsing quick tasks (thpo+horde@dotrc.de, Bug #10720).
3.0.73.0.0stablestable2011-12-13GPL-2.0
* [jan] Fix setting custom alarm methods (Bug #9543).
* [jan] Update Japanese translation (Hiromi Kimura <hiromi@tac.tsukuba.ac.jp>).
3.0.83.0.0stablestable2012-05-29GPL-2.0
* [jan] Update Swedish translation (Per Olof Ljungmark <peo@bsdlabs.com>).
* [jan] Update Italian translation (Massimo Malabotta <mmalabotta@units.it>).
* [jan] Update Hungarian translation (Zoltán Németh <nemeth.zoltan@etit.hu>).
3.0.93.0.0stablestable2012-05-29GPL-2.0
* [mms] Fix getTasklist() API call.
* [jan] Fix changing sort direction.
* [jan] Fix sorting by owner if displaying external task sources.
* [jan] Update Turkish translation (İstanbul Technical University).
2012-08-294.0.0beta14.0.0beta1betabetaGPL-2.0
* [mjr] Add support for smart tasklists.
* [mjr] Migrate categories to tags.
* [jan] Fix fatal error if the task form doesn't validate.
* [jan] Add recurring tasks (Request #2150).
4.0.0beta24.0.0beta1betabeta2012-10-12GPL-2.0
* [jan] Synchronize tags with Kolab categories.
* [mjr] Fix deleting tasks from the task form.
* [mjr] Implement Purge Completed Tasks LoginTask (Request #2520).
* [gwr] Mark the initial default share as such with the Kolab backend.
* [gwr] Always mark the initial share as default tasklist.
* [jan] Move complete task list management into the sidebar.
* [jan] Allow to edit task list color in traditional view.
* [mjr] Tweak display of smartlist links in list view.
* [mjr] Fix fatal recursion error when searching with smartlists were present.
4.0.0RC14.0.0beta1betabeta2012-10-27GPL-2.0
* [jan] Fix export links for tasklists (Bug #11576).
* [mjr] Fix various issues with importing CSV files.
* [mjr] Format datetime fields when exporting a task as a hash (Bug #11537).
* [jan] Fix retrieving tasks from Kolab backend by UID (Bug #11532).
* [jan] Fix toggling tasklists (Bug #11511).
* [jan] Fix listing tasks from Kolab backends.
* [jan] Fix sending alarm e-mail messages.
4.0.04.0.0stablestable2012-10-30GPL-2.0
* [jan] Update Polish translation (Krzysztof Kozera <krzysztof113@o2.pl>).
4.0.14.0.0stablestable2012-11-27GPL-2.0
* [jan] Use Kolab modification and creation dates in the UI (Bug #11591).
* [gwr] Ignore empty dates (Bug #11736).
* [jan] Use encoded UIDs as IDs in Kolab driver.
* [mjr] Fix support for due/start dates for certain ActiveSync clients (Bug #11693).
* [jan] Fix importing tasks from iCalendar (Bug #11681).
* [jan] Fix listing of recurring tasks via timeobjects API.
4.0.24.0.0stablestable2013-01-10GPL-2.0
* [mjr] Fix closing the quick task dialog when cancel is pressed (Bug #11848).
* [mjr] Fix displaying and editing tasks from search results (Bug #11847).
* [jan] Update Basque translation (Ibon Igartua <ibon.igartua@ehu.es>).
* [mjr] Fix incoming task sync with certain combinations of due date values.
* [mjr] Fix quick task addition without session cookies enabled (Bug #11875, Thomas Jarosch <thomas.jarosch@intra2net.com>).
* [jan] Fix editing tasks from the merged list view in smartmobile mode (Bug #11840).
* [jan] Use default Kolab folder for default taks list preference.
* [jan] Mark Kolab folder as default when changing default task list preference.
4.0.34.0.0stablestable2013-05-29GPL-2.0
* [jan] Fix recurring tasks in Kolab driver (Thomas Jarosch <thomas.jarosch@intra2net.com>, Bug #12222).
* [mjr] Fix setting alarm time when syncing (Bug #12201, <thomas.jarosch@intra2net.com>).
* [mjr] Fix issue causing due dates to be offset when syncing (Bug #12200, <thomas.jarosch@intra2net.com>)
* [jan] Fix listing alarms of recurring tasks.
* [jan] Fix default task list in portal block (Bug #12133).
* [jan] Fix displaying of due dates of recurring events in some views.
* [jan] Update broken rows due to migration bug in PostgreSQL (Bug #12101).
* [mjr] Fix uncompleting tasks from task view in smartmobile view (Bug #12098, <ctimoteo@sapo.pt>).
* [jan] Include parent tasks in cost object labels.
* [jan] Only return completed tasks up to a week old as cost objects.
* [mjr] Set correct timezone on incoming ActiveSync tasks (Bug #12053).
* [jan] Update French translation (Paul De Vlieger <paul.de_vlieger@moniut.univ-bpclermont.fr>).
2013-05-074.1.0beta14.0.0betastableGPL-2.0
* [jan] Add CalDAV server support (Request #4267).
* [jan] Update design to closer match the Horde 5 UI.
4.1.0beta24.0.0betastable2013-05-14GPL-2.0
* [jan] Fix incorrect dependencies.
4.1.0RC14.1.0betastable2013-05-29GPL-2.0
* [jan] Don't include external tasks in listTasks() API results.
4.1.04.1.0stablestable2013-06-05GPL-2.0
* Final release.
4.1.14.1.0stablestable2013-07-16GPL-2.0
* [jan] Display CalDAV URL of system tasklists (Bug #12342).
* [mjr] Only query Horde_History when it's required.
* [jan] Fix system tasklist listing via WebDAV.
* [rla] Add system tasklist support for Nag CalDAV access (Request #12342).
* [jan] Fix task description tooltip (Bug #12421).
* [mjr] Persist the tasklist and parent when creating a new task and using "Save and New" (Bug #12400).
* [mjr] Fix tag browsing of shared tasklists (Bug #12405).
* [mjr] Fix importing VTODOs containing RELATED-TO fields (Bug #12355).
* [mjr] Add API methods for using history modification sequences.
4.1.24.1.0stablestable2013-08-27GPL-2.0
* [jan] Fix highlighting task type tabs in list view.
* [jan] Fix fatal errors if DAV support is disabled (Bug #12481).
* [mjr] Fix requesting changes by modification sequence (Bug #12507, Thomas Jarosch <thomas.jarosch@intra2net.com>).
4.1.34.1.0stablestable2013-10-29GPL-2.0
* [jan] SECURITY: Fix XSS vulnerabilities when deleting task lists.
* [jan] Fix updating alarm if completing a task recurrence.
* [jan] Fix editing tasks via CalDAV (Bug #12745).
nag-4.1.3/app/controllers/CompleteTask.php 0000664 0001750 0001750 00000001767 12233763366 016650 0 ustar jan jan getRequestVars();
if (isset($requestVars['task']) && isset($requestVars['tasklist'])) {
$nag_task = new Nag_CompleteTask();
$result = $nag_task->result($requestVars['task'], $requestVars['tasklist']);
} else {
$result = array('error' => 'missing parameters');
}
$requestVars = $request->getGetVars();
if (!empty($requestVars['format']) &&
$requestVars['format'] == 'json') {
$response->setContentType('application/json');
$response->setBody(json_encode($result));
} elseif ($requestVars['url']) {
$response->setRedirectUrl($requestVars['url']);
}
}
}
nag-4.1.3/app/controllers/SaveTask.php 0000664 0001750 0001750 00000011263 12233763366 015766 0 ustar jan jan getInjector()->getInstance('Horde_Registry');
$notification = $this->getInjector()->getInstance('Horde_Notification');
$form = new Nag_Form_Task($vars, $vars->get('task_id') ? sprintf(_("Edit: %s"), $vars->get('name')) : _("New Task"));
if (!$form->validate($vars)) {
// Hideous
$_REQUEST['actionID'] = 'task_form';
require NAG_BASE . '/task.php';
exit;
}
$form->getInfo($vars, $info);
// Check if we are here due to a deletebutton push
if ($vars->deletebutton) {
try {
$share = $GLOBALS['nag_shares']->getShare($info['old_tasklist']);
} catch (Horde_Share_Exception $e) {
$notification->push(sprintf(_("Access denied deleting task: %s"), $e->getMessage()), 'horde.error');
Horde::url('list.php', true)->redirect();
}
if (!$share->hasPermission($registry->getAuth(), Horde_Perms::DELETE)) {
$notification->push(_("Access denied deleting task"), 'horde.error');
Horde::url('list.php', true)->redirect();
}
$storage = $GLOBALS['injector']
->getInstance('Nag_Factory_Driver')
->create($info['old_tasklist']);
try {
$storage->delete($info['task_id']);
} catch (Nag_Exception $e) {
$notification->push(sprintf(_("Error deleting task: %s"), $e->getMessage()), 'horde.error');
Horde::url('list.php', true)->redirect();
}
$notification->push(_("Task successfully deleted"), 'horde.success');
Horde::url('list.php', true)->redirect();
}
if ($prefs->isLocked('default_tasklist') ||
count(Nag::listTasklists(false, Horde_Perms::EDIT)) <= 1) {
$info['tasklist_id'] = $info['old_tasklist'] = Nag::getDefaultTasklist(Horde_Perms::EDIT);
}
try {
$share = $GLOBALS['nag_shares']->getShare($info['tasklist_id']);
} catch (Horde_Share_Exception $e) {
$notification->push(sprintf(_("Access denied saving task: %s"), $e->getMessage()), 'horde.error');
Horde::url('list.php', true)->redirect();
}
if (!$share->hasPermission($registry->getAuth(), Horde_Perms::EDIT)) {
$notification->push(_("Access denied saving task to this task list."), 'horde.error');
Horde::url('list.php', true)->redirect();
}
/* If a task id is set, we're modifying an existing task. Otherwise,
* we're adding a new task with the provided attributes. */
if (!empty($info['task_id']) && !empty($info['old_tasklist'])) {
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')
->create($info['old_tasklist']);
$info['tasklist'] = $info['tasklist_id'];
$result = $storage->modify($info['task_id'], $info);
} else {
/* Check permissions. */
$perms = $this->getInjector()->getInstance('Horde_Core_Perms');
if ($perms->hasAppPermission('max_tasks') !== true &&
$perms->hasAppPermission('max_tasks') <= Nag::countTasks()) {
Horde::url('list.php', true)->redirect();
}
/* Creating a new task. */
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')
->create($info['tasklist_id']);
// These must be unset since the form sets them to NULL
unset($info['owner']);
unset($info['uid']);
try {
$newid = $storage->add($info);
} catch (Nag_Exception $e) {
$notification->push(sprintf(_("There was a problem saving the task: %s."), $e->getMessage()), 'horde.error');
Horde::url('list.php', true)->redirect();
}
}
$notification->push(sprintf(_("Saved %s."), $info['name']), 'horde.success');
/* Return to the last page or to the task list. */
if ($vars->savenewbutton) {
$url = Horde::url('task.php', true)->add(array(
'actionID' => 'add_task',
'tasklist_id' => $info['tasklist_id'],
'parent' => $info['parent']));
} else {
$url = Horde_Util::getFormData('url', Horde::url('list.php', true));
}
$response->setRedirectUrl($url);
}
}
nag-4.1.3/bin/nag-convert-datatree-shares-to-sql 0000775 0001750 0001750 00000012763 12233763366 017624 0 ustar jan jan #!/usr/bin/env php
get('horde_dir', null, 'pear.horde.org') . '/nag/lib/Application.php';
}
Horde_Registry::appInit('nag', array('cli' => true));
$db = $injector->getInstance('Horde_Db_Adapter');
$error_cnt = 0;
$delete_dt_data = false;
$answer = $cli->prompt('Do you want to keep your old datatree data or delete it?', array('Keep', 'Delete'));
if ($answer == 1) {
$delete_dt_data = true;
}
$answer = $cli->prompt(sprintf("Data will be copied into the new tables, and %s be deleted from the datatree.\n Is this what you want?", $delete_dt_data ? 'WILL' : 'WILL NOT'), array('y' => 'Yes', 'n' => 'No'));
if ($answer != 'y') {
exit;
}
/* Get the share entries */
try {
$shares_result = $db->selectAssoc('SELECT datatree_id, datatree_name FROM horde_datatree WHERE group_uid = ' . $db->quoteString('horde.shares.nag'));
} catch (Horde_Db_Exception $e) {
$cli->message($e->getMessage(), 'cli.error');
exit;
}
$query = 'SELECT attribute_name, attribute_key, attribute_value FROM horde_datatree_attributes WHERE datatree_id = ?';
foreach ($shares_result as $share_id => $share_name) {
$data = array('share_name' => $share_name);
try {
$rows = $db->select($query, array($share_id));
} catch (Horde_Db_Exception $e) {
$cli->message($e->getMessage(), 'cli.error');
exit;
}
$users = array();
$groups = array();
foreach ($rows as $row) {
if ($row['attribute_name'] == 'perm_groups') {
/* Group table entry */
$groups[] = array('group_uid' => $row['attribute_key'],
'perm' => $row['attribute_value']);
} elseif ($row['attribute_name'] == 'perm_users') {
/* User table entry */
$users[] = array('user_uid' => $row['attribute_key'],
'perm' => $row['attribute_value']);
} else {
/* Everything else goes in the main share table */
switch ($row['attribute_name']) {
case 'perm_creator':
case 'perm_default':
case 'perm_guest':
$data[$row['attribute_name']] = $row['attribute_value'];
break;
case 'owner':
$data['share_owner'] = $row['attribute_value'];
break;
case 'name':
// Note the key to the $data array is not related to
// the attribute_name field in the dt_attributes table.
$data['attribute_name'] = $row['attribute_value'];
break;
case 'desc':
$data['attribute_desc'] = $row['attribute_value'];
break;
}
}
}
/* Set flags */
$data['share_flags'] = 0;
if (count($users)) {
$data['share_flags'] |= 1;
}
if (count($groups)) {
$data['share_flags'] |= 2;
}
/* Insert the new data */
$cli->message('Migrating share data for share_id: ' . $share_id, 'cli.message');
$error = false;
$db->beginDbTransaction();
try {
$nextId = insertData('nag_shares', $data, $db);
} catch (Horde_Db_Exception $e) {
$cli->message($e->getMessage(), 'cli.error');
$error = true;
}
if (count($groups)) {
foreach ($groups as $group) {
$group['share_id'] = $nextId;
try {
insertData('nag_shares_groups', $group, $db);
} catch (Horde_Db_Exception $e) {
$cli->message($e->getMessage(), 'cli.error');
$error = true;
}
}
}
if (count($users)) {
foreach ($users as $user) {
$user['share_id'] = $nextId;
try {
$result = insertData('nag_shares_users', $user, $db);
} catch (Horde_Db_Exception $e) {
$cli->message($result->getMessage(), 'cli.error');
$error = true;
}
}
}
/* Delete the datatree data, but ONLY if it was requested */
if ($delete_dt_data && !$error) {
$cli->message('DELETING datatree data for share_id: ' . $share_id, 'cli.message');
try {
$db->delete('DELETE FROM horde_datatree_attributes WHERE datatree_id = ?', array($share_id));
$db->delete('DELETE FROM horde_datatree WHERE datatree_id = ?', array($share_id));
} catch (Horde_Db_Exception $e) {
$cli->message($e->getMessage(), 'cli.error');
$error = true;
}
}
unset($row, $rows, $data, $groups, $users);
if ($error) {
$db->rollbackDbTransaction();
$cli->message('Rollback for share data for share_id: ' . $share_id, 'cli.message');
++$error_cnt;
} else {
$db->commitDbTransaction();
$cli->message('Commit for share data for share_id: ' . $share_id, 'cli.message');
}
}
if ($error_cnt) {
$cli->message(sprintf("Encountered %u errors.", $error_cnt));
}
echo "\nDone.\n";
/**
* Helper function
*/
function insertData($table, $data, $db)
{
$fields = array_keys($data);
$values = array_values($data);
$sql = 'INSERT INTO ' . $table . ' (' . implode(', ', $fields) . ') VALUES (' . str_repeat('?, ', count($values) - 1) . '?)';
return $db->insert($sql, $values);
}
nag-4.1.3/bin/nag-convert-sql-shares-to-sqlng 0000775 0001750 0001750 00000002570 12233763366 017152 0 ustar jan jan #!/usr/bin/env php
get('horde_dir', null, 'pear.horde.org') . '/nag/';
}
require_once $baseDir . 'lib/Application.php';
Horde_Registry::appInit('nag', array('cli' => true));
require_once $baseDir . 'migration/6_nag_upgrade_sqlng.php';
$db = $injector->getInstance('Horde_Db_Adapter');
$migration = new NagUpgradeSqlng($db);
$delete = $cli->prompt('Delete existing shares from the NEW backend before migrating the OLD backend? This should be done to avoid duplicate entries or primary key collisions in the storage backend from earlier migrations.', array('y' => 'Yes', 'n' => 'No'), 'n');
if ($delete == 'y' || $delete == 'Y') {
$db->delete('DELETE FROM nag_sharesng');
$db->delete('DELETE FROM nag_sharesng_users');
$db->delete('DELETE FROM nag_sharesng_groups');
}
$migration->dataUp();
nag-4.1.3/bin/nag-create-missing-add-histories-sql 0000775 0001750 0001750 00000003045 12233763366 020112 0 ustar jan jan #!/usr/bin/env php
*/
if (file_exists(__DIR__ . '/../../nag/lib/Application.php')) {
$baseDir = __DIR__ . '/../';
} else {
require_once 'PEAR/Config.php';
$baseDir = PEAR_Config::singleton()
->get('horde_dir', null, 'pear.horde.org') . '/nag/';
}
require_once $baseDir . 'lib/Application.php';
Horde_Registry::appInit('nag', array('cli' => true));
$history = $GLOBALS['injector']->getInstance('Horde_History');
// Run through every tasklist.
$tasklists = $nag_shares->listAllShares();
foreach ($tasklists as $tasklist => $share) {
$cli->writeln("Creating default histories for $tasklist ...");
// List all tasks.
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklist);
$storage->retrieve();
$tasks = $storage->listTasks();
foreach ($tasks as $taskId => $task) {
$log = $history->getHistory('nag:' . $tasklist . ':' . $task['uid']);
$created = false;
foreach ($log as $entry) {
if ($entry['action'] == 'add') {
$created = true;
break;
}
}
if (!$created) {
$history->log('nag:' . $tasklist . ':' . $task['uid'], array('action' => 'add'), true);
}
}
}
$cli->writeln("** Default histories successfully created ***");
nag-4.1.3/bin/nag-import-vtodos 0000775 0001750 0001750 00000002741 12233763366 014476 0 ustar jan jan #!/usr/bin/env php
*/
$baseFile = __DIR__ . '/../../nag/lib/Application.php';
if (file_exists($baseFile)) {
require_once $baseFile;
} else {
require_once 'PEAR/Config.php';
require_once PEAR_Config::singleton()
->get('horde_dir', null, 'pear.horde.org') . '/nag/lib/Application.php';
}
Horde_Registry::appInit('nag', array('cli' => true));
// Read command line parameters.
if (count($argv) != 3) {
$cli->message('Too many or too few parameters.', 'cli.error');
usage();
}
$tasklist = $argv[1];
$user = $argv[2];
// Read standard input.
$vtodo = $cli->readStdin();
if (empty($vtodo)) {
$cli->message('No import data provided.', 'cli.error');
usage();
}
// Set user.
$registry->setAuth($user, array());
// Import data.
try {
$result = $registry->tasks->import($vtodo, 'text/calendar', $tasklist);
} catch (Horde_Exception $e) {
$cli->fatal($result->toString());
}
$cli->message('Imported successfully ' . count($result) . ' tasks', 'cli.success');
function usage()
{
$GLOBALS['cli']->writeln('Usage: nag-import-vtodos tasklist user');
exit;
}
nag-4.1.3/config/.htaccess 0000644 0001750 0001750 00000000016 12233763366 013441 0 ustar jan jan Deny from all
nag-4.1.3/config/conf.xml 0000664 0001750 0001750 00000002151 12233763366 013316 0 ustar jan jan
Storage System Settingssql
nag_tasksTasklist Handler Settingsdefault
Menu settingstrue
nag-4.1.3/config/hooks.php.dist 0000644 0001750 0001750 00000002772 12233763366 014454 0 ustar jan jan \0', $text);
// $text = preg_replace('/(bug|ticket|request|enhancement|issue):\s*#?(\d+)/i', '\0', $text);
//
// $text = preg_replace_callback('/\[\[note: ?(.*)\]\]/i', create_function('$m', 'return \'\' . htmlspecialchars($m[0]) . \'\';'), $text);
// $text = preg_replace_callback('/\[\[task: ?(.*)\]\]/i', create_function('$m', 'return \'\' . htmlspecialchars($m[0]) . \'\';'), $text);
//
// return $text;
// }
/**
* TODO
*/
// public function description_help()
// {
// return '
To create a link to a bug, use #123 where 123 is the bug number. To create a link to another task, use [[task: name]], where name is the beginning of the task name. To create a link to a note, use [[note: title]] where title is the beginning of the note title.
';
// }
}
nag-4.1.3/config/menu.php.dist 0000644 0001750 0001750 00000002347 12233763366 014273 0 ustar jan jan 'http://www.example.com/',
* 'text' => 'Example, Inc.',
* 'icon' => 'example.png',
* 'icon_path' => 'http://www.example.com/images/',
* 'target' => '_blank',
* 'onclick' => ''
* );
*
* You can also add a "separator" (a spacer) between menu items. To add a
* separator, simply add a new string to the $_menu array set to the text
* 'separator'. It should look like this:
*
* $_menu[] = 'separator';
*
* $Id$
*/
$_menu = array();
/* Add your custom entries below this line. */
nag-4.1.3/config/prefs.php 0000664 0001750 0001750 00000025605 12233763366 013510 0 ustar jan jan _("General Preferences"),
'label' => _("Display Preferences"),
'desc' => _("Change your task sorting and display preferences."),
'members' => array('tasklist_columns', 'sortby', 'altsortby', 'sortdir'),
);
$prefGroups['deletion'] = array(
'column' => _("General Preferences"),
'label' => _("Deleting Tasks"),
'desc' => _("Delete behaviour"),
'members' => array('delete_opt', 'purge_completed_interval', 'purge_completed_keep'),
);
$prefGroups['tasks'] = array(
'column' => _("General Preferences"),
'label' => _("Task Defaults"),
'desc' => _("Defaults for new tasks"),
'members' => array('default_due', 'default_due_days', 'default_due_time'),
);
$prefGroups['share'] = array(
'column' => _("Task List and Share Preferences"),
'label' => _("Default Task List"),
'desc' => _("Choose your default task list."),
'members' => array('default_tasklist'),
);
$prefGroups['sync'] = array(
'column' => _("Task List and Share Preferences"),
'label' => _("Synchronization Preferences"),
'desc' => _("Choose the task lists to use for synchronization with external devices."),
'members' => array('sync_lists'),
);
$prefGroups['notification'] = array(
'column' => _("Task List and Share Preferences"),
'label' => _("Notifications"),
'desc' => _("Choose if you want to be notified of task changes and task alarms."),
'members' => array('task_notification', 'task_notification_exclude_self', 'task_alarms_select'),
);
$prefGroups['external'] = array(
'column' => _("Task List and Share Preferences"),
'label' => _("External Data"),
'desc' => _("Show data from other applications or sources."),
'members' => array('show_external'),
);
// columns in the list view
$_prefs['tasklist_columns'] = array(
'value' => 'a:2:{i:0;s:8:"priority";i:1;s:3:"due";}',
'type' => 'multienum',
'enum' => array(
'tasklist' => _("Task List"),
'priority' => _("Priority"),
'assignee' => _("Assignee"),
'due' => _("Due Date"),
'start' => _("Start Date"),
'estimate' => _("Estimated Time")
),
'desc' => _("Select the columns that should be shown in the list view:")
);
// user preferred sorting column
$_prefs['sortby'] = array(
'value' => Nag::SORT_PRIORITY,
'type' => 'enum',
'enum' => array(
Nag::SORT_PRIORITY => _("Priority"),
Nag::SORT_NAME => _("Task Name"),
Nag::SORT_DUE => _("Due Date"),
Nag::SORT_START => _("Start Date"),
Nag::SORT_COMPLETION => _("Completed?"),
Nag::SORT_ESTIMATE => _("Estimated Time"),
Nag::SORT_ASSIGNEE => _("Assignee"),
Nag::SORT_OWNER => _("Task List")
),
'desc' => _("Sort tasks by:"),
);
// alternate sort column
$_prefs['altsortby'] = array(
'value' => Nag::SORT_DUE,
'type' => 'enum',
'enum' => array(
Nag::SORT_PRIORITY => _("Priority"),
Nag::SORT_NAME => _("Task Name"),
Nag::SORT_DUE => _("Due Date"),
Nag::SORT_START => _("Start Date"),
Nag::SORT_COMPLETION => _("Completed?"),
Nag::SORT_ESTIMATE => _("Estimated Time"),
Nag::SORT_ASSIGNEE => _("Assignee"),
Nag::SORT_OWNER => _("Task List")
),
'desc' => _("Then:"),
);
// user preferred sorting direction
$_prefs['sortdir'] = array(
'value' => Nag::SORT_ASCEND,
'type' => 'enum',
'enum' => array(
Nag::SORT_ASCEND => _("Ascending"),
Nag::SORT_DESCEND => _("Descending")
),
'desc' => _("Sort direction:"),
);
// preference for delete confirmation dialog.
$_prefs['delete_opt'] = array(
'value' => 1,
'type' => 'checkbox',
'desc' => _("Do you want to confirm deleting entries?"),
);
// how often to purge completed tasks?
$_prefs['purge_completed_interval'] = array(
'value' => 0,
'type' => 'enum',
'enum' => array_merge(array(0 => _("Never")), Horde_LoginTasks::getLabels()),
'desc' => _("Purge completed tasks how often:")
);
$_prefs['purge_completed_keep'] = array(
'value' => 30,
'type' => 'number',
'desc' => _("Purge completed tasks older than this amount of days.")
);
// default to tasks having a due date?
$_prefs['default_due'] = array(
'value' => 0,
'type' => 'checkbox',
'desc' => _("When creating a new task, should it default to having a due date?"),
);
// default number of days out for due dates
$_prefs['default_due_days'] = array(
'value' => 1,
'type' => 'number',
'zero' => true,
'desc' => _("When creating a new task, how many days in the future should the default due date be (0 means today)?"),
);
// default due time
$_prefs['default_due_time'] = array(
'value' => 'now',
'type' => 'enum',
'enum' => array(),
'desc' => _("What do you want to be the default due time for tasks?"),
'on_init' => function($ui) {
$enum = array('now' => _("The current hour"));
$twentyfour = $GLOBALS['prefs']->getValue('twentyFour');
for ($i = 0; $i < 24; ++$i) {
$value = sprintf('%02d:00', $i);
$enum[$value] = $twentyfour
? $value
: sprintf('%02d:00 ' . ($i >= 12 ? _("pm") : _("am")), ($i % 12 ? $i % 12 : 12));
}
$ui->prefs['default_due_time']['enum'] = $enum;
}
);
// new task notifications
$_prefs['task_notification'] = array(
'value' => '',
'type' => 'enum',
'enum' => array(
'' => _("No"),
'owner' => _("On my task lists only"),
'show' => _("On all shown task lists"),
'read' => _("On all task lists I have read access to")
),
'desc' => _("Choose if you want to be notified of new, edited, and deleted tasks by email:"),
);
$_prefs['task_notification_exclude_self'] = array(
'value' => 0,
'locked' => false,
'type' => 'checkbox',
'desc' => _("Don't send me a notification if I've added, changed or deleted the task?")
);
// alarm methods
$_prefs['task_alarms_select'] = array(
'type' => 'special',
'handler' => 'Nag_Prefs_Special_TaskAlarms',
'suppress' => function() {
return empty($GLOBALS['conf']['alarms']['driver']);
}
);
$_prefs['task_alarms'] = array(
'value' => 'a:1:{s:6:"notify";a:0:{}}'
);
// show data from other applications that can be listed as tasks?
$_prefs['show_external'] = array(
'value' => 'a:0:{}',
'type' => 'multienum',
'enum' => array('whups' => $GLOBALS['registry']->get('name', 'whups')),
'desc' => _("Show data from any of these other applications in your task list?"),
'suppress' => function() {
return !$GLOBALS['registry']->hasMethod('getListTypes', 'whups');
}
);
// show complete/incomplete tasks?
$_prefs['show_completed'] = array(
'value' => 1,
'type' => 'enum',
'enum' => array(
Nag::VIEW_ALL => _("All tasks"),
Nag::VIEW_INCOMPLETE => _("Incomplete tasks"),
Nag::VIEW_COMPLETE => _("Complete tasks"),
Nag::VIEW_FUTURE => _("Future tasks")
),
'desc' => _("Show complete, incomplete, or all tasks in the task list?"),
);
// default tasklists
// Set locked to true if you don't want users to have multiple task lists.
$_prefs['default_tasklist'] = array(
'value' => '',
'type' => 'enum',
'enum' => array(),
'desc' => _("Your default task list:"),
'on_init' => function($ui) {
$enum = array();
foreach (Nag::listTasklists(false, Horde_Perms::EDIT, false) as $key => $val) {
$enum[$key] = Nag::getLabel($val);
}
$ui->prefs['default_tasklist']['enum'] = $enum;
},
'on_change' => function() {
$GLOBALS['injector']->getInstance('Nag_Factory_Tasklists')
->create()
->setDefaultShare($GLOBALS['prefs']->getValue('default_tasklist'));
$sync = @unserialize($GLOBALS['prefs']->getValue('sync_lists'));
$haveDefault = false;
$default = Nag::getDefaultTasklist(Horde_Perms::EDIT);
foreach ($sync as $cid) {
if ($cid == $default) {
$haveDefault = true;
break;
}
}
if (!$haveDefault) {
$sync[] = $default;
$GLOBALS['prefs']->setValue('sync_lists', serialize($sync));
}
}
);
// store the task lists to diplay
$_prefs['display_tasklists'] = array(
'value' => 'a:0:{}'
);
// Calendars use for synchronization
$_prefs['sync_lists'] = array(
'value' => 'a:0:{}',
'type' => 'multienum',
'enum' => array(),
'desc' => _("Select the tasklists that, in addition to the default, should be used for synchronization with external devices:"),
'on_init' => function($ui) {
$enum = array();
$sync = @unserialize($GLOBALS['prefs']->getValue('sync_lists'));
if (empty($sync)) {
$GLOBALS['prefs']->setValue('sync_lists', serialize(array(Nag::getDefaultTasklist())));
}
foreach (Nag::listTasklists(false, Horde_Perms::EDIT, false) as $key => $list) {
if ($list->getName() != Nag::getDefaultTasklist(Horde_Perms::EDIT)) {
$enum[$key] = Nag::getLabel($list);
}
}
$ui->prefs['sync_lists']['enum'] = $enum;
},
'on_change' => function() {
$sync = @unserialize($GLOBALS['prefs']->getValue('sync_lists'));
$haveDefault = false;
$default = Nag::getDefaultTasklist(Horde_Perms::EDIT);
foreach ($sync as $cid) {
if ($cid == $default) {
$haveDefault = true;
break;
}
}
if (!$haveDefault) {
$sync[] = $default;
$GLOBALS['prefs']->setValue('sync_lists', serialize($sync));
}
if ($GLOBALS['conf']['activesync']['enabled']) {
try {
$sm = $GLOBALS['injector']->getInstance('Horde_ActiveSyncState');
$sm->setLogger($GLOBALS['injector']->getInstance('Horde_Log_Logger'));
$devices = $sm->listDevices($GLOBALS['registry']->getAuth());
foreach ($devices as $device) {
$sm->removeState(array(
'devId' => $device['device_id'],
'id' => Horde_Core_ActiveSync_Driver::TASKS_FOLDER_UID,
'user' => $GLOBALS['registry']->getAuth()
));
}
$GLOBALS['notification']->push(_("All state removed for your ActiveSync devices. They will resynchronize next time they connect to the server."));
} catch (Horde_ActiveSync_Exception $e) {
$GLOBALS['notification']->push(_("There was an error communicating with the ActiveSync server: %s"), $e->getMessage(), 'horde.error');
}
}
}
);
nag-4.1.3/config/routes.php 0000664 0001750 0001750 00000000325 12233763366 013702 0 ustar jan jan connect('/t/complete',
array(
'controller' => 'CompleteTask',
));
$mapper->connect('/t/save',
array(
'controller' => 'SaveTask',
));
nag-4.1.3/docs/vtodo/todo.ics 0000644 0001750 0001750 00000000702 12233763366 014130 0 ustar jan jan BEGIN:VCALENDAR
BEGIN:VTODO
DTSTAMP:19980130T134500Z
SEQUENCE:2
UID:<1234@example.com>
ORGANIZER:MAILTO:
ATTENDEE;PARTSTAT=ACCEPTED:MAILTO:
DUE:19980415T235959
STATUS:NEEDS-ACTION
SUMMARY:Submit Income Taxes
BEGIN:VALARM
ACTION:AUDIO
TRIGGER;VALUE=DATE-TIME:19980403T120000
ATTACH;FMTTYPE=audio/basic:http://host.com/pub/audio-files/ssbanner.aud
REPEAT:4
DURATION:PT1H
END:VALARM
END:VTODO
END:VCALENDAR
nag-4.1.3/docs/CHANGES 0000600 0001750 0001750 00000062425 12233763366 012325 0 ustar jan jan ------
v4.1.3
------
[jan] SECURITY: Fix XSS vulnerabilities when deleting task lists.
[jan] Fix updating alarm if completing a task recurrence.
[jan] Fix editing tasks via CalDAV (Bug #12745).
------
v4.1.2
------
[jan] Fix highlighting task type tabs in list view.
[jan] Fix fatal errors if DAV support is disabled (Bug #12481).
[mjr] Fix requesting changes by modification sequence (Bug #12507, Thomas
Jarosch ).
------
v4.1.1
------
[jan] Display CalDAV URL of system tasklists (Bug #12342).
[mjr] Only query Horde_History when it's required.
[jan] Fix system tasklist listing via WebDAV.
[rla] Add system tasklist support for Nag CalDAV access (Request #12342).
[jan] Fix task description tooltip (Bug #12421).
[mjr] Persist the tasklist and parent when creating a new task and using "Save
and New" (Bug #12400).
[mjr] Fix tag browsing of shared tasklists (Bug #12405).
[mjr] Fix importing VTODOs containing RELATED-TO fields (Bug #12355).
[mjr] Add API methods for using history modification sequences.
------
v4.1.0
------
[jan] Don't include external tasks in listTasks() API results.
-----------
v4.1.0beta2
-----------
[jan] Fix incorrect dependencies.
-----------
v4.1.0beta1
-----------
[jan] Add CalDAV server support (Request #4267).
[jan] Update design to closer match the Horde 5 UI.
------
v4.0.3
------
[jan] Fix recurring tasks in Kolab driver (Thomas Jarosch
, Bug #12222).
[mjr] Fix setting alarm time when syncing (Bug #12201,
).
[mjr] Fix issue causing due dates to be offset when syncing (Bug #12200,
)
[jan] Fix listing alarms of recurring tasks.
[jan] Fix default task list in portal block (Bug #12133).
[jan] Fix displaying of due dates of recurring events in some views.
[jan] Update broken rows due to migration bug in PostgreSQL (Bug #12101).
[mjr] Fix uncompleting tasks from task view in smartmobile view
(ctimoteo@sapo.pt, Bug #12098).
[jan] Include parent tasks in cost object labels.
[jan] Only return completed tasks up to a week old as cost objects.
[mjr] Set correct timezone on incoming ActiveSync tasks (Bug #12053).
[jan] Update French translation (Paul De Vlieger
).
------
v4.0.2
------
[mjr] Fix closing the quick task dialog when cancel is pressed (Bug #11848).
[mjr] Fix displaying and editing tasks from search results (Bug #11847).
[jan] Update Basque translation (Ibon Igartua ).
[mjr] Fix incoming task sync with certain combinations of due date values.
[mjr] Fix quick task addition without session cookies enabled (Bug #11875,
Thomas Jarosch ).
[jan] Fix editing tasks from the merged list view in smartmobile mode (Bug
#11840).
[jan] Use default Kolab folder for default taks list preference.
[jan] Mark Kolab folder as default when changing default task list preference.
------
v4.0.1
------
[jan] Use Kolab modification and creation dates in the UI (Bug #11591).
[gwr] Ignore empty dates (Bug #11736).
[jan] Use encoded UIDs as IDs in Kolab driver.
[mjr] Fix support for due/start dates for certain ActiveSync clients (Bug
#11693).
[jan] Fix importing tasks from iCalendar (Bug #11681).
[jan] Fix listing of recurring tasks via timeobjects API.
------
v4.0.0
------
[jan] Update Polish translation (Krzysztof Kozera ).
---------
v4.0.0RC1
---------
[jan] Fix export links for tasklists (Bug #11576).
[mjr] Fix various issues with importing CSV files.
[mjr] Format datetime fields when exporting a task as a hash (Bug #11537).
[jan] Fix retrieving tasks from Kolab backend by UID (Bug #11532).
[jan] Fix toggling tasklists (Bug #11511).
[jan] Fix listing tasks from Kolab backends.
[jan] Fix sending alarm e-mail messages.
-----------
v4.0.0beta2
-----------
[jan] Synchronize tags with Kolab categories.
[mjr] Fix deleting tasks from the task form.
[mjr] Implement Purge Completed Tasks LoginTask (Request #2520).
[gwr] Mark the initial default share as such with the Kolab backend.
[gwr] Always mark the initial share as default tasklist.
[jan] Move complete task list management into the sidebar.
[jan] Allow to edit task list color in traditional view.
[mjr] Tweak display of smartlist links in list view.
[mjr] Fix fatal recursion error when searching with smartlists were present.
-----------
v4.0.0beta1
-----------
[mjr] Add support for smart tasklists.
[mjr] Migrate categories to tags.
[jan] Fix fatal error if the task form doesn't validate.
[jan] Add recurring tasks (Request #2150).
----------
v3.0.9-git
----------
[mms] Fix getTasklist() API call.
[jan] Fix changing sort direction.
[jan] Fix sorting by owner if displaying external task sources.
[jan] Update Turkish translation (İstanbul Technical University).
------
v3.0.8
------
[jan] Update Swedish translation (Per Olof Ljungmark ).
[jan] Update Italian translation (Massimo Malabotta ).
[jan] Update Hungarian translation (Zoltán Németh ).
------
v3.0.7
------
[jan] Fix setting custom alarm methods (Bug #9543).
[jan] Update Japanese translation (Hiromi Kimura ).
------
v3.0.6
------
[jan] Don't display task details of private tasks via the API (Bug #10712).
[jan] Use correct locale when parsing quick tasks (thpo+horde@dotrc.de, Bug
#10720).
------
v3.0.5
------
[cjh] Nicer date/time input with fewer input fields and helper javascript.
[mjr] Add ability to choose which tasklists to synchronize.
[mjr] Fix task export to ActiveSync for tasks with no due dates or reminders.
------
v3.0.4
------
[jan] Add missing parameters when PUTing tasks (Bug #10545).
[mjr] Fix removeUserData implementation.
[gwr] Fix Kolab object attribute handling for tasks.
[gwr] Avoid including the owner name into the default share.
------
v3.0.3
------
[jan] Don't load all shares with requested permissions from the backend if
$conf['share']['hidden'] is enabled.
------
v3.0.2
------
[jan] Add Ukrainian translation (Andriy Kopystyansky ).
------
v3.0.1
------
[jan] Fix reversed logic of displaying sub-task tree icons depending on the
text direction (Bug #10033).
[jan] Fix encoding of non-ascii characters in parent task drop-down.
[mjr] Fix editing tasklists (Bug #9965).
----
v3.0
----
[jan] Fix application-specific permission checks (Bug #9786).
[mjr] Datatree share to SQL upgrade script refactored for Horde 4.
[jan] Fix creating new tasks through WebDAV (Bug #9675).
[jan] Move all executable scripts to bin/ and prefix with nag-.
--------
v3.0-RC1
--------
[jan] Update installation and upgrade instructions.
[gwr] Adapted the Kolab driver to Horde 4.
----------
v3.0-BETA1
----------
[mjr] Fix saving tasks via PUT method.
[mjr] Fix deleting task lists.
[mjr] Fix hiding the External prefs group (Bug #9643).
-----------
v3.0-ALPHA1
-----------
[jan] Provide default configuration files instead of .dist versions.
[jan] Send alarm notifications with HTML part and convert to Horde_View.
[jan] Default task lists no longer have the user name as the ID.
[jan] Create a default task list if the user doesn't own any yet.
[jan] Add start date to possible colums in task list (Joel Smith
, Request #9083).
[jan] Add system task lists (Request #2059).
[jan] Set colors per task list (Request #7480).
[cjh] Quick Add support: there is a javascript UI element for quickly adding
tasks, and an API method (tasks/quickAdd) that provides the same
functionality.
[jan] Add individual notification methods for single tasks (Alfonso Marín
Marín ).
------
v2.3.7
------
[jan] Add upgrade scripts for next-generation SQL share driver.
------
v2.3.6
------
[jan] Remove stray closing tag when displaying the date of completion.
[mjr] Do not remove history entries when removing user data (Bug #8755).
------
v2.3.5
------
[jan] Set an alarm to one minute, if users try to set an alarm without time.
[jan] Add missing preference for not sending update notifications to yourself
(Joel Smith , Request #8978).
[gwr] Fix organizer field not saved in tasks (kolab/issue3888).
------
v2.3.4
------
[jan] Add and fix Oracle-specific SQL scripts.
[jan] Fix importing due date attribute from iCalendar (Bug #8644).
[jan] Fix charset when exporting tasks to iCalendar 2.0 (Bug #8637).
[jan] Add Croatian translation (Matej Vela ).
------
v2.3.3
------
[mms] Upgrade prototype.js to v1.6.1.
[jan] Fix synchronization with output compression enabled (Bug #7769).
[jan] Display application name as task list name when listing external tasks.
[jan] Fix importing vTodo data including more iCalendar components.
------
v2.3.2
------
[jan] Don't allow to set alarms if no due date has been set.
[cjh] Work around BC break with Horde versions before 3.2 (Bug #7820).
[cjh] Add URL access to tasks by "starts-with" search on the task name.
[cjh] Add hooks for altering the displayed task description and showing help
text next to the task description entry field.
[cjh] Add estimated time to the fields available in the task list.
[jan] Fix link escaping in notification messages (Alfonso Marín Marín
, Bug #7509).
------
v2.3.1
------
[cjh] Add a PostgreSQL-specific upgrade script for 2.2 to 2.3.
[cjh] Fix fatal error when completing tasks (Bug #7400).
[mms] Upgrade prototype.js to v1.6.0.3.
----
v2.3
----
[jan] Change group field in shares table to work with LDAP groups (Bug #6883).
[jan] Log completion date if adding a completed task (Bug #7275).
[jan] Fix user name conversion with user hooks in the task list panel
(Bug #7366).
[jan] Fix displayed WebDAV subscription URLs in the task list manager.
--------
v2.3-RC1
--------
[jan] Log moving of tasks in the history backend (Bug #3207).
[jan] Fix deleting all tasks over WebDAV (Bug #7004).
[jan] Add Estonian translation (Alar Sing ).
[jan] Send a more detailed notification message and use the recipient's
preferred language and date/time format after a task has changed.
[jan] Add Basque translation (Euskal Herriko Unibertsitatea EHU/UPV
).
[jan] Fix task relationship getting lost when importing tasks
(tkrah@fachschaft.imn.htwk-leipzig.de, bb.apc.ag, Bug #6770).
[jan] Add preference to set columns for the list view.
[jan] Allow to set task assignee.
[jan] Add options to export screen for choosing task lists and task states.
[mjr] Remove user permissions on all shares when deleting a user.
[mjr] Fix issue with removeUserData api that caused the deleted user's task list
to not be deleted (Bug #6969).
----
v2.2
----
[cjh] Improve resource usage in datatree_to_sql share migration script
(Bug #6740).
[jan] Allow to import all fields that can be exported.
[cjh] Fix displayed WebDAV subscription URLs in the panel (Bug #6709).
--------
v2.2-RC3
--------
[bak] Move tasklists into per-owner subdirectories when viewed through WebDAV.
(Request #6595)
[cjh] Apply fix for http://dev.rubyonrails.org/ticket/11473 to prototype.js
(Request #6590).
[cjh] Add an upgrade script for the new SQL share driver (Request #6109).
[jan] Correctly determine default task list in the API (SyncML, WebDAV).
[cjh] Show who created or made the last change to a task along with the date
(Request #6305).
[jan] Add Turkish translation (METU ).
--------
v2.2-RC2
--------
[jan] SECURITY: Fix privilege escalation in Horde API.
[cjh] SECURITY: Fix missing ownership validation on share changes.
[cjh] Fix sorting tasks by Task list.
[jan] Fix tasks losing their parent task when being completed (Bug #6035).
[cjh] Add the ability to filter the list of task lists in the panel.
[cjh] New share management UI that doesn't require JavaScript.
[jan] Fix 2006-04-18_add_creator_and_assignee_fields.php upgrade script.
--------
v2.2-RC1
--------
[jan] Implement WebDAV access.
[jan] Add exportTasklist() API method.
[jan] Add SQL upgrade script.
[jan] Show estimated time including sub-tasks.
[cjh] Fix generation of UIDs with PHP 5.2+.
----------
v2.2-ALPHA
----------
[cjh] Add preferences for turning due dates on by default, setting the
default due date's number of days in the future, and setting the
default due date's time.
[cjh] Add vCalendar 2.0 alarm export (munzli@olmero.ch, Bug #4851).
[jan] Add sub-tasks.
[jan] Add start dates for tasks.
[jan] Add support for the Horde_Alarm framework (requires Horde 3.2).
[cjh] Default to a javascript quick search, if available, with options
for the full search and a direct link to full search if javascript
is unavailable.
[cjh] Allow explicitly searching All, Incomplete, or Completed tasks
(Request #4222).
[cjh] Allow configuring the tasklists that the task summary block shows,
and let the user set a title for the block to differentiate multiple
instances (Request #2388).
[cjh] Show task description tooltips in the task summary block (Request #3444).
[cjh] Store completion date and add history entries for task completion.
[cjh] Move tasklist selection/deselection to a collapsible panel.
[mas] Conform to WCAG 1.0 Priority 2/Section 508 accessibility guidelines.
(Request #4080)
[cjh] Allow using the checkboxes to mark tasks as incomplete as well
as complete (michael.sheldon@credativ.de, Request #4250).
[cjh] Sort date fields correctly with JS table sorting.
[jan] Add private flag.
[jan] Add a field for estimated time being spent on a task.
[mdj] Add support for split read/write database.
[mdj] Add failover support for SQL backend.
[cjh] Add a tree block for showing current alarms.
------
v2.1.4
------
[jan] SECURITY: Fix privilege escalation in Horde API.
[cjh] SECURITY: Fix missing ownership validation on share changes.
[cjh] Send iCalendar data as UTF-8.
------
v2.1.3
------
[jan] Show alarms for overdue tasks too.
[jan] Add Catalan translation (Jordi Giralt ).
------
v2.1.2
------
[jan] Show error message if imported file didn't contain tasks.
[jan] Add categories from imported contacts to the user's categories.
[jan] Fix import of CSV data.
[jan] Add Slovenian translation (Duck ).
[jan] Show personal tasklist by default with disabled preferences (Bug #4078).
------
v2.1.1
------
[jan] Only load completed/incomplete tasks from backend where sufficient
(Request #2387).
[jan] Don't show tooltips for tasks without read permissions (Bug #3836).
[jan] Fix fatal error when dealing with very old tasks that don't have a UID
yet (Bug #3818).
[jan] Add 1.1 to 2.x upgrade script for Oracle.
----
v2.1
----
[ben] Better support for MS-SQL.
--------
v2.1-RC1
--------
[jan] Add Portuguese translation (Manuel Menezes de Sequeira
).
[jan] Add preference to send email notifications to users when tasks have
been added, edited, or deleted in their calendars (kevin_myer@iu13.org,
Request #2332).
[jan] Confirm task deletions (Request #1155).
[jan] Add CLI script to import vTodo data.
[cjh] Add support for dynamic re-sorting of the task list, including saving
the sort preferences on any changes.
[cjh] Deprecate the DataTree tasks driver.
[cjh] Make the completed checkbox in task view functional
(martin@mein-horde.de, Bug #2157).
[mas] Change any output of and tags to and for better
accessibility support.
[jan] Add permissions to restrict number of tasks.
[jan] Add print view to task list (kevin_myer@iu13.org, Bug #1875).
[cjh] Add ics.php, which can be used for integration with Sunbird,
iCal, et. al.
[jan] Show category colors in portal block (Brandon Knitter
, Bug #1126).
------
v2.0.4
------
[cjh] Close several XSS vulnerabilities with task and tasklist data.
------
v2.0.3
------
[jan] Allow to import more than one task from vTodo data at once.
------
v2.0.2
------
[cjh] Add a PostgreSQL upgrade script (Bug #1780).
[cjh] Use bind variables in the SQL driver (selsky@columbia.edu, Bug #1681).
[cjh] Fix problem where you couldn't select any tasklists once you'd
deselected all of them.
[jan] Add shortcut icon (favicon.ico).
[jan] Fix warnings if no tasks to export exist.
[jan] Fix CSV imports (Bug #1387).
[cjh] Allow hiding the Task List column in the List view.
------
v2.0.1
------
[jan] Allow to select the "Unfiled" category in the portal block (Mathieu
Clabaut , Bug #1237).
[cjh] Don't highlight the New Task menu item when editing tasks.
[cjh] Use a checkbox for editing completed/incomplete in the task
edit view, to match the list view (Bug #1212).
[jan] Fix print button.
[jan] Add Japanese translation (Hiromi Kimura ).
----
v2.0
----
[cjh] UIDs need to be stored in the History system with a nag: prefix and
with the tasklist_id so as not to confuse different instances of the
same task (if two users both have a task on their seperate tasklists,
they should have unique histories for that task).
[jan] Fix upgrade script to create unique IDs.
--------
v2.0-RC3
--------
[jan] Add Polish translation (Piotr Kuczynski ).
[cjh] Add My Tasklists menu entry.
--------
v2.0-RC2
--------
[jan] Add Latvian translation (Janis Eisaks ).
--------
v2.0-RC1
--------
[jan] Tweak layout of the summary block.
---------
v2.0-BETA
---------
[jan] Add special black-on-white styles for message printing.
[cjh] Users can now specify a secondary sort column
(Andrew Coleman ).
[cjh] Preserve searches while re-sorting (Francois Marier ).
[jan] Add access keys.
[cjh] Add Kolab drivers (Stuart Bingë ).
----------
v2.0-ALPHA
----------
[cjh] GUIDs now only contain nag: and the task ID - sharename is not needed.
[cjh] Task IDs are now 32-character unique strings, to be useable as GUIDs.
The SQL table definition has changed; conversion scripts are in scripts/.
[jan] Add Indonesian language (Slamin ).
[cjh] Add import and export in vTodo (iCalendar) format.
[cjh] Make sure that the correct categories for a task's current tasklist
are always used.
[cjh] Track addition, modification, and deletion of tasks
with the Horde History:: API.
[cjh] Change how tasks are stored in the SQL driver. scripts/create_sequence.php
will update an existing database with no loss of data.
[cjh] Show tooltips containing task descriptions.
[cjh] Add a preference for the default category
(Brian Keifer ).
[cjh] Make the due date in the summary block configurable
(Mathieu CLABAUT ).
[cjh] Add task alarms (Mathieu CLABAUT ).
[jan] Add UTF-8 support and charset parameter for backend drivers.
[cjh] Only show selected task categories on the summary screen
(John Morrissey ).
[cjh] Much more comprehensive permissions checking, and support for
guest access.
[cjh] Use the global shares editing page for changing/assigning share permissions.
[cjh] Add options for showing, sorting by, and grouping by task owner
(Brian Keifer ).
[mac] Allow importing onto any available Task list.
[cjh] Add an option to show only complete tasks, and add links on the
task view to switch between all, complete, and incomplete.
[mac] Add shared task lists.
------
v1.1.3
------
[jan] Close XSS when setting the parent frame's page title by javascript (cjh).
------
v1.1.2
------
[jan] Add Polish translation (Piotr Kuczynski ).
[cjh] Fix changing of task attributes that have not been set previously
(rvs@angara.ru, Bug #569).
------
v1.1.1
------
[jan] Add Indonesian language (Slamin ).
[jan] Add Arabic (Syria) translation (Platinum Development Team
).
[jan] Add Romanian translation (Eugen Hoanca ,
Marius Dragulescu ).
[jon] Display the task navbar above the description in the task view, as well
(John Morrissey ).
----
v1.1
----
[jan] Add Greek translation (Stefanos I. Dimitriou ).
[jan] Add print task button (mac).
[jan] Add Slovak translation (Ivan Noris ).
[jan] Add ability to create new categories (mac).
[jan] Add Nag::addParameter().
[jan] Add Norwegian Bokmaal translation (Torstein S. Hansen ).
[jan] Add Bulgarian translation (Miroslav Pendev ).
[jan] Add preferences for showing priorities and due dates in the summary
(Brian Keifer ).
[jan] Add Lithuanian translation (Darius Matuliauskas ).
[jan] Replace = with ).
[jan] Add Danish translation (Martin List-Petersen ).
----
v1.0
----
[jan] Add Hungarian translation (Laszlo L. Tornoci ).
[jan] Add Simplified Chinese translation (Peter Wang ).
[jan] Add Korean translation (J.I Kim ).
[jan] Add Norwegian Nynorsk translation (Per-Stian Vatne ).
[cjh] Completed tasks aren't overdue, no matter what the due date.
[cjh] Filter out completed tasks earlier if requested, so the task count
is correct.
[cjh] Simplify color scheme a bit.
[cjh] Add category column.
[cjh] Remove task_added column.
[cjh] Keep track of when a task was last modified (for sync purposes).
[jan] Add Finnish translation (Tero Matinlassi ).
[jan] Add Brazilian Portuguese translation (Antonio Dias ).
[cjh] Close a potential problem with register_globals On and $js_onLoad.
[cjh] Add new task link to the summary (Quinn Wilson ).
[cjh] Use the new PrefsUI class.
[cjh] Remove the STORAGE_* constants in favor of PEAR_Errors.
[cjh] Add a preference to not show completed tasks in the task list.
[cjh] Fix completion widget in the task modification screen.
[jon] Adapt to the new Horde::img() syntax.
[cjh] Switch output compression to ob_gzhandler().
[cjh] Use NAG_TEMPLATES constant for all template paths.
[cjh] Use $registry->get() for all Registry information.
[cjh] Prefix all application constants with NAG_.
[cjh] Use the new Notification system.
[jon] Enable the "portability" option in the SQL driver.
[jan] Remove the standard value for the language preference. The language to
fall back to should be set Horde wide in lang.php instead.
[jan] Add Swedish translation (Andreas Dahlén ).
[jan] Add Traditional Chinese translation (David Chang ).
[bjn] Change 'en' and 'en_EN' locales to 'en_US' (default).
[cjh] Change Nag_Storage:: to Nag_Driver::.
[cjh] Allow setting priority for multiple tasks in the list view.
[cjh] Change task priorities to be 1-5 (matches Palm todo list).
[jon] Apply a "strike-through" style to closed (completed) tasks.
[jon] Remove task dependency code.
[jon] Add completion status. (Paul Cooper )
[jan] Add French translation (Mikhaël Janson ).
[cjh] Let the Registry handle retrieving preferences.
[jan] Due tasks can now be shown in Kronolith.
[jan] Add Italian translation (Giovanni Meneghetti ).
[cjh] Add Czech translation (pchytil@asp.ogi.edu).
[cjh] Add Russian translation (Ignat Ikryanov ).
[avsm] Replace $conf['paths'] with the $registry equivalents.
[jon] Updated to reflect the changes in PEAR DB's quoteString().
[cjh] Add $conf['menu']['apps'] support.
[cjh] Add low and high priority icons.
[cjh] Show a note icon if a task has a description.
[jon] Added the concept of task priorities (low, medium, high).
[jon] Return to the task detail view after additions or modifications.
[jon] Allow a task to be dependent on another task.
[cjh] Add translation framework.
[jon] Added Nag::formatDate() to format internal date values.
------
v0.0.1
------
[cjh] Start application-specific constants at 100 and prefix them with NAG_.
[cjh] Add a Horde summary API function.
[jon] Added initial help text.
[cjh] Use prefs.gif and generic prefs templates from Horde.
[jon] Display task descriptions using a proportional (fixed) font.
[jon] Added externally-accessible API for registry integration.
[jon] HORDE_BASE is now defined in lib/base.php instead of config/conf.php.
[jon] Added detailed installation documentation (docs/INSTALL).
[jon] Moved task retrieval out of base.php for more efficiency and granularity.
[jon] Display overdue tasks in a different color.
[jon] Added status support for Horde messages / errors.
[cjh] Make multiple task deletion from the task list work.
[cjh] Make task storage work when deleting more than one task.
[jon] Added rudimentary searching capabilities.
[cjh] Define the HORDE_BASE constant in config/conf.php, and use it when
referring to any of Horde's files.
[jon] Added the ability to add, remove, and delete tasks.
[jon] Made the task view date and time formats separately configurable.
[jon] Added individual task viewing (with descriptions).
[jon] Added sorting to the task list.
[jon] Added initial preferences support.
[jon] Added an initial "List Tasks" implementation
[jon] Added the 'sql' storage implementation.
nag-4.1.3/docs/CREDITS 0000664 0001750 0001750 00000011521 12233763366 012353 0 ustar jan jan ======================
Nag Development Team
======================
Core Developers
===============
- Chuck Hagenbuch
- Jan Schneider
Localization
============
===================== ======================================================
Arabic (Syria) Platinum Development Team
Basque Euskal Herriko Unibertsitatea
Brazilian Portuguese Antonio Dias
Fabio Gomes
Luis Felipe Marzagao
Eduardo de Carli
Bulgarian Miroslav Pendev
Catalan Jordi Giralt
Chinese (Simplified) Peter Wang
Chinese (Traditional) David Chang
Croatian Matej Vela
Czech Pavel Chytil
Danish Martin List-Petersen
Brian Truelsen
Niels Baggesen
Dutch Jan Kuipers
Ariën Huisken
Arjen de Korte
Estonian Alar Sing
Finnish Tero Matinlassi
Leena Heino
French Mikhaël Janson
Benoit St-André
Pierre Lachance
Thierry Thomas
Yannick Sebastia
Laurent Foucher
Paul De Vlieger
German Jan Schneider
Greek Stefanos I. Dimitriou
Silligardos Xristoforos
Anagnostopoulos Apostolis
Konstantinos C. Milosis
Hungarian Laszlo L. Tornoci
Andras Galos
Zoltán Németh
Kiraly Laszlo
Indonesian Slamin
Italian Giovanni Meneghetti
Marco Pirovano
Cristian Manoni
Massimo Malabotta
Japanese Hiromi Kimura
Korean J.I Kim
Latvian Janis Eisaks
Lithuanian Darius Matuliauskas
Vilius Šumskas
Norwegian Bokmaal Torstein S. Hansen
Norwegian Nynorsk Per-Stian Vatne
Polish Piotr Kuczynski
Piotr Adamcio
Tadeusz Lesiecki
Piotr Tarnowski
Krzysztof Kozera
Portuguese Manuel Menezes de Sequeira
Romanian Eugen Hoanca
Marius Dragulescu
Russian Ignat Ikryanov
Alexey Zakharov
Slovak Ivan Noris
Martin Matuška
Slovenian Duck
Spanish Raúl Alvarez Venegas
Manuel Perez Ayala
Juan C. Blanco
Swedish Andreas Dahlén
Joaquim Homrighausen
Per Olof Ljungmark
Turkish Istanbul Technical University IT Department
Middle East Technical University
Ukrainian Andriy Kopystyansky
===================== ======================================================
Inactive Developers
===================
- Jon Parise
nag-4.1.3/docs/INSTALL 0000664 0001750 0001750 00000013423 12233763366 012367 0 ustar jan jan ===================
Installing Nag H5
===================
:Contact: nag@lists.horde.org
.. contents:: Contents
.. section-numbering::
This document contains instructions for installing the Nag web-based todo list
application on your system.
For information on the capabilities and features of Nag, see the file README_
in the top-level directory of the Nag distribution.
Prerequisites
=============
To function properly, Nag **requires** the following:
1. A working Horde installation.
Nag runs within the `Horde Application Framework`_, a set of common tools
for web applications written in PHP. You must install Horde before
installing Nag.
.. Important:: Nag H5 requires version 5.0+ of the Horde Framework -
earlier versions of Horde will **not** work.
.. Important:: Be sure to have completed all of the steps in the
`horde/docs/INSTALL`_ file for the Horde Framework before
installing Nag. Many of Nag's prerequisites are also
Horde prerequisites. Additionally, many of Nag's optional
features are configured via the Horde install.
.. _`Horde Application Framework`: http://www.horde.org/apps/horde
2. SQL support in PHP *or* a configured Kolab Server.
Nag will store its data in either an SQL database or on a Kolab Server.
If you use SQL, build PHP with whichever SQL driver you require; see the
Horde INSTALL_ file for details.
Installing Nag
==============
The **RECOMMENDED** way to install Nag is using the PEAR installer.
Alternatively, if you want to run the latest development code or get the
latest not yet released fixes, you can install Nag from Git.
Installing with PEAR
~~~~~~~~~~~~~~~~~~~~
First follow the instructions in `horde/docs/INSTALL`_ to prepare a PEAR
environment for Horde and install the Horde Framework.
When installing Nag through PEAR now, the installer will automatically install
any dependencies of Nag too. If you want to install Nag with all optional
dependencies, but without the binary PECL packages that need to be compiled,
specify both the ``-a`` and the ``-B`` flag::
pear install -a -B horde/nag
By default, only the required dependencies will be installed::
pear install horde/nag
If you want to install Nag even with all binary dependencies, you need to
remove the ``-B`` flag. Please note that this might also try to install PHP
extensions through PECL that might need further configuration or activation in
your PHP configuration::
pear install -a horde/nag
Installing from Git
~~~~~~~~~~~~~~~~~~~
See http://www.horde.org/source/git.php
Configuring Nag
===============
1. Configuring Horde for Nag
Nag requires a permanent ``Shares`` backend in Horde to manage tasklists and
to add tasks to tasklists. If you didn't setup a Share backend yet, go to
the configuration interface, select Horde from the list of applications and
select the ``Shares`` tab. Unless you are using Kolab, you should select
``SQL``. Make sure that you ran the necessary scripts to create a storage
backend for the Share system, e.g. one of the ``create.*.sql``.
2. Configuring Nag
You must login to Horde as a Horde Administrator to finish the configuration
of Nag. Use the Horde ``Administration`` menu item to get to the
administration page, and then click on the ``Configuration`` icon to get the
configuration page. Select ``Tasks`` from the selection list of
applications. Fill in or change any configuration values as needed. When
done click on ``Generate Tasks Configuration`` to generate the ``conf.php``
file. If your web server doesn't have write permissions to the Nag
configuration directory or file, it will not be able to write the file. In
this case, go back to ``Configuration`` and choose one of the other methods
to create the configuration file ``nag/config/conf.php``.
Documentation on the format and purpose of the other configuration files in
the ``config/`` directory can be found in each file. You may create
``*.local.php`` versions of these files if you wish to customize Nag's
appearance and behavior. See the header of the configuration files for
details and examples. The defaults will be correct for most sites.
3. Creating the database table
Once you finished the configuration in the previous step, you can create all
database tables by clicking the ``DB schema is out of date.`` link in the
Nag row of the configuration screen.
Alternatively you creating the Nag database tables can be accomplished with
horde's ``horde-db-migrate`` utility. If your database is properly setup in the Horde configuration, just run the following::
horde/bin/horde-db-migrate nag
4. Testing Nag
Use Nag to create, modify, and delete tasks. Test at least the following:
- Creating a new task
- Modifying a task
- Completing a task
- Deleting a task
Obtaining Support
=================
If you encounter problems with Nag, help is available!
The Horde Frequently Asked Questions List (FAQ), available on the Web at
http://wiki.horde.org/FAQ
The Horde Project runs a number of mailing lists, for individual applications
and for issues relating to the project as a whole. Information, archives, and
subscription information can be found at
http://www.horde.org/community/mail
Lastly, Horde developers, contributors and users may also be found on IRC,
on the channel #horde on the Freenode Network (irc.freenode.net).
Please keep in mind that Nag is free software written by volunteers. For
information on reasonable support expectations, please read
http://www.horde.org/community/support
Thanks for using Nag!
The Horde team
.. _README: README
.. _INSTALL:
.. _`horde/docs/INSTALL`: ../../horde/docs/INSTALL
.. _`horde/docs/TRANSLATIONS`: ../../horde/docs/TRANSLATIONS
nag-4.1.3/docs/lighttpd-nag.conf 0000664 0001750 0001750 00000001071 12233763366 014563 0 ustar jan jan ## This file should be reviewed prior to inclusion in your lighttpd
## configuration. Specifically, if you have horde somewhere other than
## /horde you will need to edit the following rules to match your server
## configuration.
## This file should be included in your lighttpd.conf file with the "include"
## directive. Example:
## include "path/to/lighttpd-nag.conf"
## The exact path you use will of course depend on your specific configuration.
url.rewrite-once += (
## Rampage Rewrite Rules
"^/horde/nag/t/(.*)$" => "/horde/rampage.php/$1"
)
nag-4.1.3/docs/RELEASE_NOTES 0000664 0001750 0001750 00000003417 12233763366 013313 0 ustar jan jan notes['fm']['focus'] = array(Horde_Release::FOCUS_MINORSECURITY, Horde_Release::FOCUS_MINORBUG);
/* Mailing list release notes. */
$this->notes['ml']['changes'] = <<notes['fm']['changes'] = <<notes['name'] = 'Nag';
$this->notes['fm']['project'] = 'nag';
$this->notes['fm']['branch'] = 'Horde 5';
nag-4.1.3/docs/TODO 0000644 0001750 0001750 00000000712 12233763366 012021 0 ustar jan jan ===========================
Nag Development TODO List
===========================
:Contact: nag@lists.horde.org
- Allow resorting search results.
- Send email warnings as due dates approach.
- Next/Previous buttons in the task view.
- Adopt some UI enhancements from Palm ToDo app - dropdown list of categories
to filter by category, view tasks due today, etc.
- Ability to purge completed tasks.
- Automatically publish tasks in iCalendar format.
nag-4.1.3/docs/UPGRADING 0000664 0001750 0001750 00000004440 12233763366 012600 0 ustar jan jan ===============
Upgrading Nag
===============
:Contact: nag@lists.horde.org
.. contents:: Contents
.. section-numbering::
General instructions
====================
These are instructions to upgrade from earlier Nag versions. Please backup
your existing data before running any of the steps described below. You can't
use the updated data with your old Nag version anymore.
Upgrading Nag is as easy as running::
pear upgrade -a -B horde/nag
If you want to upgrade from a Nag version prior to 3.0, please follow
the instructions in INSTALL_ to install the most recent Nag version
using the PEAR installer.
After updating to a newer Nag version, you **always** need to update
configurations and database schemes. Log in as an administrator, go to
Administration => Configuration and update anything that's highlighted as
outdated.
Upgrading Nag from 2.2.x to 2.3.x
=================================
Some fields in the SQL share driver tables have been changed. Execute the
provided SQL script to update your database if you are using the native SQL
share driver.
mysql --user=root --password= < scripts/upgrades/2.2_to_2.3.sql
Upgrading Nag from 2.1.x to 2.2.x
=================================
SQL Backends
------------
A few new fields have been added to the default SQL table layout.
Execute the provided SQL script to update your data to the new Nag version,
e.g.::
mysql --user=root --password= < scripts/upgrades/2.1_to_2.2.sql
You also have to execute the provided PHP script::
php scripts/upgrades/2006-04-18_add_creator_and_assignee_fields.php
New Beta SQL Share Driver Support
---------------------------------
A new beta-level SQL Horde_Share driver has been added in Horde 3.2. This driver
offers significant performance improvements over the existing Datatree driver,
but it has not received the same level of testing, thus the beta designation.
In order to make use of this driver, you must be using Horde 3.2-RC3 or
later. The new tables needed for this driver already should have been created
by the step above.
If you want to use the new SQL Share driver, you must also execute the
provided PHP script to migrate your existing share data to the new format::
nag-convert-datatree-shares-to-sql
.. _INSTALL: INSTALL
nag-4.1.3/js/calendar.js 0000664 0001750 0001750 00000005371 12233763366 013134 0 ustar jan jan /**
* calendar.js - Calendar related javascript.
*
* Copyright 2010-2013 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 Michael Slusarz
* @category Horde
* @package Nag
*/
var NagCalendar =
{
calendarSelect: function(e)
{
var prefix, radio;
switch (e.element().identify()) {
case 'dueimg':
prefix = 'due';
radio = 'due_type_specified';
break;
case 'startimg':
prefix = 'start';
radio = 'start_date_specified';
break;
default:
return;
}
$(prefix + '_date').setValue(e.memo.toString(Nag.conf.date_format));
$(radio).setValue(1);
this.updateWday(prefix);
},
updateWday: function(p)
{
$(p + '_wday').update('(' + Horde_Calendar.fullweekdays[this.getFormDate(p).getDay()] + ')');
},
getFormDate: function(p)
{
return Date.parseExact($F(p + '_date'), Nag.conf.date_format);
},
clickHandler: function(e)
{
if (e.isRightClick()) {
return;
}
var elt = e.element(),
id = elt.readAttribute('id');
switch (id) {
case 'dueimg':
case 'startimg':
Horde_Calendar.open(elt, this.getFormDate(id.slice(0, -3)));
e.stop();
break;
case 'due_am_pm_am':
case 'due_am_pm_am_label':
case 'due_am_pm_pm':
case 'due_am_pm_pm_label':
$('due_type_specified').setValue(1);
break;
}
},
changeHandler: function(e)
{
switch (e.element().readAttribute('id')) {
case 'due_date':
this.updateWday('due');
// Fall-through
case 'due_time':
$('due_type_specified').setValue(1);
break;
case 'start_date':
this.updateWday('start');
// Fall-through
case 'start_time':
$('start_date_specified').setValue(1);
break;
case 'alarm_unit':
case 'alarm_value':
$('alarmon').setValue(1);
break;
}
},
onDomLoad: function()
{
this.updateWday('due');
this.updateWday('start');
$('nag_form_task_active').observe('click', this.clickHandler.bindAsEventListener(this));
$('nag_form_task_active').observe('change', this.changeHandler.bindAsEventListener(this));
}
};
document.observe('dom:loaded', NagCalendar.onDomLoad.bind(NagCalendar));
document.observe('Horde_Calendar:select', NagCalendar.calendarSelect.bindAsEventListener(NagCalendar));
nag-4.1.3/js/smartmobile.js 0000664 0001750 0001750 00000032611 12233763366 013676 0 ustar jan jan /**
* Base smartmobile application logic for Nag.
*
* Copyright 2011-2013 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 Michael J Rubinsky
* @category Horde
* @license http://www.horde.org/licenses/gpl GPL
* @package Nag
*/
var NagMobile = {
tasklists: {},
tasks: {},
currentList: undefined,
/**
* Toggle the completion status of the task.
*
* @param object d The data object.
*/
toggleComplete: function(d)
{
var parsed = d.options.parsedUrl;
HordeMobile.doAction(
'smartmobileToggle',
{
task: parsed.params.task_id,
tasklist: parsed.params.tasklist
},
function(r) { NagMobile.toggleCompleteCallback(r, d.options.data) }
);
},
/**
* Callback for the toggleComplete action
*
* @param object r The response object.
* @param object elt The element containing the task.
*/
toggleCompleteCallback: function(r, elt)
{
if (!r.data) {
return;
}
switch (r.data) {
case 'complete':
NagMobile.tasks[elt.jqmData('tasklist')][elt.jqmData('task_id')].cp = true;
if (Nag.conf.showCompleted == 'incomplete' ||
Nag.conf.showCompleted == 'future-incomplete') {
// Hide the task
elt.parent().remove();
} else {
elt.jqmData('icon', 'check')
.find('span.ui-icon')
.removeClass('ui-icon-nag-unchecked')
.addClass('ui-icon-check');
NagMobile.styleTask(elt, NagMobile.tasks[elt.jqmData('tasklist')][elt.jqmData('task_id')]);
}
break;
default:
NagMobile.tasks[elt.jqmData('tasklist')][elt.jqmData('task_id')].cp = false;
if (Nag.conf.showCompleted == 'complete') {
// Hide the task
elt.parent().remove();
} else {
elt.jqmData('icon', 'minus')
.find('span.ui-icon')
.removeClass('ui-icon-check')
.addClass('ui-icon-nag-unchecked');
NagMobile.styleTask(elt, NagMobile.tasks[elt.jqmData('tasklist')][elt.jqmData('task_id')]);
}
}
},
/**
* Get a task from the server.
*
* @param object d The data object.
*/
getTask: function(d)
{
var parsed = d.options.parsedUrl;
HordeMobile.doAction(
'getTask',
{
task: parsed.params.task_id,
tasklist: parsed.params.tasklist
},
NagMobile.getTaskCallback
);
$('#nag-taskform-view a[href^="#task-delete"]').show();
HordeMobile.changePage('nag-taskform-view', d);
},
/**
* Callback for the getTask action.
*
* @param object r The response object.
*/
getTaskCallback: function(r)
{
if (!r.task) {
return;
}
var task = r.task,
f = $('form')[0];
f.reset();
$("#task_title").val(task.n);
$("#task_desc").val(task.de);
$("#task_assignee").val(task.as);
$("task_private").prop("checked", task.pr).checkboxradio("refresh");
if (task.dd) {
$("#task_due").val(Date.parse(task.dd).toString('yyyy-MM-dd'));
}
if (task.s) {
$("#task_start").val(Date.parse(task.s).toString('yyyy-MM-dd'));
}
var myselect = $("#task_priority");
myselect[0].selectedIndex = task.pr - 1;
myselect.selectmenu("refresh");
$("#task_completed").prop("checked", task.cp).checkboxradio("refresh");
$("#task_estimate").val(task.e);
$("#task_id").val(task.id);
$("#tasklist").val(task.l);
},
/**
* Get a list of tasklists from the server and display the nag-lists view.
*
* @param object d The data object.
*/
toLists: function(d)
{
HordeMobile.changePage('nag-lists', d);
HordeMobile.doAction(
'getTaskLists',
{},
NagMobile.getTasklistsCallback
);
},
/**
* Callback for the getTaskLists action
*
* @param object r The response object.
*/
getTasklistsCallback: function(r)
{
if (!r.tasklists) {
return;
}
var list = $('#nag-lists :jqmData(role="listview")'),
count = 0;
list.empty();
$.each(r.tasklists, function(i, l) {
count = count + l.count;
NagMobile.insertTasklist(list, l, false);
});
NagMobile.insertTasklist(
list,
{
'name': Nag.strings.all,
'count': count
},
true
);
list.listview('refresh');
},
/**
* Insert a tasklist element into the tasklist list.
*
* @param object el The UL element.
* @param object l The list hash.
* @param boolean top Place new list at top of list if true.
*/
insertTasklist: function(el, l, top)
{
var url = HordeMobile.createUrl('nag-list', { tasklist: l.id }),
list;
NagMobile.tasklists[l.id] = l;
list = $('
';
}
return '';
}
/**
* Get HTML to represent the currently selected tags.
*
* @return string
*/
protected function _getTagTrail()
{
if ($this->_browser->tagCount() >= 1) {
$html = '
';
}
return '';
}
/**
* Get HTML for a link to remove a tag from the current search.
*
* @param string $tag The tag we want the link for.
*
* @return string
*/
protected function _linkRemoveTag($tag)
{
return Horde::url('list.php')
->add(array('actionID' => 'browse_remove', 'tag' => $tag));
}
/**
* Get HTML for a link to add a new tag to the current search.
*
* @param string $tag The tag we want to add.
*
* @return string
*/
protected function _linkAddTag($tag)
{
return Horde::url('list.php')->add(array('actionID' => 'browse_add', 'tag' => $tag));
}
}
nag-4.1.3/lib/.htaccess 0000644 0001750 0001750 00000000016 12233763366 012742 0 ustar jan jan Deny from all
nag-4.1.3/lib/Api.php 0000664 0001750 0001750 00000151143 12233763366 012400 0 ustar jan jan '%application%/view.php?tasklist=|tasklist|&task=|task|&uid=|uid|'
);
/**
* Returns a number of defaults necessary for the ajax view.
*
* @return array A hash with default values.
*/
public function ajaxDefaults()
{
return array(
'URI_TASKLIST_EXPORT' => str_replace(
array('%23', '%2523', '%7B', '%257B', '%7D', '%257D'),
array('#', '#', '{', '{', '}', '}'),
strval($GLOBALS['registry']->downloadUrl('#{tasklist}.ics', array('actionID' => 'export', 'exportTasks' => 1, 'exportID' => Horde_Data::EXPORT_ICALENDAR, 'exportList' => '#{tasklist}'))->setRaw(true))),
'default_tasklist' => Nag::getDefaultTasklist(Horde_Perms::EDIT),
'default_due' => (bool)$GLOBALS['prefs']->getValue('default_due'),
'default_due_days' => (int)$GLOBALS['prefs']->getValue('default_due_days'),
'default_due_time' => $GLOBALS['prefs']->getValue('default_due_time'),
'prefs_url' => strval($GLOBALS['registry']->getServiceLink('prefs', 'nag')->setRaw(true)),
);
}
/**
* 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.
* DEFAULT: show_completed pref is used.
* - 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.
* - include_tags: (boolean) Autoload all tags.
* DEFAULT: false (Tags are lazy loaded as needed.)
* - json: (boolean) Return data as JSON.
* DEFAULT: false (Data is returned as Nag_Task)
* - tasklists: (array) An array of tasklists to include.
* DEFAULT: Use $GLOBALS['display_tasklists'];
*
* @return array An array of the requested tasks.
*/
public function listTasks(array $options = array())
{
global $prefs;
$completedArray = array(
'incomplete' => Nag::VIEW_INCOMPLETE,
'all' => Nag::VIEW_ALL,
'complete' => Nag::VIEW_COMPLETE,
'future' => Nag::VIEW_FUTURE,
'future_incomplete' => Nag::VIEW_FUTURE_INCOMPLETE);
// Prevent null tasklists value from obscuring the default value.
if (array_key_exists('tasklists', $options) && empty($options['tasklists'])) {
unset($options['tasklists']);
}
if (is_null($options['completed']) || !isset($completedArray[$options['completed']])) {
$options['completed'] = $prefs->getValue('show_completed');
} else {
$options['completed'] = $completedArray[$options['completed']];
}
$options = array_merge(
array(
'sortby' => $prefs->getValue('sortby'),
'sortdir' => $prefs->getValue('sortdir'),
'altsortby' => $prefs->getValue('altsortby'),
'tasklists' => $GLOBALS['display_tasklists'],
'include_tags' => false,
'external' => false,
'json' => false
),
$options
);
$tasks = Nag::listTasks($options);
$tasks->reset();
$list = array();
while ($task = $tasks->each()) {
$list[$task->id] = $options['json'] ? $task->toJson() : $task->toHash();
}
return $list;
}
/**
* Returns a list of task lists.
*
* @param boolean $owneronly Only return tasklists that this user owns?
* Defaults to false.
* @param integer $permission The permission to filter tasklists by.
* @param boolean $smart Include smart tasklists in results.
*
* @return array The task lists.
*/
public function listTasklists($owneronly = false, $permission = Horde_Perms::SHOW, $smart = true)
{
return Nag::listTasklists($owneronly, $permission, $smart);
}
/**
* Returns a task list.
*
* @param string $name A task list name.
*
* @return Horde_Share_Object The task list.
*/
public function getTasklist($name)
{
try {
$tasklist = $GLOBALS['nag_shares']->getShare($name);
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e->getMessage(), 'ERR');
throw new Nag_Exception($e);
}
if (!$tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ)) {
throw new Horde_Exception_PermissionDenied(_("You are not allowed to retrieve this task list."));
}
return $tasklist;
}
/**
* Adds a new task list.
*
* @param string $name Task list name.
* @param string $description Task list description.
* @param string $color Task list color.
*
* @return integer The new tasklist's id.
*/
public function addTasklist($name, $description = '', $color = '')
{
$tasklist = Nag::addTasklist(array('name' => $name, 'description' => $description, 'color' => $color));
return $tasklist->getName();
}
/**
* Updates an existing task list.
*
* @param string $name A task list name.
* @param array $info Hash with task list information.
*/
public static function updateTasklist($name, $info)
{
try {
$tasklist = $GLOBALS['nag_shares']->getShare($name);
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e->getMessage(), 'ERR');
throw new Nag_Exception($e);
}
return Nag::updateTasklist($tasklist, $info);
}
/**
* Deletes a task list.
*
* @param string $id A task list id.
*/
public function deleteTasklist($id)
{
$tasklist = $GLOBALS['nag_shares']->getShare($id);
return Nag::deleteTasklist($tasklist);
}
/**
* Returns the displayed task lists.
*
* @return array Displayed tasklists.
*/
public function getDisplayedTasklists()
{
return $GLOBALS['display_tasklists'];
}
/**
* Sets the displayed task lists.
*
* @param array $list Displayed tasklists.
*/
public function setDisplayedTasklists($list)
{
$GLOBALS['display_tasklists'] = $list;
$GLOBALS['prefs']->setValue('display_tasklists', serialize($list));
}
/**
* Returns the last modification timestamp of a given uid.
*
* @param string $uid The uid to look for.
* @param string $tasklist The tasklist to look in.
*
* @return integer The timestamp for the last modification of $uid.
*/
public function modified($uid, $tasklist = null)
{
$modified = $this->getActionTimestamp($uid, 'modify', $tasklist);
if (empty($modified)) {
$modified = $this->getActionTimestamp($uid, 'add', $tasklist);
}
return $modified;
}
/**
* Browse through Nag's object tree.
*
* @param string $path The level of the tree to browse.
* @param array $properties The item properties to return. Defaults to 'name',
* 'icon', and 'browseable'.
*
* @return array The contents of $path
*/
public function browse($path = '', $properties = array())
{
global $registry;
// Default properties.
if (!$properties) {
$properties = array('name', 'icon', 'browseable');
}
if (substr($path, 0, 3) == 'nag') {
$path = substr($path, 3);
}
$path = trim($path, '/');
$parts = explode('/', $path);
if (empty($path)) {
// This request is for a list of all users who have tasklists
// visible to the requesting user.
$tasklists = Nag::listTasklists(false, Horde_Perms::READ);
$owners = array();
foreach ($tasklists as $tasklist) {
$owners[$tasklist->get('owner') ? $tasklist->get('owner') : '-system-'] = true;
}
$results = array();
foreach (array_keys($owners) as $owner) {
if (in_array('name', $properties)) {
$results['nag/' . $owner]['name'] = $owner;
}
if (in_array('icon', $properties)) {
$results['nag/' . $owner]['icon'] = Horde_Themes::img('user.png');
}
if (in_array('browseable', $properties)) {
$results['nag/' . $owner]['browseable'] = true;
}
if (in_array('contenttype', $properties)) {
$results['nag/' . $owner]['contenttype'] =
'httpd/unix-directory';
}
if (in_array('contentlength', $properties)) {
$results['nag/' . $owner]['contentlength'] = 0;
}
if (in_array('modified', $properties)) {
$results['nag/' . $owner]['modified'] =
$_SERVER['REQUEST_TIME'];
}
if (in_array('created', $properties)) {
$results['nag/' . $owner]['created'] = 0;
}
}
return $results;
} elseif (count($parts) == 1) {
// This request is for all tasklists owned by the requested user
$owner = $parts[0] == '-system-' ? '' : $parts[0];
$tasklists = $GLOBALS['nag_shares']->listShares(
$GLOBALS['registry']->getAuth(),
array('perm' => Horde_Perms::SHOW,
'attributes' => $owner));
$results = array();
foreach ($tasklists as $tasklistId => $tasklist) {
if ($parts[0] == '-system-' && $tasklist->get('owner')) {
continue;
}
$retpath = 'nag/' . $parts[0] . '/' . $tasklistId;
if (in_array('name', $properties)) {
$results[$retpath]['name'] = sprintf(_("Tasks from %s"), Nag::getLabel($tasklist));
$results[$retpath . '.ics']['name'] = Nag::getLabel($tasklist);
}
if (in_array('icon', $properties)) {
$results[$retpath]['icon'] = Horde_Themes::img('nag.png');
$results[$retpath . '.ics']['icon'] = Horde_Themes::img('mime/icalendar.png');
}
if (in_array('browseable', $properties)) {
$results[$retpath]['browseable'] = $tasklist->hasPermission($GLOBALS['registry']->getAuth(), Horde_Perms::READ);
$results[$retpath . '.ics']['browseable'] = false;
}
if (in_array('contenttype', $properties)) {
$results[$retpath]['contenttype'] = 'httpd/unix-directory';
$results[$retpath . '.ics']['contenttype'] = 'text/calendar';
}
if (in_array('contentlength', $properties)) {
$results[$retpath]['contentlength'] = 0;
$results[$retpath . '.ics']['contentlength'] = strlen($this->exportTasklist($tasklistId, 'text/calendar'));
}
if (in_array('modified', $properties)) {
// @TODO Find a way to get the actual modification times
$results[$retpath]['modified'] = $_SERVER['REQUEST_TIME'];
$results[$retpath . '.ics']['modified'] = $_SERVER['REQUEST_TIME'];
}
if (in_array('created', $properties)) {
// @TODO Find a way to get the actual creation times
$results[$retpath]['created'] = 0;
$results[$retpath . '.ics']['created'] = 0;
}
}
return $results;
} elseif (count($parts) == 2 && substr($parts[1], -4) == '.ics') {
//
// This is a request for the entire tasklist in iCalendar format.
//
$tasklist = substr($parts[1], 0, -4);
if (!Nag::hasPermission($tasklist, Horde_Perms::READ)) {
throw new Nag_Exception(_("Invalid tasklist file requested."), 404);
}
$ical_data = $this->exportTasklist($tasklist, 'text/calendar');
$result = array('data' => $ical_data,
'mimetype' => 'text/calendar',
'contentlength' => strlen($ical_data),
'mtime' => $_SERVER['REQUEST_TIME']);
return $result;
} elseif (count($parts) == 2) {
//
// This request is browsing into a specific tasklist. Generate the list
// of items and represent them as files within the directory.
//
if (!Nag::hasPermission($parts[1], Horde_Perms::READ)) {
throw new Nag_Exception(_("Invalid tasklist requested."), 404);
}
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($parts[1]);
try {
$storage->retrieve();
} catch (Nag_Exception $e) {
throw new Nag_Exception($e->getMessage, 500);
}
$icon = Horde_Themes::img('nag.png');
$results = array();
$storage->tasks->reset();
while ($task = $storage->tasks->each()) {
$key = 'nag/' . $parts[0] . '/' . $parts[1] . '/' . $task->id;
if (in_array('name', $properties)) {
$results[$key]['name'] = $task->name;
}
if (in_array('icon', $properties)) {
$results[$key]['icon'] = $icon;
}
if (in_array('browseable', $properties)) {
$results[$key]['browseable'] = false;
}
if (in_array('contenttype', $properties)) {
$results[$key]['contenttype'] = 'text/calendar';
}
if (in_array('contentlength', $properties)) {
// FIXME: This is a hack. If the content length is longer
// than the actual data then some WebDAV clients will report
// an error when the file EOF is received. Ideally we should
// determine the actual size of the data and report it here, but
// the performance hit may be prohibitive. This requires
// further investigation.
$results[$key]['contentlength'] = 1;
}
if (in_array('modified', $properties)) {
$results[$key]['modified'] = $this->modified($task->uid, $parts[1]);
}
if (in_array('created', $properties)) {
$results[$key]['created'] = $this->getActionTimestamp($task->uid, 'add', $parts[1]);
}
}
return $results;
} else {
//
// The only valid request left is for either a specific task item.
//
if (count($parts) == 3 &&
Nag::hasPermission($parts[1], Horde_Perms::READ)) {
//
// This request is for a specific item within a given task list.
//
/* Create a Nag storage instance. */
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($parts[1]);
$storage->retrieve();
try {
$storage->get($parts[2]);
} catch (Nag_Exception $e) {
throw new Nag_Exception($e->getMessage(), 500);
}
$result = array(
'data' => $this->export($task->uid, 'text/calendar'),
'mimetype' => 'text/calendar');
$modified = $this->modified($task->uid, $parts[1]);
if (!empty($modified)) {
$result['mtime'] = $modified;
}
return $result;
} elseif (count($parts) == 2 &&
substr($parts[1], -4) == '.ics' &&
Nag::hasPermission(substr($parts[1], 0, -4), Horde_Perms::READ)) {
// ??
} else {
//
// All other requests are a 404: Not Found
//
return false;
}
}
}
/**
* Saves a file into the Nag tree.
*
* @param string $path The path where to PUT the file.
* @param string $content The file content.
* @param string $content_type The file's content type.
*
* @return array The event UIDs
*/
public function put($path, $content, $content_type)
{
if (substr($path, 0, 3) == 'nag') {
$path = substr($path, 3);
}
$path = trim($path, '/');
$parts = explode('/', $path);
if (count($parts) == 2 &&
substr($parts[1], -4) == '.ics') {
// Workaround for WebDAV clients that are not smart enough to send
// the right content type. Assume text/calendar.
if ($content_type == 'application/octet-stream') {
$content_type = 'text/calendar';
}
$tasklist = substr($parts[1], 0, -4);
} elseif (count($parts) == 3) {
$tasklist = $parts[1];
// Workaround for WebDAV clients that are not smart enough to send
// the right content type. Assume the same format we send individual
// tasklist items: text/calendar
if ($content_type == 'application/octet-stream') {
$content_type = 'text/calendar';
}
} else {
throw new Nag_Exception(_("Invalid tasklist name supplied."), 403);
}
if (!Nag::hasPermission($tasklist, Horde_Perms::EDIT)) {
// FIXME: Should we attempt to create a tasklist based on the filename
// in the case that the requested tasklist does not exist?
throw new Nag_Exception(_("Tasklist does not exist or no permission to edit"), 403);
}
// Store all currently existings UIDs. Use this info to delete UIDs not
// present in $content after processing.
$ids = array();
$uids_remove = array_flip($this->listUids($tasklist));
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklist);
switch ($content_type) {
case 'text/calendar':
case 'text/x-vcalendar':
$iCal = new Horde_Icalendar();
if (!($content instanceof Horde_Icalendar_Vtodo)) {
if (!$iCal->parsevCalendar($content)) {
throw new Nag_Exception(_("There was an error importing the iCalendar data."), 400);
}
} else {
$iCal->addComponent($content);
}
foreach ($iCal->getComponents() as $content) {
if (!($content instanceof Horde_Icalendar_Vtodo)) {
continue;
}
$task = new Nag_Task();
$task->fromiCalendar($content);
$task->tasklist = $tasklist;
$create = true;
if (isset($task->uid)) {
try {
$existing = $storage->getByUID($task->uid);
$create = false;
} catch (Horde_Exception_NotFound $e) {
}
}
if (!$create) {
// Entry exists, remove from uids_remove list so we
// won't delete in the end.
if (isset($uids_remove[$task->uid])) {
unset($uids_remove[$task->uid]);
}
if ($existing->private &&
$existing->owner != $GLOBALS['registry']->getAuth()) {
continue;
}
// Check if our task is newer then the existing - get
// the task's history.
$history = $GLOBALS['injector']->getInstance('Horde_History');
$created = $modified = null;
try {
$log = $history->getHistory('nag:' . $tasklist . ':' . $task->uid);
foreach ($log as $entry) {
switch ($entry['action']) {
case 'add':
$created = $entry['ts'];
break;
case 'modify':
$modified = $entry['ts'];
break;
}
}
} catch (Exception $e) {}
if (empty($modified) && !empty($add)) {
$modified = $add;
}
if (!empty($modified) &&
$modified >= $content->getAttribute('LAST-MODIFIED')) {
// LAST-MODIFIED timestamp of existing entry
// is newer: don't replace it.
continue;
}
// Don't change creator/owner.
$task->owner = $existing->owner;
try {
$storage->modify($existing->id, $task->toHash());
} catch (Nag_Exception $e) {
throw new Nag_Exception($e->getMessage(), 500);
}
$ids[] = $task->uid;
} else {
try {
$newTask = $storage->add($task->toHash());
} catch (Nag_Exception $e) {
throw new Nag_Exception($e->getMessage(), 500);
}
// use UID rather than ID
$ids[] = $newTask[1];
}
}
break;
default:
throw new Nag_Exception(sprintf(_("Unsupported Content-Type: %s"), $content_type), 400);
}
if (Nag::hasPermission($tasklist, Horde_Perms::DELETE)) {
foreach (array_keys($uids_remove) as $uid) {
$this->delete($uid);
}
}
return $ids;
}
/**
* Deletes a file from the Nag tree.
*
* @param string $path The path to the file.
*
* @return string The event's UID
* @throws Nag_Exception
*/
public function path_delete($path)
{
if (substr($path, 0, 3) == 'nag') {
$path = substr($path, 3);
}
$path = trim($path, '/');
$parts = explode('/', $path);
if (count($parts) == 2) {
// @TODO Deny deleting of the entire tasklist for now.
// Allow users to delete tasklists but not create them via WebDAV will
// be more confusing than helpful. They are, however, still able to
// delete individual task items within the tasklist folder.
throw Nag_Exception(_("Deleting entire tasklists is not supported."), 403);
// To re-enable the functionality just remove this if {} block.
}
if (substr($parts[1], -4) == '.ics') {
$tasklistID = substr($parts[1], 0, -4);
} else {
$tasklistID = $parts[1];
}
if (!(count($parts) == 2 || count($parts) == 3) ||
!Nag::hasPermission($tasklistID, Horde_Perms::DELETE)) {
throw new Nag_Exception(_("Tasklist does not exist or no permission to delete"), 403);
}
/* Create a Nag storage instance. */
try {
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklistID);
$storage->retrieve();
} catch (Nag_Exception $e) {
throw new Nag_Exception(sprintf(_("Connection failed: %s"), $e->getMessage()), 500);
}
if (count($parts) == 3) {
// Delete just a single entry
return $storage->delete($parts[2]);
} else {
// Delete the entire task list
try {
$storage->deleteAll();
} catch (Nag_Exception $e) {
throw new Nag_Exception(sprintf(_("Unable to delete tasklist \"%s\": %s"), $tasklistID, $e->getMessage()), 500);
}
// Remove share and all groups/permissions.
$share = $GLOBALS['nag_shares']->getShare($tasklistID);
try {
$GLOBALS['nag_shares']->removeShare($share);
} catch (Horde_Share_Exception $e) {
throw new Nag_Exception($e->getMessage());
}
}
}
/**
* Returns an array of UIDs for all tasks that the current user is authorized
* to see.
*
* @param mixed $tasklists The tasklist or an array of taskslists to list.
*
* @return array An array of UIDs for all tasks
* the user can access.
*
* @throws Horde_Exception_PermissionDenied
* @throws Nag_Exception
*/
public function listUids($tasklists = null)
{
if (!isset($GLOBALS['conf']['storage']['driver'])) {
throw new Nag_Exception(_("Not configured"));
}
if (empty($tasklists)) {
$tasklists = Nag::getSyncLists();
} else {
if (!is_array($tasklists)) {
$tasklists = array($tasklists);
}
foreach ($tasklists as $list) {
if (!Nag::hasPermission($list, Horde_Perms::READ)) {
throw new Horde_Exception_PermissionDenied();
}
}
}
$tasks = Nag::listTasks(array(
'tasklists' => $tasklists,
'completed' => Nag::VIEW_ALL,
'include_history' => false)
);
$uids = array();
$tasks->reset();
while ($task = $tasks->each()) {
$uids[] = $task->uid;
}
return $uids;
}
/**
* Returns an array of UIDs for tasks that have had $action happen since
* $timestamp.
*
* @param string $action The action to check for - add, modify, or delete.
* @param integer $timestamp The time to start the search.
* @param mixed $tasklists The tasklists to be used. If 'null', the
* user's default tasklist will be used.
* @param integer $end The optional ending timestamp.
* @param boolean $isModSeq If true, $timestamp and $end are modification
* sequences and not timestamps. @since 4.1.1
*
* @return array An array of UIDs matching the action and time criteria.
*
* @throws Horde_History_Exception
* @throws InvalidArgumentException
*/
public function listBy($action, $timestamp, $tasklist = null, $end = null, $isModSeq = false)
{
if (empty($tasklist)) {
$tasklist = Nag::getSyncLists();
$results = array();
foreach ($tasklist as $list) {
$results = array_merge($results, $this->listBy($action, $timestamp, $list, $end, $isModSeq));
}
return $results;
}
$filter = array(array('op' => '=', 'field' => 'action', 'value' => $action));
if (!empty($end) && !$isModSeq) {
$filter[] = array('op' => '<', 'field' => 'ts', 'value' => $end);
}
if (!$isModSeq) {
$histories = $GLOBALS['injector']
->getInstance('Horde_History')
->getByTimestamp('>', $timestamp, $filter, 'nag:' . $tasklist);
} else {
$histories = $GLOBALS['injector']
->getInstance('Horde_History')
->getByModSeq($timestamp, $end, $filter, 'nag:' . $tasklist);
}
// Strip leading nag:username:.
return preg_replace('/^([^:]*:){2}/', '', array_keys($histories));
}
/**
* Method for obtaining all server changes between two timestamps. Basically
* a wrapper around listBy(), but returns an array containing all adds,
* edits and deletions.
*
* @param integer $start The starting timestamp
* @param integer $end The ending timestamp.
* @param boolean $isModSeq If true, $timestamp and $end are
* modification sequences and not
* timestamps. @since 4.1.1
*
* @return array An hash with 'add', 'modify' and 'delete' arrays.
*/
public function getChanges($start, $end, $isModSeq = false)
{
return array(
'add' => $this->listBy('add', $start, null, $end, $isModSeq),
'modify' => $this->listBy('modify', $start, null, $end, $isModSeq),
'delete' => $this->listBy('delete', $start, null, $end, $isModSeq));
}
/**
* Return all changes occuring between the specified modification
* sequences.
*
* @param integer $start The starting modseq.
* @param integer $end The ending modseq.
*
* @return array The changes @see getChanges()
* @since 4.1.1
*/
public function getChangesByModSeq($start, $end)
{
return $this->getChanges($start, $end, true, true);
}
/**
* Returns the timestamp of an operation for a given uid an action.
*
* @param string $uid The uid to look for.
* @param string $action The action to check for - add, modify, or delete.
* @param string $tasklist The tasklist to be used. If 'null', the
* user's default tasklist will be used.
* @param boolean $modSeq Request a modification sequence instead of a
* timestamp. @since 4.1.1
*
* @return integer The timestamp for this action.
*
* @throws InvalidArgumentException
* @throws Horde_Exception_PermissionDenied
* @thorws Horde_History_Exception
*/
public function getActionTimestamp($uid, $action, $tasklist = null, $modSeq = false)
{
if ($tasklist === null) {
$tasklist = Nag::getDefaultTasklist(Horde_Perms::READ);
} elseif (!Nag::hasPermission($tasklist, Horde_Perms::READ)) {
throw new Horde_Exception_PermissionDenied();
}
if (!$modSeq) {
return $GLOBALS['injector']
->getInstance('Horde_History')
->getActionTimestamp('nag:' . $tasklist . ':' . $uid, $action);
} else {
return $GLOBALS['injector']
->getInstance('Horde_History')
->getActionModSeq('nag:' . $tasklist . ':' . $uid, $action);
}
}
/**
* Return the largest modification sequence from the history backend.
*
* @return integer The modseq.
* @since 4.1.1
*/
public function getHighestModSeq()
{
return $GLOBALS['injector']->getInstance('Horde_History')->getHighestModSeq('nag');
}
/**
* Imports one or more tasks represented in the specified content type.
*
* If a UID is present in the content and the task is already in the
* database, a replace is performed rather than an add.
*
* @param string $content The content of the task.
* @param string $contentType What format is the data in? Currently supports:
* text/calendar
* text/x-vcalendar
* @param string $tasklist The tasklist into which the task will be
* imported. If 'null', the user's default
* tasklist will be used.
*
* @return string The new UID on one import, an array of UIDs on multiple imports,
*/
public function import($content, $contentType, $tasklist = null)
{
if ($tasklist === null) {
$tasklist = Nag::getDefaultTasklist(Horde_Perms::EDIT);
} elseif (!Nag::hasPermission($tasklist, Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
/* Create a Nag_Driver instance. */
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklist);
switch ($contentType) {
case 'text/x-vcalendar':
case 'text/calendar':
case 'text/x-vtodo':
$iCal = new Horde_Icalendar();
if (!($content instanceof Horde_Icalendar_Vtodo)) {
if (!$iCal->parsevCalendar($content)) {
throw new Nag_Exception(_("There was an error importing the iCalendar data."));
}
} else {
$iCal->addComponent($content);
}
$components = $iCal->getComponents();
if (count($components) == 0) {
throw new Nag_Exception(_("No iCalendar data was found."));
}
$ids = array();
foreach ($components as $content) {
if ($content instanceof Horde_Icalendar_Vtodo) {
$task = new Nag_Task($storage);
$task->fromiCalendar($content);
if (isset($task->uid)) {
try {
$existing = $storage->getByUID($task->uid);
$task->owner = $existing->owner;
$storage->modify($existing->id, $task->toHash());
} catch ( Horde_Exception_NotFound $e ) {
$storage->add($task->toHash());
}
$ids[] = $task->uid;
} else {
$hash = $task->toHash();
unset($hash['uid']);
$newTask = $storage->add($hash);
// use UID rather than ID
$ids[] = $newTask[1];
}
}
}
if (count($ids) == 0) {
throw new Nag_Exception(_("No iCalendar data was found."));
} else if (count($ids) == 1) {
return $ids[0];
}
return $ids;
case 'activesync':
$task = new Nag_Task();
$task->fromASTask($content);
$hash = $task->toHash();
unset($hash['uid']);
$results = $storage->add($hash);
/* array index 0 is id, 1 is uid */
return $results[1];
}
throw new Nag_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
}
/**
* Adds a task.
*
* @param array $task A hash with task information.
*
* @throws Horde_Exception_PermissionDenied
*/
public function addTask(array $task)
{
if (!$GLOBALS['registry']->isAdmin() &&
!Nag::hasPermission($task['tasklist'], Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($task['tasklist']);
return $storage->add($task);
}
/**
* 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.
* @throws Horde_Exception_PermissionDenied
*/
public function quickAdd($text, $tasklist = null)
{
if ($tasklist === null) {
$tasklist = Nag::getDefaultTasklist(Horde_Perms::EDIT);
} elseif (!Nag::hasPermission($tasklist, Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
return Nag::createTasksFromText($text, $tasklist);
}
/**
* Toggles the task completion flag.
*
* @param string $task_id The task ID.
* @param string $tasklist_id The tasklist that contains the task.
*
* @return boolean|string True if the task has been toggled, a due date if
* there are still incomplete recurrences.
*/
public function toggleCompletion($task_id, $tasklist_id)
{
if (!Nag::hasPermission($tasklist_id, Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
try {
$share = $GLOBALS['nag_shares']->getShare($tasklist_id);
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e->getMessage(), 'ERR');
throw new Nag_Exception($e);
}
$task = Nag::getTask($tasklist_id, $task_id);
$completed = $task->completed;
$task->toggleComplete();
$task->save();
$due = $task->getNextDue();
if ($task->completed == $completed) {
if ($due) {
return $due->toJson();
}
return false;
}
return true;
}
/**
* Exports a task, identified by UID, in the requested content type.
*
* @param string $uid Identify the task to export.
* @param string $contentType What format should the data be in?
* A string with one of:
* - text/calendar: iCalendar 2.0. Recommended as this is specified in
* RFC 2445.
* - text/x-vcalendar: vCalendar 1.0 format. Still in wide use.
* - activesync: Horde_ActiveSync_Message_Task.
* - raw: Nag_Task.
* @param array $options Any additional options for the exporter.
*
* @return string The requested data.
*/
public function export($uid, $contentType, array $options = array())
{
$task = $GLOBALS['injector']
->getInstance('Nag_Factory_Driver')
->create('')
->getByUID($uid);
if (!Nag::hasPermission($task->tasklist, Horde_Perms::READ)) {
throw new Horde_Exception_PermissionDenied();
}
$version = '2.0';
switch ($contentType) {
case 'text/x-vcalendar':
$version = '1.0';
case 'text/calendar':
// Create the new iCalendar container.
$iCal = new Horde_Icalendar($version);
$iCal->setAttribute('PRODID', '-//The Horde Project//Nag ' . $GLOBALS['registry']->getVersion() . '//EN');
$iCal->setAttribute('METHOD', 'PUBLISH');
// Create new vTodo object.
$vTodo = $task->toiCalendar($iCal);
$vTodo->setAttribute('VERSION', $version);
$iCal->addComponent($vTodo);
return $iCal->exportvCalendar();
case 'activesync':
return $task->toASTask($options);
case 'raw':
return $task;
default:
throw new Nag_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
}
}
/**
* Returns a task object.
*
* @param string $tasklist A tasklist id.
* @param string $id A task id.
*
* @return Nag_Task The matching task object.
*/
public function getTask($tasklist, $id)
{
if (!Nag::hasPermission($tasklist, Horde_Perms::READ)) {
throw new Horde_Exception_PermissionDenied();
}
return Nag::getTask($tasklist, $id);
}
/**
* Exports a tasklist in the requested content type.
*
* @param string $tasklist The tasklist to export.
* @param string $contentType What format should the data be in?
* A string with one of:
*
* text/calendar (VCALENDAR 2.0. Recommended as
* this is specified in rfc2445)
* text/x-vcalendar (old VCALENDAR 1.0 format.
* Still in wide use)
*
*
* @return string The iCalendar representation of the tasklist.
*/
public function exportTasklist($tasklist, $contentType)
{
if (!Nag::hasPermission($tasklist, Horde_Perms::READ)) {
throw new Horde_Exception_PermissionDenied();
}
$tasks = Nag::listTasks(array(
'tasklists' => array($tasklist),
'completed' => Nag::VIEW_ALL,
'include_tags' => true));
$version = '2.0';
switch ($contentType) {
case 'text/x-vcalendar':
$version = '1.0';
case 'text/calendar':
$share = $GLOBALS['nag_shares']->getShare($tasklist);
$iCal = new Horde_Icalendar($version);
$iCal->setAttribute('X-WR-CALNAME', $share->get('name'));
$tasks->reset();
while ($task = $tasks->each()) {
$iCal->addComponent($task->toiCalendar($iCal));
}
return $iCal->exportvCalendar();
}
throw new Nag_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
}
/**
* Deletes a task identified by UID.
*
* @param string|array $uid Identify the task to delete, either a single UID
* or an array.
*
* @return boolean Success or failure.
*/
public function delete($uid)
{
// Handle an arrray of UIDs for convenience
if (is_array($uid)) {
foreach ($uid as $g) {
$result = $this->delete($g);
}
return true;
}
$factory = $GLOBALS['injector']->getInstance('Nag_Factory_Driver');
$task = $factory->create('')->getByUID($uid);
if (!$GLOBALS['registry']->isAdmin() &&
!Nag::hasPermission($task->tasklist, Horde_Perms::DELETE)) {
throw new Horde_Exception_PermissionDenied();
}
return $factory->create($task->tasklist)->delete($task->id);
}
/**
* Deletes a task identified by tasklist and ID.
*
* @param string $tasklist A tasklist id.
* @param string $id A task id.
*/
public function deleteTask($tasklist, $id)
{
if (!$GLOBALS['registry']->isAdmin() &&
!Nag::hasPermission($tasklist, Horde_Perms::DELETE)) {
throw new Horde_Exception_PermissionDenied();
}
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklist);
return $storage->delete($id);
}
/**
* Replaces the task identified by UID with the content represented in the
* specified content type.
*
* If you want to replace multiple tasks with the UID specified in the
* VCALENDAR data, you may use $this->import instead. This automatically does a
* replace if existings UIDs are found.
*
*
* @param string $uid Identify the task to replace.
* @param string $content The content of the task.
* @param string $contentType What format is the data in? Currently supports:
* - text/x-vcalendar
* - text/calendar
*
* @return boolean Success or failure.
*/
public function replace($uid, $content, $contentType)
{
$factory = $GLOBALS['injector']->getInstance('Nag_Factory_Driver');
$existing = $factory->create('')->getByUID($uid);
$taskId = $existing->id;
$owner = $existing->owner;
if (!Nag::hasPermission($existing->tasklist, Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
switch ($contentType) {
case 'text/calendar':
case 'text/x-vcalendar':
if (!($content instanceof Horde_Icalendar_Vtodo)) {
$iCal = new Horde_Icalendar();
if (!$iCal->parsevCalendar($content)) {
throw new Nag_Exception(_("There was an error importing the iCalendar data."));
}
$components = $iCal->getComponents();
$component = null;
foreach ($components as $content) {
if ($content instanceof Horde_Icalendar_Vtodo) {
if ($component !== null) {
throw new Nag_Exception(_("Multiple iCalendar components found; only one vTodo is supported."));
}
$component = $content;
}
}
if ($component === null) {
throw new Nag_Exception(_("No iCalendar data was found."));
}
}
$task = new Nag_Task();
$task->fromiCalendar($content);
$task->owner = $owner;
$factory->create($existing->tasklist)->modify($taskId, $task->toHash());
break;
case 'activesync':
$task = new Nag_Task();
$task->fromASTask($content);
$task->owner = $owner;
$factory->create($existing->tasklist)->modify($taskId, $task->toHash());
break;
default:
throw new Nag_Exception(sprintf(_("Unsupported Content-Type: %s"), $contentType));
}
return $result;
}
/**
* Changes a task identified by tasklist and ID.
*
* @param string $tasklist A tasklist id.
* @param string $id A task id.
* @param array $task A hash with overwriting task information.
*/
public function updateTask($tasklist, $id, $task)
{
if (!$GLOBALS['registry']->isAdmin() &&
!Nag::hasPermission($tasklist, Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($tasklist);
$existing = $storage->get($id);
$task['owner'] = $existing->owner;
return $storage->modify($id, $task);
}
/**
* Lists active tasks as cost objects.
*
* @todo Implement $criteria parameter.
*
* @param array $criteria Filter attributes
*/
public function listCostObjects($criteria)
{
$tasks = Nag::listTasks(array(
'completed' => Nag::VIEW_ALL,
'include_history' => false)
);
$result = array();
$tasks->reset();
$last_week = time() - 7 * 86400;
while ($task = $tasks->each()) {
if ($task->completed && $task->completed_date < $last_week) {
continue;
}
$result[$task->id] = array(
'id' => $task->id,
'active' => !$task->completed,
'name' => $task->name
);
for ($parent = $task->parent; $parent->parent; $parent = $parent->parent) {
$result[$task->id]['name'] = $parent->name . ': '
. $result[$task->id]['name'];
}
if (!empty($task->estimate)) {
$result[$task->id]['estimate'] = $task->estimate;
}
}
if (count($result) == 0) {
return array();
} else {
return array(array('category' => _("Tasks"),
'objects' => array_values($result)));
}
}
public function listTimeObjectCategories()
{
$categories = array();
$tasklists = Nag::listTasklists(false, Horde_Perms::SHOW | Horde_Perms::READ);
foreach ($tasklists as $tasklistId => $tasklist) {
$categories[$tasklistId] = array('title' => Nag::getLabel($tasklist), 'type' => 'share');
}
return $categories;
}
/**
* Lists active tasks as time objects.
*
* @param array $categories The time categories (from
* listTimeObjectCategories) to list.
* @param mixed $start The start date of the period.
* @param mixed $end The end date of the period.
*/
public function listTimeObjects($categories, $start, $end)
{
$allowed_tasklists = Nag::listTasklists(false, Horde_Perms::READ);
foreach ($categories as $tasklist) {
if (!isset($allowed_tasklists[$tasklist])) {
throw new Horde_Exception_PermissionDenied();
}
}
$timeobjects = array();
$start = new Horde_Date($start);
$start_ts = $start->timestamp();
$end = new Horde_Date($end);
$end_ts = $end->timestamp();
// List incomplete tasks.
$tasks = Nag::listTasks(array(
'tasklists' => $categories,
'completed' => Nag::VIEW_INCOMPLETE,
'include_history' => false)
);
$tasks->reset();
while ($task = $tasks->each()) {
// If there's no due date, it's not a time object.
if (!$task->due ||
$task->due > $end_ts ||
(!$task->recurs() && $task->due + 1 < $start_ts) ||
($task->recurs() && $task->recurrence->getRecurEnd() &&
$task->recurrence->getRecurEnd()->timestamp() + 1 < $start_ts)) {
continue;
}
$due_date = date('Y-m-d\TH:i:s', $task->due);
$recurrence = null;
if ($task->recurs()) {
$recurrence = array(
'type' => $task->recurrence->getRecurType(),
'interval' => $task->recurrence->getRecurInterval(),
'end' => $task->recurrence->getRecurEnd(),
'count' => $task->recurrence->getRecurCount(),
'days' => $task->recurrence->getRecurOnDays(),
'exceptions' => $task->recurrence->getExceptions(),
'completions' => $task->recurrence->getCompletions());
}
$timeobjects[$task->id] = array(
'id' => $task->id,
'title' => $task->name,
'description' => $task->desc,
'start' => $due_date,
'end' => $due_date,
'recurrence' => $recurrence,
'color' => $allowed_tasklists[$task->tasklist]->get('color'),
'owner' => $allowed_tasklists[$task->tasklist]->get('owner'),
'permissions' => $GLOBALS['nag_shares']->getPermissions($task->tasklist, $GLOBALS['registry']->getAuth()),
'variable_length' => false,
'params' => array(
'task' => $task->id,
'tasklist' => $task->tasklist,
),
'link' => Horde::url('view.php', true)->add(array('tasklist' => $task->tasklist, 'task' => $task->id)),
'edit_link' => Horde::url('task.php', true)->add(array('tasklist' => $task->tasklist, 'task' => $task->id, 'actionID' => 'modify_task')),
'delete_link' => Horde::url('task.php', true)->add(array('tasklist' => $task->tasklist, 'task' => $task->id, 'actionID' => 'delete_task')),
'ajax_link' => 'task:' . $task->tasklist . ':' . $task->id
);
}
return $timeobjects;
}
/**
* Saves properties of a time object back to the task that it represents.
*
* At the moment only the title, description and due date are saved.
*
* @param array $timeobject A time object hash.
* @throws Nag_Exception
*/
public function saveTimeObject(array $timeobject)
{
if (!Nag::hasPermission($timeobject['params']['tasklist'], Horde_Perms::EDIT)) {
throw new Horde_Exception_PermissionDenied();
}
$storage = $GLOBALS['injector']
->getInstance('Nag_Factory_Driver')
->create($timeobject['params']['tasklist']);
$existing = $storage->get($timeobject['id']);
$info = array();
if (isset($timeobject['start'])) {
$info['due'] = new Horde_Date($timeobject['start']);
$info['due'] = $info['due']->timestamp();
}
if (isset($timeobject['title'])) {
$info['name'] = $timeobject['title'];
}
if (isset($timeobject['description'])) {
$info['desc'] = $timeobject['description'];
}
$storage->modify($timeobject['id'], $info);
}
}
nag-4.1.3/lib/Application.php 0000664 0001750 0001750 00000052771 12233763366 014141 0 ustar jan jan true,
'modseq' => true,
);
/**
*/
public $version = 'H5 (4.1.3)';
/**
* Global variables defined:
* $nag_shares - TODO
*/
protected function _init()
{
// Set the timezone variable.
$GLOBALS['registry']->setTimeZone();
/* For now, autoloading the Content_* classes depend on there being a
* registry entry for the 'content' application that contains at least
* the fileroot entry. */
$GLOBALS['injector']->getInstance('Horde_Autoloader')
->addClassPathMapper(
new Horde_Autoloader_ClassPathMapper_Prefix('/^Content_/', $GLOBALS['registry']->get('fileroot', 'content') . '/lib/'));
// Create a share instance.
$GLOBALS['nag_shares'] = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Share')->create();
Nag::initialize();
}
/**
*/
public function perms()
{
return array(
'max_tasks' => array(
'title' => _("Maximum Number of Tasks"),
'type' => 'int'
)
);
}
/**
* Generate links in the sidebar.
*
* @param Horde_Menu The menu object.
*/
public function menu($menu)
{
global $conf;
$menu->add(Horde::url('list.php'), _("_List Tasks"), 'nag-list', null, null, null, (basename($_SERVER['PHP_SELF']) == 'list.php' || strpos($_SERVER['PHP_SELF'], 'nag/index.php') !== false) ? 'current' : null);
/* Search. */
$menu->add(Horde::url('search.php'), _("_Search"), 'nag-search');
/* Import/Export. */
if ($conf['menu']['import_export']) {
$menu->add(Horde::url('data.php'), _("_Import/Export"), 'horde-data');
}
}
/**
* Add additional items to the sidebar.
*
* @param Horde_View_Sidebar $sidebar The sidebar object.
*/
public function sidebar($sidebar)
{
// @TODO: Implement an injector factory for this.
global $display_tasklists, $page_output, $prefs;
$perms = $GLOBALS['injector']->getInstance('Horde_Core_Perms');
if (Nag::getDefaultTasklist(Horde_Perms::EDIT) &&
($perms->hasAppPermission('max_tasks') === true ||
$perms->hasAppPermission('max_tasks') > Nag::countTasks())) {
$sidebar->addNewButton(
_("_New Task"),
Horde::url('task.php')->add('actionID', 'add_task'));
if ($GLOBALS['browser']->hasFeature('dom')) {
$page_output->addScriptFile('scriptaculous/effects.js', 'horde');
$page_output->addScriptFile('redbox.js', 'horde');
$blank = new Horde_Url();
$sidebar->newExtra = $blank->link(
array_merge(
array('onclick' => 'RedBox.showInline(\'quickAddInfoPanel\'); $(\'quickText\').focus(); return false;'),
Horde::getAccessKeyAndTitle(_("_Quick Add"), false, true)
)
);
require_once NAG_TEMPLATES . '/quick.inc';
}
}
$list = Horde::url('list.php');
$edit = Horde::url('tasklists/edit.php');
$user = $GLOBALS['registry']->getAuth();
$sidebar->containers['my'] = array(
'header' => array(
'id' => 'nag-toggle-my',
'label' => _("My Task Lists"),
'collapsed' => false,
),
);
if (!$GLOBALS['prefs']->isLocked('default_tasklist')) {
$sidebar->containers['my']['header']['add'] = array(
'url' => Horde::url('tasklists/create.php'),
'label' => _("Create a new Task List"),
);
}
$sidebar->containers['shared'] = array(
'header' => array(
'id' => 'nag-toggle-shared',
'label' => _("Shared Task Lists"),
'collapsed' => true,
),
);
foreach (Nag::listTaskLists(false, Horde_Perms::SHOW, false) as $name => $tasklist) {
$url = $list->add(array(
'display_tasklist' => $name,
'actionID' => in_array($name, $display_tasklists)
? 'remove_displaylist'
: 'add_displaylist'
));
$row = array(
'selected' => in_array($name, $display_tasklists),
'url' => $url,
'label' => Nag::getLabel($tasklist),
'color' => $tasklist->get('color') ?: '#dddddd',
'edit' => $edit->add('t', $tasklist->getName()),
'type' => 'checkbox',
);
if ($tasklist->get('owner') == $user) {
$sidebar->addRow($row, 'my');
} else {
$sidebar->addRow($row, 'shared');
}
}
}
/**
*/
public function hasPermission($permission, $allowed, $opts = array())
{
if (is_array($allowed)) {
switch ($permission) {
case 'max_tasks':
$allowed = max($allowed);
break;
}
}
return $allowed;
}
/**
* Remove all data for the specified user.
*
* @param string $user The user to remove.
* @throws Nag_Exception
*/
public function removeUserData($user)
{
try {
$shares = $GLOBALS['nag_shares']
->listShares($user, array('attributes' => $user));
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e, 'ERR');
throw new Nag_Exception($e);
}
$error = false;
foreach ($shares as $share) {
$storage = $GLOBALS['injector']->getInstance('Nag_Factory_Driver')->create($share->getName());
$result = $storage->deleteAll();
try {
$GLOBALS['nag_shares']->removeShare($share);
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e, 'NOTICE');
$error = true;
}
}
/* Now remove perms for this user from all other shares */
try {
$shares = $GLOBALS['nag_shares']->listShares($user);
foreach ($shares as $share) {
$share->removeUser($user);
}
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e, 'NOTICE');
$error = true;
}
if ($error) {
throw new Nag_Exception(sprintf(_("There was an error removing tasks for %s. Details have been logged."), $user));
}
}
/* Alarm method. */
/**
*/
public function listAlarms($time, $user = null)
{
if ((empty($user) || $user != $GLOBALS['registry']->getAuth()) &&
!$GLOBALS['registry']->isAdmin()) {
throw new Horde_Exception_PermissionDenied();
}
$group = $GLOBALS['injector']->getInstance('Horde_Group');
$alarm_list = array();
$tasklists = is_null($user) ?
array_keys($GLOBALS['nag_shares']->listAllShares()) :
$GLOBALS['display_tasklists'];
$alarms = Nag::listAlarms($time, $tasklists);
foreach ($alarms as $alarm) {
try {
$share = $GLOBALS['nag_shares']->getShare($alarm->tasklist);
} catch (Horde_Share_Exception $e) {
continue;
}
if (empty($user)) {
$users = $share->listUsers(Horde_Perms::READ);
$groups = $share->listGroups(Horde_Perms::READ);
foreach ($groups as $gid) {
$users = array_merge($users, $group->listUsers($gid));
}
$users = array_unique($users);
} else {
$users = array($user);
}
foreach ($users as $alarm_user) {
$prefs = $GLOBALS['injector']->getInstance('Horde_Core_Factory_Prefs')->create('nag', array(
'cache' => false,
'user' => $alarm_user
));
$GLOBALS['registry']->setLanguageEnvironment($prefs->getValue('language'));
$alarm_list[] = $alarm->toAlarm($alarm_user, $prefs);
}
}
return $alarm_list;
}
/* Topbar method. */
/**
*/
public function topbarCreate(Horde_Tree_Renderer_Base $tree, $parent = null,
array $params = array())
{
global $registry;
switch ($params['id']) {
case 'menu':
$add = Horde::url('task.php')->add('actionID', 'add_task');
$tree->addNode(array(
'id' => $parent . '__new',
'parent' => $parent,
'label' => _("New Task"),
'expanded' => false,
'params' => array(
'icon' => Horde_Themes::img('add.png'),
'url' => $add
)
));
foreach (Nag::listTasklists(false, Horde_Perms::EDIT) as $name => $tasklist) {
$tree->addNode(array(
'id' => $parent . $name . '__new',
'parent' => $parent . '__new',
'label' => sprintf(_("in %s"), Nag::getLabel($tasklist)),
'expanded' => false,
'params' => array(
'icon' => Horde_Themes::img('add.png'),
'url' => $add->copy()->add('tasklist_id', $name)
)
));
}
$tree->addNode(array(
'id' => $parent . '__search',
'parent' => $parent,
'label' => _("Search"),
'expanded' => false,
'params' => array(
'icon' => Horde_Themes::img('search.png'),
'url' => Horde::url('search.php')
)
));
break;
}
}
/* Download data. */
/**
* @throws Nag_Exception
*/
public function download(Horde_Variables $vars)
{
global $display_tasklists, $injector, $registry;
switch ($vars->actionID) {
case 'export':
$tasklists = $vars->get('exportList', $display_tasklists);
if (!is_array($tasklists)) {
$tasklists = array($tasklists);
}
/* Get the full, sorted task list. */
$tasks = Nag::listTasks(array(
'tasklists' => $tasklists,
'completed' => $vars->exportTasks,
'include_tags' => true,
'include_history' => false)
);
$tasks->reset();
switch ($vars->exportID) {
case Horde_Data::EXPORT_CSV:
$data = array();
while ($task = $tasks->each()) {
$task = $task->toHash();
$task['desc'] = str_replace(',', '', $task['desc']);
$task['tags'] = implode(',', $task['tags']);
unset(
$task['complete_link'],
$task['delete_link'],
$task['edit_link'],
$task['parent'],
$task['task_id'],
$task['tasklist_id'],
$task['view_link'],
$task['recurrence'],
$task['methods']
);
foreach (array('start', 'due', 'completed_date') as $field) {
if (!empty($task[$field])) {
$date = new Horde_Date($task[$field]);
$task[$field] = $date->format('c');
}
}
$data[] = $task;
}
$injector->getInstance('Horde_Core_Factory_Data')->create('Csv', array('cleanup' => array($this, 'cleanupData')))->exportFile(_("tasks.csv"), $data, true);
exit;
case Horde_Data::EXPORT_ICALENDAR:
$iCal = new Horde_Icalendar();
$iCal->setAttribute(
'PRODID',
'-//The Horde Project//Nag ' . $registry->getVersion() . '//EN');
while ($task = $tasks->each()) {
$iCal->addComponent($task->toiCalendar($iCal));
}
return array(
'data' => $iCal->exportvCalendar(),
'name' => _("tasks.ics"),
'type' => 'text/calendar'
);
}
}
}
/**
*/
public function cleanupData()
{
$GLOBALS['import_step'] = 1;
return Horde_Data::IMPORT_FILE;
}
/* DAV methods. */
/**
*/
public function davGetCollections($user)
{
$opts = array('perm' => Horde_Perms::SHOW);
if ($user != '-system-') {
$opts['attributes'] = $user;
}
$shares = $GLOBALS['nag_shares']
->listShares($GLOBALS['registry']->getAuth(), $opts);
$dav = $GLOBALS['injector']
->getInstance('Horde_Dav_Storage');
$tasklists = array();
foreach ($shares as $id => $share) {
if ($user == '-system-' && $share->get('owner')) {
continue;
}
try {
$id = $dav->getExternalCollectionId($id, 'tasks') ?: $id;
} catch (Horde_Dav_Exception $e) {
}
$tasklists[] = array(
'id' => $id,
'uri' => $id,
'principaluri' => 'principals/' . $user,
'{DAV:}displayname' => Nag::getLabel($share),
'{urn:ietf:params:xml:ns:caldav}calendar-description' =>
$share->get('desc'),
'{http://apple.com/ns/ical/}calendar-color' =>
$share->get('color'),
'{urn:ietf:params:xml:ns:caldav}supported-calendar-component-set' => new Sabre\CalDAV\Property\SupportedCalendarComponentSet(array('VTODO')),
);
}
return $tasklists;
}
/**
*/
public function davGetObjects($collection)
{
$dav = $GLOBALS['injector']
->getInstance('Horde_Dav_Storage');
$internal = $dav->getInternalCollectionId($collection, 'tasks') ?: $collection;
if (!Nag::hasPermission($internal, Horde_Perms::READ)) {
throw new Nag_Exception("Task List does not exist or no permission to edit");
}
$storage = $GLOBALS['injector']
->getInstance('Nag_Factory_Driver')
->create($internal);
$storage->retrieve();
$storage->tasks->reset();
$tasks = array();
while ($task = $storage->tasks->each()) {
$id = $task->id;
$modified = $this->_modified($internal, $task->uid);
try {
$id = $dav->getExternalObjectId($id, $internal) ?: $id . '.ics';
} catch (Horde_Dav_Exception $e) {
}
$tasks[] = array(
'id' => $id,
'uri' => $id,
'lastmodified' => $modified,
'etag' => '"' . md5($task->id . '|' . $modified) . '"',
'calendarid' => $collection,
);
}
return $tasks;
}
/**
*/
public function davGetObject($collection, $object)
{
$dav = $GLOBALS['injector']
->getInstance('Horde_Dav_Storage');
$internal = $dav->getInternalCollectionId($collection, 'tasks') ?: $collection;
if (!Nag::hasPermission($internal, Horde_Perms::READ)) {
throw new Nag_Exception("Task List does not exist or no permission to edit");
}
try {
$object = $dav->getInternalObjectId($object, $internal) ?: preg_replace('/\.ics$/', '', $object);
} catch (Horde_Dav_Exception $e) {
}
$task = Nag::getTask($internal, $object);
$id = $task->id;
$modified = $this->_modified($internal, $task->uid);
try {
$id = $dav->getExternalObjectId($id, $internal) ?: $id . '.ics';
} catch (Horde_Dav_Exception $e) {
}
$share = $GLOBALS['nag_shares']->getShare($internal);
$ical = new Horde_Icalendar('2.0');
$ical->setAttribute('X-WR-CALNAME', $share->get('name'));
$ical->addComponent($task->toiCalendar($ical));
$data = $ical->exportvCalendar();
return array(
'id' => $id,
'calendardata' => $data,
'uri' => $id,
'lastmodified' => $modified,
'etag' => '"' . md5($task->id . '|' . $modified) . '"',
'calendarid' => $collection,
'size' => strlen($data),
);
}
/**
*/
public function davPutObject($collection, $object, $data)
{
$dav = $GLOBALS['injector']
->getInstance('Horde_Dav_Storage');
$internal = $dav->getInternalCollectionId($collection, 'tasks') ?: $collection;
if (!Nag::hasPermission($internal, Horde_Perms::EDIT)) {
throw new Nag_Exception("Task List does not exist or no permission to edit");
}
$ical = new Horde_Icalendar();
if (!$ical->parsevCalendar($data)) {
throw new Nag_Exception(_("There was an error importing the iCalendar data."));
}
$storage = $GLOBALS['injector']
->getInstance('Nag_Factory_Driver')
->create($internal);
foreach ($ical->getComponents() as $content) {
if (!($content instanceof Horde_Icalendar_Vtodo)) {
continue;
}
$task = new Nag_Task();
$task->fromiCalendar($content);
try {
try {
$existing_id = $dav->getInternalObjectId($object, $internal)
?: preg_replace('/\.ics$/', '', $object);
} catch (Horde_Dav_Exception $e) {
$existing_id = $object;
}
$existing_task = Nag::getTask($internal, $existing_id);
/* Check if our task is newer then the existing - get the
* task's history. */
$modified = $this->_modified($internal, $existing_task->uid);
try {
if (!empty($modified) &&
$content->getAttribute('LAST-MODIFIED') < $modified) {
/* LAST-MODIFIED timestamp of existing entry is newer:
* don't replace it. */
continue;
}
} catch (Horde_Icalendar_Exception $e) {
}
$task->owner = $existing_task->owner;
$storage->modify($existing_task->id, $task->toHash());
} catch (Horde_Exception_NotFound $e) {
$hash = $task->toHash();
$newTask = $storage->add($hash);
$dav->addObjectMap($newTask[0], $object, $internal);
}
}
}
/**
*/
public function davDeleteObject($collection, $object)
{
$dav = $GLOBALS['injector']->getInstance('Horde_Dav_Storage');
$internal = $dav->getInternalCollectionId($collection, 'tasks') ?: $collection;
if (!Nag::hasPermission($internal, Horde_Perms::DELETE)) {
throw new Nag_Exception("Task List does not exist or no permission to delete");
}
try {
$object = $dav->getInternalObjectId($object, $internal)
?: preg_replace('/\.ics$/', '', $object);
} catch (Horde_Dav_Exception $e) {
}
$GLOBALS['injector']
->getInstance('Nag_Factory_Driver')
->create($internal)
->delete($object);
try {
$dav->deleteExternalObjectId($object, $internal);
} catch (Horde_Dav_Exception $e) {
}
}
/**
* Returns the last modification (or creation) date of a task.
*
* @param string $collection A task list ID.
* @param string $object A task UID.
*
* @return integer Timestamp of the last modification.
*/
protected function _modified($collection, $uid)
{
$history = $GLOBALS['injector']
->getInstance('Horde_History');
$modified = $history->getActionTimestamp(
'nag:' . $collection . ':' . $uid,
'modify'
);
if (!$modified) {
$modified = $history->getActionTimestamp(
'nag:' . $collection . ':' . $uid,
'add'
);
}
return $modified;
}
}
nag-4.1.3/lib/CompleteTask.php 0000664 0001750 0001750 00000003047 12233763366 014261 0 ustar jan jan getShare($tasklist);
$task = Nag::getTask($tasklist, $task);
if (!$share->hasPermission($registry->getAuth(), Horde_Perms::EDIT)) {
$result = array('error' => 'permission denied');
$notification->push(_("Access denied completing this task."), 'horde.error');
} else {
$wasCompleted = $task->completed;
$task->toggleComplete();
$task->save();
if ($task->completed) {
$result = array('data' => 'complete');
$notification->push(sprintf(_("Completed %s."), $task->name), 'horde.success');
} elseif (!$wasCompleted) {
$result = array('data' => 'incomplete');
$notification->push(sprintf(_("%s is still incomplete."), $task->name), 'horde.success');
} else {
$result = array('data' => 'incomplete');
$notification->push(sprintf(_("%s is now incomplete."), $task->name), 'horde.success');
}
}
} catch (Exception $e) {
$result = array('error' => $e->getMessage());
$notification->push(sprintf(_("There was a problem completing %s: %s"), $task->name, $e->getMessage()), 'horde.error');
}
return $result;
}
}
nag-4.1.3/lib/Driver.php 0000664 0001750 0001750 00000042713 12233763366 013124 0 ustar jan jan
* @author Jan Schneider
* @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.
* - 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,
'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::logMessage($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::logMessage($e, 'ERR');
}
}
/* Notify users about the new event. */
$result = Nag::sendNotification('add', $task);
/* Add an alarm if necessary. */
if (!empty($task->alarm) &&
($alarm = $task->toAlarm())) {
$alarm['start'] = new Horde_Date($alarm['start']);
$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. */
if (isset($properties['completed']) &&
$properties['completed'] != $task->completed) {
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));
$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::logMessage($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::logMessage($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::logMessage($e, 'ERR');
}
try {
$history->log('nag:' . $properties['tasklist'] . ':' . $task->uid, array('action' => 'add'), true);
} catch (Exception $e) {
Horde::logMessage($e, 'ERR');
}
$log_tasklist = $properties['tasklist'];
}
}
/* Update alarm if necessary. */
$horde_alarm = $GLOBALS['injector']->getInstance('Horde_Alarm');
if ((isset($properties['alarm']) && empty($properties['alarm'])) ||
!empty($properties['completed'])) {
$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::logMessage($e, 'ERR');
}
}
/* Log completion status changes. */
if (isset($properties['completed']) &&
$task->completed != $properties['completed']) {
$attributes = array('action' => 'complete');
if (!$properties['completed']) {
$attributes['ts'] = 0;
}
try {
$GLOBALS['injector']->getInstance('Horde_History')
->log('nag:' . $log_tasklist . ':' . $task->uid,
$attributes,
true);
} catch (Exception $e) {
Horde::logMessage($e, 'ERR');
}
}
/* Notify users about the changed event. */
try {
$result = Nag::sendNotification('edit', $new_task, $task);
} catch (Nag_Exception $e) {
Horde::logMessage($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);
$delete = $this->_delete($taskId);
/* 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::logMessage($e, 'ERR');
}
}
/* Notify users about the deleted event. */
try {
$result = Nag::sendNotification('delete', $task);
} catch (Nag_Exception $e) {
Horde::logMessage($e, 'ERR');
}
/* Delete alarm if necessary. */
if (!empty($task->alarm)) {
$GLOBALS['injector']->getInstance('Horde_Alarm')->delete($task->uid);
}
/* Remove any CalDAV mappings. */
try {
$GLOBALS['injector']
->getInstance('Horde_Dav_Storage')
->deleteInternalObjectId($task->id, $task->tasklist);
} catch (Horde_Exception $e) {
Horde::log($e);
}
}
/**
* Deletes all tasks for the current task list.
*
* @throws Nag_Exception
*/
public function deleteAll()
{
$ids = $this->_deleteAll();
// Update History.
$history = $GLOBALS['injector']->getInstance('Horde_History');
try {
foreach ($ids as $id) {
$history->log(
'nag:' . $this->_tasklist . ':' . $id,
array('action' => 'delete'),
true);
}
} catch (Exception $e) {
Horde::logMessage($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'
);
}
}
nag-4.1.3/lib/Exception.php 0000664 0001750 0001750 00000000505 12233763366 013620 0 ustar jan jan
* @author Chuck Hagenbuch
* @author Jan Schneider
* @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;
/**
*
* @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 = Horde_Nls::getLangInfo(D_FMT);
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.
* - 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.
* - include_tags: (boolean) Autoload all tags.
* DEFAULT: false (Tags are lazy loaded as needed.)
* - tasklists: (array) An array of tasklists to include.
* DEFAULT: Use $GLOBALS['display_tasklists'];
* - include_history: (boolean) Autoload created/modified data from Horde_History.
* DEFAULT: true (Automatically load history data).
*
* @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']) {
// We look for registered apis that support listAs(taskHash).
$apps = @unserialize($prefs->getValue('show_external'));
if (is_array($apps)) {
foreach ($apps as $app) {
if ($app != 'nag' &&
$registry->hasMethod('getListTypes', $app)) {
try {
$types = $registry->callByPackage($app, 'getListTypes');
} catch (Horde_Exception $e) {
continue;
}
if (!empty($types['taskHash'])) {
try {
$newtasks = $registry->callByPackage($app, 'listAs', array('taskHash'));
foreach ($newtasks as $task) {
$task['tasklist_id'] = '**EXTERNAL**';
$task['tasklist_name'] = $registry->get('name', $app);
$tasks->add(new Nag_Task(null, $task));
}
} catch (Horde_Exception $e) {
Horde::logMessage($newtasks, 'ERR');
}
}
}
}
}
}
// 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.
*
* This method takes the $conf['share']['hidden'] setting into account. If
* this setting is enabled, even if requesting permissions different than
* SHOW, it will only return calendars that the user owns or has SHOW
* permissions for. For checking individual calendar's permissions, use
* hasPermission() instead.
*
* @param boolean $owneronly Only return tasklists that this user owns?
* Defaults to false.
* @param integer $permission The permission to filter tasklists 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'));
} catch (Horde_Share_Exception $e) {
Horde::logMessage($e->getMessage(), 'ERR');
return array();
}
$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::logMessage($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])) {
$GLOBALS['prefs']->setValue('default_tasklist', $default_tasklist);
return $default_tasklist;
}
reset($tasklists);
return key($tasklists);
}
/**
* 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 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