<?php
/**
 * Nag external API interface.
 *
 * $Horde: nag/lib/api.php,v 1.100.10.6 2005/10/05 13:59:30 jan Exp $
 *
 * This file defines Nag's external API interface. Other applications
 * can interact with Nag through this API.
 *
 * @package Nag
 */

$_services['show'] = array(
    'link' => '%application%/view.php?tasklist=|tasklist|&task=|task|&uid=|uid|',
);

$_services['browse'] = array(
    'args' => array('path' => 'string'),
    'type' => '{urn:hash}hashHash',
);

$_services['addTasklist'] = array(
    'args' => array('name' => 'string', 'description' => 'string'),
    'type' => 'string',
);

$_services['listTasklists'] = array(
    'args' => array('owneronly' => 'boolean', 'permission' => 'int'),
    'type' => '{urn:hash}stringArray',
);

$_services['listTasks'] = array(
    'args' => array('sortby' => 'string', 'sortdir' => 'int'),
    'type' => '{urn:hash}stringArray',
);

$_services['list'] = array(
    'args' => array(),
    'type' => '{urn:hash}stringArray',
);

$_services['listBy'] = array(
    'args' => array('action' => 'string', 'timestamp' => 'int'),
    'type' => '{urn:hash}stringArray',
);

$_services['import'] = array(
    'args' => array('content' => 'string', 'contentType' => 'string', 'tasklist' => 'string'),
    'type' => 'int',
);

$_services['export'] = array(
    'args' => array('uid' => 'string', 'contentType' => '{urn:hash}stringArray'),
    'type' => 'string',
);

$_services['delete'] = array(
    'args' => array('uid' => '{urn:hash}stringArray'),
    'type' => 'boolean',
);

$_services['replace'] = array(
    'args' => array('uid' => 'string', 'content' => 'string', 'contentType' => 'string'),
    'type' => 'boolean',
);


function _nag_listTasks($sortby = null, $sortdir = null)
{
    require_once dirname(__FILE__) . '/base.php';
    global $prefs;

    if (!isset($sortby)) {
        $sortby = $prefs->getValue('sortby');
    }
    if (!isset($sortdir)) {
        $sortdir = $prefs->getValue('sortdir');
    }

    return Nag::listTasks($sortby, $sortdir);
}

function _nag_addTasklist($name, $description = '')
{
    if (!Auth::getAuth()) {
        return PEAR::raiseError(_("Permission denied"));
    }

    require_once dirname(__FILE__) . '/base.php';
    global $nag_shares;

    $tasklistId = md5(microtime());
    $tasklist = $nag_shares->newShare($tasklistId);

    if (is_a($tasklist, 'PEAR_Error')) {
        return $tasklist;
    }

    $tasklist->set('name', $name, false);
    $tasklist->set('desc', $description, false);
    $result = $nag_shares->addShare($tasklist);

    if (is_a($result, 'PEAR_Error')) {
        return $result;
    }

    return $tasklistId;
}

/**
 * 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
 */
function _nag_browse($path = '', $properties = array())
{
    function _modified($uid)
    {
        $modified = _nag_getActionTimestamp($uid, 'modify');
        if (empty($modified)) {
            $modified = _nag_getActionTimestamp($uid, 'add');
        }
        return $modified;
    }

    require_once dirname(__FILE__) . '/base.php';
    global $registry;

    // Default properties.
    if (!$properties) {
        $properties = array('name', 'icon', 'browseable');
    }

    if (substr($path, 0, 3) == 'nag') {
        $path = substr($path, 3);
    }
    if (substr($path, 0, 1) == '/') {
        $path = substr($path, 1);
    }
    if (substr($path, -1) == '/') {
        $path = substr($path, 0, -1);
    }

    if (empty($path)) {
        $tasklists = Nag::listTasklists(false, PERMS_SHOW);
        $results = array();
        foreach ($tasklists as $tasklistId => $tasklist) {
            $results['nag/' . $tasklistId] =
                array('name' => $tasklist->get('name'),
                      'icon' => $registry->getImageDir() . '/nag.png',
                      'browseable' => $tasklist->hasPermission(Auth::getAuth(), PERMS_READ));
        }
        return $results;
    } elseif (array_key_exists($path, Nag::listTasklists(false, PERMS_READ))) {
        /* Create a Nag storage instance. */
        $storage = &Nag_Driver::singleton($path);
        $storage->retrieve();

        $tasks = $storage->listTasks();
        if (is_a($tasks, 'PEAR_Error')) {
            return $tasks;
        }

        $results = array();
        foreach ($tasks as $taskId => $task) {
            $key = 'nag/' . $task['tasklist_id'] . '/' . $taskId;
            if (in_array('name', $properties)) {
                $results[$key]['name'] = $task['name'];
            }
            if (in_array('icon', $properties)) {
                $results[$key]['icon'] = $registry->getImageDir() . '/nag.png';
            }
            if (in_array('browseable', $properties)) {
                $results[$key]['browseable'] = false;
            }
            if (in_array('contenttype', $properties)) {
                $results[$key]['contenttype'] = 'text/x-vtodo';
            }
            if (in_array('contentlength', $properties)) {
                $data = _nag_export($task['uid'], 'text/x-vtodo');
                if (is_a($data, 'PEAR_Error')) {
                    $data = '';
                }
                $results[$key]['contentlength'] = strlen($data);
            }
            if (in_array('modified', $properties)) {
                $results[$key]['modified'] = _modified($task['uid']);
            }
            if (in_array('created', $properties)) {
                $results[$key]['created'] = _nag_getActionTimestamp($task['uid'], 'add');
            }
        }
        return $results;
    } else {
        $parts = explode('/', $path);
        if (count($parts) == 2 &&
            array_key_exists($parts[0], Nag::listTasklists(false, PERMS_READ))) {
            /* Create a Nag storage instance. */
            $storage = &Nag_Driver::singleton($parts[0]);
            if (is_a($storage, 'PEAR_Error')) {
                return PEAR::raiseError(sprintf(_("Connection failed: %s"), $storage->getMessage()));
            }
            $storage->retrieve();

            $task = $storage->get($parts[1]);
            if (is_a($task, 'PEAR_Error')) {
                return $task;
            }

            $result = array('data' => _nag_export($task['uid'], 'text/x-vtodo'),
                            'mimetype' => 'text/x-vtodo');
            $modified = _modified($task['uid']);
            if (!empty($modified)) {
                $result['mtime'] = $modified;
            }
            return $result;
        }
    }

    return PEAR::raiseError($path . ' does not exist or permission denied');
}

/**
 * @param boolean $owneronly   Only return tasklists that this user owns?
 *                             Defaults to false.
 * @param integer $permission  The permission to filter tasklists by.
 *
 * @return array  The task lists.
 */
function _nag_listTasklists($owneronly, $permission)
{
    require_once dirname(__FILE__) . '/base.php';

    return Nag::listTasklists($owneronly, $permission);
}

/**
 * Returns an array of UIDs for all tasks that the current user is authorized
 * to see.
 *
 * @return array  An array of UIDs for all tasks the user can access.
 */
function _nag_list()
{
    require_once dirname(__FILE__) . '/base.php';
    global $conf;

    if (!isset($conf['storage']['driver']) ||
        !isset($conf['storage']['params'])) {
        return PEAR::raiseError('Not configured');
    }

    $tasks = Nag::listTasks();
    if (is_a($tasks, 'PEAR_Error')) {
        return $tasks;
    }

    $uids = array();
    foreach ($tasks as $task) {
        $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.
 *
 * @return array  An array of UIDs matching the action and time criteria.
 */
function &_nag_listBy($action, $timestamp)
{
    require_once dirname(__FILE__) . '/base.php';
    require_once 'Horde/History.php';

    $history = &Horde_History::singleton();
    $histories = $history->getByTimestamp('>', $timestamp, array(array('op' => '=', 'field' => 'action', 'value' => $action)), 'nag');
    if (is_a($histories, 'PEAR_Error')) {
        return $histories;
    }

    return array_keys($histories);
}

/**
 * Imports a task represented in the specified content type.
 *
 * @param string $content      The content of the task.
 * @param string $contentType  What format is the data in? Currently supports:
 *                             text/x-icalendar
 *                             text/x-vcalendar
 *                             text/x-vtodo
 * @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, or PEAR_Error on failure.
 */
function _nag_import($content, $contentType, $tasklist = null)
{
    require_once dirname(__FILE__) . '/base.php';
    global $prefs;

    if ($tasklist === null) {
        $tasklist = Nag::getDefaultTasklist(PERMS_EDIT);
    }

    if (!array_key_exists($tasklist, Nag::listTasklists(false, PERMS_EDIT))) {
        return PEAR::raiseError(_("Permission Denied"));
    }

    /* Create a Nag_Driver instance. */
    require_once NAG_BASE . '/lib/Driver.php';
    $storage = &Nag_Driver::singleton($tasklist);

    switch ($contentType) {
    case 'text/x-icalendar':
    case 'text/x-vcalendar':
    case 'text/x-vtodo':
        if (!is_a($content, 'Horde_iCalendar_vtodo')) {
            require_once 'Horde/iCalendar.php';
            $iCal = &new Horde_iCalendar();
            if (!$iCal->parsevCalendar($content)) {
                return PEAR::raiseError(_("There was an error importing the iCalendar data."));
            }

            $components = $iCal->getComponents();
            switch (count($components)) {
            case 0:
                return PEAR::raiseError(_("No iCalendar data was found."));

            case 1:
                $content = $components[0];
                break;

            default:
                $ids = array();
                foreach ($components as $content) {
                    if (is_a($content, 'Horde_iCalendar_vtodo')) {
                        $task = $storage->fromiCalendar($content);
                        $taskId = $storage->add(
                            isset($task['name']) ? $task['name'] : '',
                            isset($task['desc']) ? $task['desc'] : '',
                            isset($task['due']) ? $task['due'] : '',
                            isset($task['priority']) ? $task['priority'] : 3,
                            !empty($task['completed']) ? 1 : 0,
                            isset($task['category']) ? $task['category'] : '',
                            isset($task['alarm']) ? $task['alarm'] : 0,
                            isset($task['uid']) ? $task['uid'] : null);
                        if (is_a($taskId, 'PEAR_Error')) {
                            return $taskId;
                        }
                        $ids[] = $taskId;
                    }
                }
                return $ids;
            }
        }

        /**
         * @TODO Need to check for alarms.
         */
        $task = $storage->fromiCalendar($content);
        $taskId = $storage->add(isset($task['name']) ? $task['name'] : '',
                                isset($task['desc']) ? $task['desc'] : '',
                                isset($task['due']) ? $task['due'] : '',
                                isset($task['priority']) ? $task['priority'] : 3,
                                !empty($task['completed']) ? 1 : 0,
                                isset($task['category']) ? $task['category'] : '',
                                0, // Alarms are missing.
                                isset($task['uid']) ? $task['uid'] : null);
        break;

    default:
        return PEAR::raiseError(_("Unsupported Content-Type."));
    }

    if (is_a($taskId, 'PEAR_Error')) {
        return $taskId;
    }

    $task = $storage->get($taskId);
    return $task['uid'];
}

/**
 * Exports a task, identified by UID, in the requested content type.
 *
 * @param string $uid          Identify the task to export.
 * @param mixed  $contentType  What format should the data be in?
 *                             Either a string with one of:
 *                             text/x-vtodo
 *                             text/x-vcalendar
 *                             text/x-icalendar
 *                             'ContentType':  as above
 *                             'ENCODING': (optional) character encoding
 *                                         for strings fields
 *                             'CHARSET':  (optional) charset. Like UTF-8
 *
 * @return string  The requested data.
 */
function _nag_export($uid, $contentType)
{
    require_once dirname(__FILE__) . '/base.php';

    $storage = &Nag_Driver::singleton();
    $task = $storage->getByUID($uid);
    if (is_a($task, 'PEAR_Error')) {
        return $task;
    }

    if (!array_key_exists($task['tasklist_id'], Nag::listTasklists(false, PERMS_EDIT))) {
        return PEAR::raiseError(_("Permission Denied"));
    }

    if (is_array($contentType)) {
        $options = $contentType;
        $contentType = $options['ContentType'];
        unset($options['ContentType']);
    } else {
        $options = array();
    }

    switch ($contentType) {
    case 'text/x-icalendar':
    case 'text/x-vcalendar':
    case 'text/x-vtodo':
        require_once dirname(__FILE__) . '/version.php';
        require_once 'Horde/iCalendar.php';

        // Create the new iCalendar container.
        $iCal = &new Horde_iCalendar();
        $iCal->setAttribute('VERSION', '2.0');
        $iCal->setAttribute('PRODID', '-//The Horde Project//Nag ' . NAG_VERSION . '//EN');
        $iCal->setAttribute('METHOD', 'PUBLISH');

        // Create new vTodo object.
        $vTodo = $storage->toiCalendar($task, $iCal);

        // Set encoding options for all string values.
        $vTodo->setParameter('SUMMARY', $options);
        $vTodo->setParameter('DESCRIPTION', $options);

        $iCal->addComponent($vTodo);

        return $iCal->exportvCalendar();

    default:
        return PEAR::raiseError(_("Unsupported Content-Type."));
    }
}

/**
 * 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.
 */
function _nag_delete($uid)
{
    // Handle an arrray of UIDs for convenience of deleting multiple
    // tasks at once.
    if (is_array($uid)) {
        foreach ($uid as $g) {
            $result = _nag_delete($g);
            if (is_a($result, 'PEAR_Error')) {
                return $result;
            }
        }

        return true;
    }

    require_once dirname(__FILE__) . '/base.php';

    $storage = &Nag_Driver::singleton();
    $task = $storage->getByUID($uid);
    if (is_a($task, 'PEAR_Error')) {
        return $task;
    }

    if (!array_key_exists($task['tasklist_id'], Nag::listTasklists(false, PERMS_DELETE))) {
        return PEAR::raiseError(_("Permission Denied"));
    }

    return $storage->delete($task['task_id']);
}

/**
 * Replaces the task identified by UID with the content represented in the
 * specified content type.
 *
 * @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-icalendar
 *                             text/x-vcalendar
 *                             text/x-vtodo
 *
 * @return boolean  Success or failure.
 */
function _nag_replace($uid, $content, $contentType)
{
    require_once dirname(__FILE__) . '/base.php';

    $storage = &Nag_Driver::singleton();
    $task = $storage->getByUID($uid);
    if (is_a($task, 'PEAR_Error')) {
        return $task;
    }
    $taskId = $task['task_id'];

    if (!array_key_exists($task['tasklist_id'], Nag::listTasklists(false, PERMS_EDIT))) {
        return PEAR::raiseError(_("Permission Denied"));
    }

    switch ($contentType) {
    case 'text/x-icalendar':
    case 'text/x-vcalendar':
    case 'text/x-vtodo':
        if (!is_a($content, 'Horde_iCalendar_vtodo')) {
            require_once 'Horde/iCalendar.php';
            $iCal = &new Horde_iCalendar();
            if (!$iCal->parsevCalendar($content)) {
                return PEAR::raiseError(_("There was an error importing the iCalendar data."));
            }

            $components = $iCal->getComponents();
            switch (count($components)) {
            case 0:
                return PEAR::raiseError(_("No iCalendar data was found."));

            case 1:
                $content = $components[0];
                break;

            default:
                return PEAR::raiseError(_("Multiple iCalendar components found; only one vTodo is supported."));
            }
        }

        /**
         * @TODO Need to check for alarms.
         */
        $task = $storage->fromiCalendar($content);
        $result = $storage->modify($taskId,
                                   isset($task['name']) ? $task['name'] : '',
                                   isset($task['desc']) ? $task['desc'] : '',
                                   isset($task['due']) ? $task['due'] : '',
                                   isset($task['priority']) ? $task['priority'] : 3,
                                   !empty($task['completed']) ? 1 : 0,
                                   isset($task['category']) ? $task['category'] : '',
                                   0 // Alarms are missing.
                                   );
        break;

    default:
        return PEAR::raiseError(_("Unsupported Content-Type."));
    }

    return $result;
}
