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

680 lines
23 KiB
PHP

<?php
/**
* Nag application API.
*
* This file defines Horde's core API interface. Other core Horde libraries
* can interact with Horde through this API.
*
* See the enclosed file COPYING for license information (GPL). If you
* did not receive this file, see http://www.horde.org/licenses/gpl.
*
* @package Nag
*/
/* Determine the base directories. */
if (!defined('NAG_BASE')) {
define('NAG_BASE', realpath(__DIR__ . '/..'));
}
if (!defined('HORDE_BASE')) {
/* If Horde does not live directly under the app directory, the HORDE_BASE
* constant should be defined in config/horde.local.php. */
if (file_exists(NAG_BASE . '/config/horde.local.php')) {
include NAG_BASE . '/config/horde.local.php';
} else {
define('HORDE_BASE', realpath(NAG_BASE . '/..'));
}
}
/* Load the Horde Framework core (needed to autoload
* Horde_Registry_Application::). */
require_once HORDE_BASE . '/lib/core.php';
use Sabre\CalDAV;
class Nag_Application extends Horde_Registry_Application
{
/**
*/
public $features = array(
'smartmobileView' => true,
'modseq' => true,
);
/**
*/
public $version = 'H5 (4.2.19)';
/**
* 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"),
);
}
if ($GLOBALS['registry']->isAdmin()) {
$sidebar->containers['system'] = array(
'header' => array(
'id' => 'nag-toggle-system',
'label' => _("System Task Lists"),
'collapsed' => true,
),
);
$sidebar->containers['system']['header']['add'] = array(
'url' => Horde::url('tasklists/create.php')->add('system', 1),
'label' => _("Create a new System 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 ($GLOBALS['registry']->isAdmin() &&
is_null($tasklist->get('owner'))) {
$sidebar->addRow($row, 'system');
} elseif ($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::log($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::log($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::log($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', true)->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
)
));
$user = $registry->getAuth();
foreach (Nag::listTasklists(false, Horde_Perms::SHOW, false) as $name => $tasklist) {
if (!$tasklist->hasPermission($user, Horde_Perms::EDIT)) {
continue;
}
$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':
$allowed = array_keys(
Nag::listTasklists(false, Horde_Perms::READ, false)
);
$tasklists = $vars->get('exportList', $allowed);
if (!is_array($tasklists)) {
$tasklists = array($tasklists);
}
$tasklists = array_intersect($tasklists, $allowed);
/* 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)
{
global $injector, $nag_shares, $registry;
$hordeUser = $registry->convertUsername($user, true);
$shares = $nag_shares->listShares($hordeUser);
$dav = $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,
'{' . CalDAV\Plugin::NS_CALENDARSERVER . '}shared-url' =>
Nag::getUrl(Nag::DAV_CALDAV, $share),
'principaluri' => 'principals/' . $user,
'{http://sabredav.org/ns}owner-principal' =>
'principals/'
. ($share->get('owner')
? $registry->convertUsername($share->get('owner'), false)
: '-system-'
),
'{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')),
'{http://sabredav.org/ns}read-only' => !$share->hasPermission($hordeUser, Horde_Perms::EDIT),
);
}
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;
}
}