Ticket #4561: Abstract ajax interface to Horde
authorMichael M Slusarz <slusarz@curecanti.org>
Wed, 20 Jan 2010 20:51:11 +0000 (13:51 -0700)
committerMichael M Slusarz <slusarz@curecanti.org>
Thu, 21 Jan 2010 06:24:49 +0000 (23:24 -0700)
22 files changed:
framework/Ajax/lib/Horde/Ajax.php [new file with mode: 0644]
framework/Ajax/lib/Horde/Ajax/Application/Base.php [new file with mode: 0644]
framework/Ajax/lib/Horde/Ajax/Exception.php [new file with mode: 0644]
framework/Ajax/package.xml
framework/Core/lib/Horde.php
framework/Core/lib/Horde/Registry.php
horde/docs/CHANGES
horde/services/ajax.php [new file with mode: 0644]
imp/ajax.php [deleted file]
imp/docs/CHANGES
imp/js/DimpBase.js
imp/js/DimpCore.js
imp/lib/Ajax/Application.php [new file with mode: 0644]
imp/lib/IMP.php
imp/templates/javascript_defs_dimp.php
kronolith/ajax.php [deleted file]
kronolith/js/kronolith.js
kronolith/lib/Ajax/Application.php [new file with mode: 0644]
kronolith/lib/Kronolith.php
shout/ajax.php [deleted file]
shout/lib/Ajax/Application.php [new file with mode: 0644]
shout/templates/extensions/list.inc

diff --git a/framework/Ajax/lib/Horde/Ajax.php b/framework/Ajax/lib/Horde/Ajax.php
new file mode 100644 (file)
index 0000000..6c50d91
--- /dev/null
@@ -0,0 +1,36 @@
+<?php
+/**
+ * Perform AJAX actions.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @package  Horde_Ajax
+ */
+class Horde_Ajax
+{
+    /**
+     * Get a Horde_Ajax_Application_Base instance.
+     *
+     * @param string $app     The application name.
+     * @param string $action  The AJAX action to perform.
+     *
+     * @return Horde_Ajax_Application_Base  The requested instance.
+     * @throws Horde_Exception
+     */
+    static public function getInstance($app, $action = null)
+    {
+        $class = $app . '_Ajax_Application';
+
+        if (class_exists($class)) {
+            return new $class($action);
+        }
+
+        throw new Horde_Ajax_Exception('Ajax configuration for ' . $app . ' not found.');
+    }
+
+}
diff --git a/framework/Ajax/lib/Horde/Ajax/Application/Base.php b/framework/Ajax/lib/Horde/Ajax/Application/Base.php
new file mode 100644 (file)
index 0000000..bf7d022
--- /dev/null
@@ -0,0 +1,76 @@
+<?php
+/**
+ * Defines the AJAX interface for an application.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde_Ajax
+ */
+abstract class Horde_Ajax_Application_Base
+{
+    /**
+     * The action to perform.
+     *
+     * @var string
+     */
+    protected $_action;
+
+    /**
+     * The list of actions that require readonly access to the session.
+     *
+     * @var array
+     */
+    protected $_readOnly = array();
+
+    /**
+     * Constructor.
+     *
+     * @param string $action  The AJAX action to perform.
+     */
+    public function __construct($action = null)
+    {
+        if (!is_null($action)) {
+            /* Close session if action is labeled as read-only. */
+            if (in_array($action, $this->_readOnly)) {
+                session_write_close();
+            }
+
+            $this->_action = $action;
+        }
+    }
+
+    /**
+     * Performs the AJAX action.
+     *
+     * @return mixed  The result of the action call.
+     * @throws Horde_Ajax_Exception
+     */
+    public function doAction()
+    {
+        if (!$this->_action) {
+            return false;
+        }
+
+        if (method_exists($this, $this->_action)) {
+        return call_user_func(array($this, $this->_action), Horde_Variables::getDefaultVariables());
+        }
+
+        throw new Horde_Ajax_Exception('Handler for action "' . $this->_action . '" does not exist.');
+    }
+
+    /**
+     * Returns a notification handler object to use to output any
+     * notification messages triggered by the AJAX action.
+     *
+     * @return Horde_Notification_Handler_Base  The notification handler.
+     */
+    public function notificationHandler()
+    {
+        return null;
+    }
+
+}
diff --git a/framework/Ajax/lib/Horde/Ajax/Exception.php b/framework/Ajax/lib/Horde/Ajax/Exception.php
new file mode 100644 (file)
index 0000000..b6ca292
--- /dev/null
@@ -0,0 +1,16 @@
+<?php
+/**
+ * Exception object for the Horde_Ajax package.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (LGPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/lgpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @package  Horde_Ajax
+ */
+class Horde_Ajax_Exception extends Horde_Exception
+{
+}
index 39e2dfa..02fabc6 100644 (file)
@@ -24,7 +24,8 @@ http://pear.php.net/dtd/package-2.0.xsd">
   <api>beta</api>
  </stability>
  <license uri="http://www.gnu.org/copyleft/lesser.html">LGPL</license>
- <notes>* Add AutoCompleter driver.
+ <notes>* Add Application framework.
+ * Add AutoCompleter driver.
  * Initial release.
  </notes>
  <contents>
@@ -32,6 +33,9 @@ http://pear.php.net/dtd/package-2.0.xsd">
    <dir name="lib">
     <dir name="Horde">
      <dir name="Ajax">
+      <dir name="Application">
+       <file name="Base.php" role="php" />
+      </dir> <!-- /lib/Horde/Ajax/Application -->
       <dir name="Imple">
        <file name="AutoCompleter.php" role="php" />
        <file name="Base.php" role="php" />
@@ -40,8 +44,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
         <file name="Geonames.php" role="php" />
        </dir>
       </dir> <!-- /lib/Horde/Ajax/Imple -->
+      <file name="Exception.php" role="php" />
       <file name="Imple.php" role="php" />
      </dir> <!-- /lib/Horde/Ajax -->
+     <file name="Ajax.php" role="php" />
     </dir> <!-- /lib/Horde -->
    </dir> <!-- /lib -->
   </dir> <!-- / -->
@@ -58,6 +64,10 @@ http://pear.php.net/dtd/package-2.0.xsd">
     <name>Core</name>
     <channel>pear.horde.org</channel>
    </package>
+   <package>
+    <name>Util</name>
+    <channel>pear.horde.org</channel>
+   </package>
   </required>
   <optional>
    <package>
@@ -72,11 +82,14 @@ http://pear.php.net/dtd/package-2.0.xsd">
  </dependencies>
  <phprelease>
   <filelist>
+   <install name="lib/Horde/Ajax/Application/Base.php" as="Horde/Ajax/Application/Base.php" />
+   <install name="lib/Horde/Ajax/Exception.php" as="Horde/Ajax/Exception.php" />
    <install name="lib/Horde/Ajax/Imple/AutoCompleter.php" as="Horde/Ajax/Imple/AutoCompleter.php" />
    <install name="lib/Horde/Ajax/Imple/Base.php" as="Horde/Ajax/Imple/Base.php" />
    <install name="lib/Horde/Ajax/Imple/SpellChecker.php" as="Horde/Ajax/Imple/SpellChecker.php" />
    <install name="lib/Horde/Ajax/Imple.php" as="Horde/Ajax/Imple.php" />
    <install name="lib/Horde/Ajax/Imple/Geocoder/Geonames.php" as="Horde/Ajax/Imple/Geocoder/Geonames.php" />
+   <install name="lib/Horde/Ajax.php" as="Horde/Ajax.php" />
   </filelist>
  </phprelease>
  <changelog/>
index 16aa953..ca54bb4 100644 (file)
@@ -554,7 +554,7 @@ HTML;
      *
      * The following are service links only and do not need to be defined
      * in Horde's menu config.
-     * 'cache', 'download', 'go', 'prefsapi'
+     * 'ajax', 'cache', 'download', 'go', 'prefsapi'
      * </pre>
      * @param string $app        The name of the current Horde application.
      * @param boolean $override  Override Horde settings?
@@ -563,7 +563,7 @@ HTML;
      */
     static public function getServiceLink($type, $app, $override = false)
     {
-        if (!in_array($type, array('cache', 'download', 'go', 'prefsapi')) &&
+        if (!in_array($type, array('ajax', 'cache', 'download', 'go', 'prefsapi')) &&
             !self::showService($type, $override)) {
             return false;
         }
@@ -601,6 +601,9 @@ HTML;
 
         case 'go':
             return self::url($webroot . '/services/go.php');
+
+        case 'ajax':
+            return self::url($webroot . '/services/ajax.php/' . $app . '/');
         }
 
         return false;
index 36072a8..e5d6920 100644 (file)
@@ -84,11 +84,12 @@ class Horde_Registry
     public $applications = array();
 
     /**
-     * Application initialization (called from within an application).
+     * Application bootstrap initialization.
      * Solves chicken-and-egg problem - need a way to init Horde environment
      * from application without an active Horde_Registry object.
      *
-     * Page compression will be started (if configured) via this function.
+     * Page compression will be started (if configured).
+     * init() will be called after the initialization is completed.
      *
      * Global variables defined:
      *   $registry - Registry object
index d60cbc3..e15343e 100644 (file)
@@ -2,6 +2,7 @@
 v4.0-cvs
 --------
 
+[mms] Add abstracted AJAX interface (Requesst #4561).
 [cjh] Cast Horde_Url objects to strings in Horde_Tree.
 [mms] Update scriptaculous to v1.8.3.
 [mjr] Add Horde_Image_Exif (ported from Ansel) and add a exiftool driver.
diff --git a/horde/services/ajax.php b/horde/services/ajax.php
new file mode 100644 (file)
index 0000000..7854f6a
--- /dev/null
@@ -0,0 +1,72 @@
+<?php
+/**
+ * Processes an AJAX request and returns a JSON encoded result.
+ *
+ * Path Info:
+ * ----------
+ * http://example.com/horde/services/ajax.php/APP/ACTION
+ *
+ * 'APP' - (string) The application name.
+ * 'ACTION' - (string) The AJAX action identifier.
+ *
+ * Reserved 'ACTION' strings:
+ * 'LogOut' - Logs user out of Horde.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package Horde
+ */
+
+require_once dirname(__FILE__) . '/../lib/Application.php';
+
+list($app, $action) = explode('/', trim(Horde_Util::getPathInfo(), '/'));
+if (empty($action)) {
+    // This is the only case where we really don't return anything, since
+    // the frontend can be presumed not to make this request on purpose.
+    // Other missing data cases we return a response of boolean false.
+    exit;
+}
+
+try {
+    Horde_Registry::appInit($app, array('authentication' => 'throw'));
+} catch (Horde_Exception $e) {
+    /* Handle session timeouts when they come from an AJAX request. */
+    if (($e->getCode() == Horde_Registry::AUTH_FAILURE) &&
+        ($action != 'LogOut')) {
+        $ajax = Horde_Ajax::getInstance($app);
+        $notification = Horde_Notification::singleton();
+
+        $notification->push(str_replace('&amp;', '&', Horde_Auth::getLogoutUrl(array('reason' => Horde_Auth::REASON_SESSION))), 'horde.ajaxtimeout', array('content.raw'));
+        Horde::sendHTTPResponse(Horde::prepareResponse(null, $ajax->notificationHandler()), 'json');
+        exit;
+    }
+
+    Horde_Auth::authenticateFailure($app, $e);
+}
+
+// Handle logout requests. This needs to be done here, rather than on the
+// browser, because the logout tokens might otherwise expire.
+if ($action == 'LogOut') {
+    Horde::redirect(str_replace('&amp;', '&', Horde::getServiceLink('logout', $app)));
+    exit;
+}
+
+// Open an output buffer to ensure that we catch errors that might break JSON
+// encoding.
+ob_start();
+
+$ajax = Horde_Ajax::getInstance($app, $action);
+$result = $ajax->doAction();
+
+// Clear the output buffer that we started above, and log any unexpected
+// output at a DEBUG level.
+if (ob_get_length()) {
+    Horde::logMessage('Unexpected output (' . $app . '): ' . ob_get_clean(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
+}
+
+// Send the final result.
+Horde::sendHTTPResponse(Horde::prepareResponse($result, $ajax->notificationHandler()), 'json');
diff --git a/imp/ajax.php b/imp/ajax.php
deleted file mode 100644 (file)
index 920bba6..0000000
+++ /dev/null
@@ -1,882 +0,0 @@
-<?php
-/**
- * Performs the AJAX-requested action.
- *
- * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author  Michael Slusarz <slusarz@horde.org>
- * @package IMP
- */
-
-function _generateDeleteResult($mbox, $indices, $change, $nothread = false)
-{
-    $imp_mailbox = IMP_Mailbox::singleton($mbox);
-
-    $del = new stdClass;
-    $del->folder = $mbox;
-    $del->uids = $GLOBALS['imp_imap']->ob()->utils->toSequenceString($indices, array('mailbox' => true));
-    $del->remove = intval($GLOBALS['prefs']->getValue('hide_deleted') ||
-                             $GLOBALS['prefs']->getValue('use_trash'));
-    $del->cacheid = $imp_mailbox->getCacheID($mbox);
-
-    $result = new stdClass;
-    $result->deleted = $del;
-
-    /* Check if we need to update thread information. */
-    if (!$change && !$nothread) {
-        $sort = IMP::getSort($mbox);
-        $change = ($sort['by'] == Horde_Imap_Client::SORT_THREAD);
-    }
-
-    if ($change) {
-        $result->ViewPort = _getListMessages($mbox, true);
-    }
-
-    $poll = _getPollInformation($mbox);
-    if (!empty($poll)) {
-        $result->poll = $poll;
-    }
-
-    return $result;
-}
-
-function _changed($mbox, $compare, $action, $rw = null)
-{
-    /* Only update search mailboxes on forced refreshes. */
-    if ($GLOBALS['imp_search']->isSearchMbox($mbox)) {
-        return ($action == 'ViewPort') || Horde_Util::getPost('forceUpdate');
-    }
-
-    /* We know we are going to be dealing with this mailbox, so select it on
-     * the IMAP server (saves some STATUS calls). */
-    if (!is_null($rw) && !$GLOBALS['imp_search']->isSearchMbox($mbox)) {
-        try {
-            $GLOBALS['imp_imap']->ob()->openMailbox($mbox, $rw ? Horde_Imap_Client::OPEN_READWRITE : Horde_Imap_Client::OPEN_AUTO);
-        } catch (Horde_Imap_Client_Exception $e) {
-            $GLOBALS['notification']->push($e, 'horde.error');
-            return null;
-        }
-    }
-
-    $imp_mailbox = IMP_Mailbox::singleton($mbox);
-    return ($imp_mailbox->getCacheID($mbox) != $compare);
-}
-
-function _getListMessages($mbox, $change)
-{
-    $args = array(
-        'applyfilter' => Horde_Util::getPost('applyfilter'),
-        'cache' => Horde_Util::getPost('cache'),
-        'cacheid' => Horde_Util::getPost('cacheid'),
-        'change' => $change,
-        'initial' => Horde_Util::getPost('initial'),
-        'mbox' => $mbox,
-        'rangeslice' => Horde_Util::getPost('rangeslice'),
-        'requestid' => Horde_Util::getPost('requestid'),
-        'qsearch' => Horde_Util::getPost('qsearch'),
-        'qsearchflag' => Horde_Util::getPost('qsearchflag'),
-        'qsearchmbox' => Horde_Util::getPost('qsearchmbox'),
-        'qsearchflagnot' => Horde_Util::getPost('qsearchflagnot'),
-        'sortby' => Horde_Util::getPost('sortby'),
-        'sortdir' => Horde_Util::getPost('sortdir'),
-    );
-
-    $search = Horde_Util::getPost('search');
-
-    if (!empty($search) || $args['initial']) {
-        $args += array(
-            'after' => intval(Horde_Util::getPost('after')),
-            'before' => intval(Horde_Util::getPost('before'))
-        );
-    }
-
-    if (empty($search)) {
-        list($slice_start, $slice_end) = explode(':', Horde_Util::getPost('slice'), 2);
-        $args += array(
-            'slice_start' => intval($slice_start),
-            'slice_end' => intval($slice_end)
-        );
-    } else {
-        $search = Horde_Serialize::unserialize($search, Horde_Serialize::JSON);
-        $args += array(
-            'search_uid' => isset($search->imapuid) ? $search->imapuid : null,
-            'search_unseen' => isset($search->unseen) ? $search->unseen : null
-        );
-    }
-
-    $list_msg = new IMP_Views_ListMessages();
-    return $list_msg->listMessages($args);
-}
-
-function _getIdxString($indices)
-{
-    $i = each($indices);
-    return reset($i['value']) . IMP::IDX_SEP . $i['key'];
-}
-
-function _getPollInformation($mbox)
-{
-    $imptree = IMP_Imap_Tree::singleton();
-    $elt = $imptree->get($mbox);
-    if ($imptree->isPolled($elt)) {
-        $info = $imptree->getElementInfo($mbox);
-        return array($mbox => isset($info['unseen']) ? intval($info['unseen']) : 0);
-    }
-    return array();
-}
-
-function _getQuota()
-{
-    if (isset($_SESSION['imp']['quota']) &&
-        is_array($_SESSION['imp']['quota'])) {
-        $quotadata = IMP::quotaData(false);
-        if (!empty($quotadata)) {
-            return array('p' => round($quotadata['percent']), 'm' => $quotadata['message']);
-        }
-    }
-
-    return null;
-}
-
-// Need to load Horde_Util:: to give us access to Horde_Util::getPathInfo().
-require_once dirname(__FILE__) . '/lib/Application.php';
-$action = basename(Horde_Util::getPathInfo());
-if (empty($action)) {
-    // This is the only case where we really don't return anything, since
-    // the frontend can be presumed not to make this request on purpose.
-    // Other missing data cases we return a response of boolean false.
-    exit;
-}
-
-// The following actions do not need write access to the session and
-// should be opened read-only for performance reasons.
-$session_control = null;
-if (in_array($action, array('chunkContent', 'Html2Text', 'Text2Html', 'GetReplyData'))) {
-    $session_control = 'readonly';
-}
-
-try {
-    Horde_Registry::appInit('imp', array('authentication' => 'throw', 'session_control' => $session_control));
-} catch (Horde_Exception $e) {
-    /* Handle session timeouts when they come from an AJAX request. */
-    if (($e->getCode() == Horde_Registry::AUTH_FAILURE) &&
-        ($action != 'LogOut')) {
-        $notification = Horde_Notification::singleton();
-        $imp_notify = $notification->attach('status', array('viewmode' => 'dimp'), 'IMP_Notification_Listener_Status');
-        $notification->push(str_replace('&amp;', '&', Horde_Auth::getLogoutUrl(array('reason' => Horde_Auth::REASON_SESSION))), 'dimp.timeout', array('content.raw'));
-        Horde::sendHTTPResponse(Horde::prepareResponse(null, $imp_notify), 'json');
-        exit;
-    }
-
-    Horde_Auth::authenticateFailure('imp', $e);
-}
-
-// Process common request variables.
-$mbox = Horde_Util::getPost('view');
-$indices = $imp_imap->ob()->utils->fromSequenceString(Horde_Util::getPost('uid'));
-$cacheid = Horde_Util::getPost('cacheid');
-
-// Open an output buffer to ensure that we catch errors that might break JSON
-// encoding.
-ob_start();
-
-$notify = true;
-$check_uidvalidity = $result = false;
-
-switch ($action) {
-case 'LogOut':
-    /* Handle logout requests. This needs to be done here, rather than on the
-     * browser, because the logout tokens might otherwise expire. */
-    Horde::redirect(str_replace('&amp;', '&', Horde::getServiceLink('logout', 'imp')));
-    exit;
-
-case 'CreateFolder':
-    if (empty($mbox)) {
-        break;
-    }
-
-    $imptree = IMP_Imap_Tree::singleton();
-    $imptree->eltDiffStart();
-
-    $imp_folder = IMP_Folder::singleton();
-
-    $new = Horde_String::convertCharset($mbox, Horde_Nls::getCharset(), 'UTF7-IMAP');
-    try {
-        $new = $imptree->createMailboxName(Horde_Util::getPost('parent'), $new);
-        $result = $imp_folder->create($new, $prefs->getValue('subscribe'));
-        if ($result) {
-            $result = IMP_Dimp::getFolderResponse($imptree);
-        }
-    } catch (Horde_Exception $e) {
-        $notification->push($e, 'horde.error');
-        $result = false;
-    }
-    break;
-
-case 'DeleteFolder':
-    if (empty($mbox)) {
-        break;
-    }
-
-    $imptree = IMP_Imap_Tree::singleton();
-    $imptree->eltDiffStart();
-
-    if ($imp_search->isEditableVFolder($mbox)) {
-        $notification->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $imp_search->getLabel($mbox)), 'horde.success');
-        $imp_search->deleteSearchQuery($mbox);
-        $result = true;
-    } else {
-        $imp_folder = IMP_Folder::singleton();
-        $result = $imp_folder->delete(array($mbox));
-    }
-
-    if ($result) {
-        $result = IMP_Dimp::getFolderResponse($imptree);
-    }
-    break;
-
-case 'RenameFolder':
-    $old = Horde_Util::getPost('old_name');
-    $new_parent = Horde_Util::getPost('new_parent');
-    $new = Horde_Util::getPost('new_name');
-    if (!$old || !$new) {
-        break;
-    }
-
-    $imptree = IMP_Imap_Tree::singleton();
-    $imptree->eltDiffStart();
-
-    $imp_folder = IMP_Folder::singleton();
-
-    try {
-        $new = $imptree->createMailboxName($new_parent, $new);
-
-        $new = Horde_String::convertCharset($new, Horde_Nls::getCharset(), 'UTF7-IMAP');
-        if ($old != $new) {
-            $result = $imp_folder->rename($old, $new);
-            if ($result) {
-                $result = IMP_Dimp::getFolderResponse($imptree);
-            }
-        }
-    } catch (Horde_Exception $e) {
-        $notification->push($e, 'horde.error');
-        $result = false;
-    }
-    break;
-
-case 'EmptyFolder':
-    if (empty($mbox)) {
-        break;
-    }
-
-    $imp_message = IMP_Message::singleton();
-    $imp_message->emptyMailbox(array($mbox));
-    $result = new stdClass;
-    $result->mbox = $mbox;
-    break;
-
-case 'FlagAll':
-    $flags = Horde_Serialize::unserialize(Horde_Util::getPost('flags'), Horde_Serialize::JSON);
-    if (empty($mbox) || empty($flags)) {
-        break;
-    }
-
-    $set = Horde_Util::getPost('set');
-
-    $imp_message = IMP_Message::singleton();
-    $result = $imp_message->flagAllInMailbox($flags, array($mbox), $set);
-
-    if ($result) {
-        $result = new stdClass;
-        $result->flags = $flags;
-        $result->mbox = $mbox;
-        if ($set) {
-            $result->set = 1;
-        }
-
-        $poll = _getPollInformation($mbox);
-        if (!empty($poll)) {
-            $result->poll = array($mbox => $poll[$mbox]);
-        }
-    }
-    break;
-
-case 'ListFolders':
-    $imptree = IMP_Imap_Tree::singleton();
-    $mask = IMP_Imap_Tree::FLIST_CONTAINER | IMP_Imap_Tree::FLIST_VFOLDER | IMP_Imap_Tree::FLIST_ELT;
-    if (Horde_Util::getPost('unsub')) {
-        $mask |= IMP_Imap_Tree::FLIST_UNSUB;
-    }
-
-    if (!Horde_Util::getPost('all')) {
-        $mask |= IMP_Imap_Tree::FLIST_NOCHILDREN;
-        if (Horde_Util::getPost('initial') || Horde_Util::getPost('reload')) {
-            $mask |= IMP_Imap_Tree::FLIST_ANCESTORS | IMP_Imap_Tree::FLIST_SAMELEVEL;
-        }
-    }
-
-    $folder_list = array();
-    foreach (Horde_Serialize::unserialize($mbox, Horde_Serialize::JSON) as $val) {
-        $folder_list += $imptree->folderList($mask, $val);
-    }
-
-    /* Add special folders explicitly to the initial folder list, since they
-     * are ALWAYS displayed and may appear outside of the folder slice
-     * requested. */
-    if (Horde_Util::getPost('initial')) {
-        foreach ($imptree->getSpecialMailboxes() as $val) {
-            if (!is_array($val)) {
-                $val = array($val);
-            }
-
-            foreach ($val as $val2) {
-                if (!isset($folder_list[$val2]) &&
-                    ($elt = $imptree->element($val2))) {
-                    $folder_list[$val2] = $elt;
-                }
-            }
-        }
-    }
-
-    $result = IMP_Dimp::getFolderResponse($imptree, array('a' => array_values($folder_list), 'c' => array(), 'd' => array()));
-
-    $quota = _getQuota();
-    if (!is_null($quota)) {
-        $result['quota'] = $quota;
-    }
-    break;
-
-case 'Poll':
-    $result = new stdClass;
-
-    $imptree = IMP_Imap_Tree::singleton();
-
-    $result->poll = array();
-    foreach ($imptree->getPollList() as $val) {
-        if ($info = $imptree->getElementInfo($val)) {
-            $result->poll[$val] = intval($info['unseen']);
-        }
-    }
-
-    $changed = false;
-    if (!empty($mbox)) {
-        $changed =_changed($mbox, $cacheid, $action);
-        if ($changed) {
-            $result->ViewPort = _getListMessages($mbox, true);
-        }
-    }
-
-    if (!is_null($changed)) {
-        $quota = _getQuota();
-        if (!is_null($quota)) {
-            $result->quota = $quota;
-        }
-    }
-    break;
-
-case 'Subscribe':
-    if ($prefs->getValue('subscribe')) {
-        $imp_folder = IMP_Folder::singleton();
-        $result = Horde_Util::getPost('sub')
-            ? $imp_folder->subscribe(array($mbox))
-            : $imp_folder->unsubscribe(array($mbox));
-    }
-    break;
-
-case 'ViewPort':
-    if (empty($mbox)) {
-        break;
-    }
-
-    /* Change sort preferences if necessary. */
-    $sortby = Horde_Util::getPost('sortby');
-    $sortdir = Horde_Util::getPost('sortdir');
-    if (!is_null($sortby) || !is_null($sortdir)) {
-        IMP::setSort($sortby, $sortdir, $mbox);
-    }
-
-    $changed = _changed($mbox, $cacheid, $action, false);
-
-    if (is_null($changed)) {
-        $list_msg = new IMP_Views_ListMessages();
-        $result = new stdClass;
-        $result->ViewPort = $list_msg->getBaseOb($mbox);
-
-        $req_id = Horde_Util::getPost('requestid');
-        if (!is_null($req_id)) {
-            $result->ViewPort->requestid = intval($req_id);
-        }
-    } elseif ($changed ||
-              Horde_Util::getPost('rangeslice') ||
-              !Horde_Util::getPost('checkcache')) {
-        $result = new stdClass;
-        $result->ViewPort = _getListMessages($mbox, $changed);
-    }
-    break;
-
-case 'MoveMessage':
-case 'CopyMessage':
-    $to = Horde_Util::getPost('tofld');
-    if (!$to || empty($indices)) {
-        break;
-    }
-
-    $change = ($action == 'MoveMessage')
-        ? _changed($mbox, $cacheid, $action, true)
-        : false;
-
-    if (!is_null($change)) {
-        $imp_message = IMP_Message::singleton();
-
-        $result = $imp_message->copy($to, ($action == 'MoveMessage') ? 'move' : 'copy', $indices);
-
-        if ($result) {
-            if ($action == 'MoveMessage') {
-                $result = _generateDeleteResult($mbox, $indices, $change);
-                // Need to manually set remove to true since we want to remove
-                // message from the list no matter the current pref settings.
-                $result->deleted->remove = 1;
-            }
-
-            // Update poll information for destination folder if necessary.
-            // Poll information for current folder will be added by
-            // _generateDeleteResult() call above.
-            $poll = _getPollInformation($to);
-            if (!empty($poll)) {
-                if (!isset($result->poll)) {
-                    $result->poll = array();
-                }
-                $result->poll = array_merge($result->poll, $poll);
-            }
-        } else {
-            $check_uidvalidity = true;
-        }
-    }
-    break;
-
-case 'FlagMessage':
-    $flags = Horde_Util::getPost('flags');
-    if (!$flags || empty($indices)) {
-        break;
-    }
-    $flags = Horde_Serialize::unserialize($flags, Horde_Serialize::JSON);
-
-    $set = $notset = array();
-    foreach ($flags as $val) {
-        if ($val[0] == '-') {
-            $notset[] = substr($val, 1);
-        } else {
-            $set[] = $val;
-        }
-    }
-
-    $imp_message = IMP_Message::singleton();
-    if (!empty($set)) {
-        $result = $imp_message->flag($set, $indices, true);
-    }
-    if (!empty($notset)) {
-        $result = $imp_message->flag($notset, $indices, false);
-    }
-
-    if ($result) {
-        $result = new stdClass;
-    } else {
-        $check_uidvalidity = true;
-    }
-    break;
-
-case 'DeleteMessage':
-    if (empty($indices)) {
-        break;
-    }
-
-    $imp_message = IMP_Message::singleton();
-    $change = _changed($mbox, $cacheid, $action, true);
-
-    if ($imp_message->delete($indices)) {
-        $result = _generateDeleteResult($mbox, $indices, $change, !$prefs->getValue('hide_deleted') && !$prefs->getValue('use_trash'));
-    } elseif (!is_null($change)) {
-        $check_uidvalidity = true;
-    }
-    break;
-
-case 'AddContact':
-    $email = Horde_Util::getPost('email');
-    $name = Horde_Util::getPost('name');
-    // Allow $name to be empty.
-    if (empty($email)) {
-        break;
-    }
-
-    try {
-        IMP::addAddress($email, $name);
-        $result = true;
-        $notification->push(sprintf(_("%s was successfully added to your address book."), $name ? $name : $email), 'horde.success');
-    } catch (Horde_Exception $e) {
-        $notification->push($e, 'horde.error');
-        $result = false;
-    }
-    break;
-
-case 'ReportSpam':
-    $change = _changed($mbox, $cacheid, $action, false);
-
-    if (IMP_Spam::reportSpam($indices, Horde_Util::getPost('spam') ? 'spam' : 'notspam')) {
-        $result = _generateDeleteResult($mbox, $indices, $change);
-        // If result is non-zero, then we know the message has been removed
-        // from the current mailbox.
-        $result->deleted->remove = 1;
-    } elseif (!is_null($change)) {
-        $check_uidvalidity = true;
-    }
-    break;
-
-case 'Blacklist':
-    if (empty($indices)) {
-        break;
-    }
-
-    $imp_filter = new IMP_Filter();
-    if (Horde_Util::getPost('blacklist')) {
-        $change = _changed($mbox, $cacheid, $action, false);
-        if (!is_null($change)) {
-            try {
-                if ($imp_filter->blacklistMessage($indices, false)) {
-                    $result = _generateDeleteResult($mbox, $indices, $change);
-                }
-            } catch (Horde_Exception $e) {
-                $check_uidvalidity = true;
-            }
-        }
-    } else {
-        try {
-            $imp_filter->whitelistMessage($indices, false);
-        } catch (Horde_Exception $e) {
-            $check_uidvalidity = true;
-        }
-    }
-    break;
-
-case 'ShowPreview':
-    if (count($indices) != 1) {
-        break;
-    }
-
-    $ptr = each($indices);
-    $args = array(
-        'mailbox' => $ptr['key'],
-        'preview' => true,
-        'uid' => intval(reset($ptr['value']))
-    );
-
-    /* We know we are going to be exclusively dealing with this mailbox, so
-     * select it on the IMAP server (saves some STATUS calls). Open R/W to
-     * clear the RECENT flag. */
-    try {
-        $imp_imap->ob()->openMailbox($ptr['key'], Horde_Imap_Client::OPEN_READWRITE);
-        $show_msg = new IMP_Views_ShowMessage();
-        $result = (object)$show_msg->showMessage($args);
-        if (isset($result->error)) {
-            $check_uidvalidity = true;
-        }
-    } catch (Horde_Imap_Client_Exception $e) {
-        $result = new stdClass;
-        $result->error = $e->getMessage();
-        $result->errortype = 'horde.error';
-        $result->mailbox = $args['mailbox'];
-        $result->uid = $args['uid'];
-    }
-
-    break;
-
-case 'Html2Text':
-    $result = new stdClass;
-    // Need to replace line endings or else IE won't display line endings
-    // properly.
-    $result->text = str_replace("\n", "\r\n", Horde_Text_Filter::filter(Horde_Util::getPost('text'), 'html2text', array('charset' => Horde_Nls::getCharset())));
-    break;
-
-case 'Text2Html':
-    $result = new stdClass;
-    $result->text = Horde_Text_Filter::filter(Horde_Util::getPost('text'), 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL, 'class' => null, 'callback' => null));
-    break;
-
-case 'GetForwardData':
-    $header = array();
-    $msg = $header = null;
-    $idx_string = _getIdxString($indices);
-
-    try {
-        $imp_contents = IMP_Contents::singleton($idx_string);
-        $imp_compose = IMP_Compose::singleton(Horde_Util::getPost('imp_compose'));
-        $imp_ui = new IMP_Ui_Compose();
-        $fwd_msg = $imp_ui->getForwardData($imp_compose, $imp_contents, $idx_string);
-        $header = $fwd_msg['headers'];
-        $header['replytype'] = 'forward';
-
-        $result = new stdClass;
-        // Can't open read-only since we need to store the message cache id.
-        $result->imp_compose = $imp_compose->getCacheId();
-        $result->fwd_list = IMP_Dimp::getAttachmentInfo($imp_compose);
-        $result->body = $fwd_msg['body'];
-        $result->header = $header;
-        $result->format = $fwd_msg['format'];
-        $result->identity = $fwd_msg['identity'];
-    } catch (Horde_Exception $e) {
-        $notification->push($e, 'horde.error');
-        $check_uidvalidity = true;
-    }
-    break;
-
-case 'GetReplyData':
-    try {
-        $imp_contents = IMP_Contents::singleton(_getIdxString($indices));
-        $imp_compose = IMP_Compose::singleton(Horde_Util::getPost('imp_compose'));
-        $reply_msg = $imp_compose->replyMessage(Horde_Util::getPost('type'), $imp_contents);
-        $header = $reply_msg['headers'];
-        $header['replytype'] = 'reply';
-
-        $result = new stdClass;
-        $result->format = $reply_msg['format'];
-        $result->body = $reply_msg['body'];
-        $result->header = $header;
-        $result->identity = $reply_msg['identity'];
-    } catch (Horde_Exception $e) {
-        $notification->push($e, 'horde.error');
-        $check_uidvalidity = true;
-    }
-    break;
-
-case 'CancelCompose':
-case 'DeleteDraft':
-    $imp_compose = IMP_Compose::singleton(Horde_Util::getPost('imp_compose'));
-    $imp_compose->destroy(false);
-    $draft_uid = $imp_compose->getMetadata('draft_uid');
-    if ($draft_uid && ($action == 'DeleteDraft')) {
-        $imp_message = IMP_Message::singleton();
-        $idx_array = array($draft_uid . IMP::IDX_SEP . IMP::folderPref($prefs->getValue('drafts_folder'), true));
-        $imp_message->delete($idx_array, array('nuke' => true));
-    }
-    $result = true;
-    break;
-
-case 'DeleteAttach':
-    $atc = Horde_Util::getPost('atc_indices');
-    if (!is_null($atc)) {
-        $imp_compose = IMP_Compose::singleton(Horde_Util::getPost('imp_compose'));
-        foreach ($imp_compose->deleteAttachment($atc) as $val) {
-            $notification->push(sprintf(_("Deleted the attachment \"%s\"."), Horde_Mime::decode($val)), 'horde.success');
-        }
-    }
-    break;
-
-case 'ShowPortal':
-    // Load the block list. Blocks are located in $dimp_block_list.
-    // KEY: Block label; VALUE: Horde_Block object
-    require IMP_BASE . '/config/portal.php';
-
-    $blocks = $linkTags = array();
-    $css_load = array('imp' => true);
-    foreach ($dimp_block_list as $title => $block) {
-        if ($block['ob'] instanceof Horde_Block) {
-            $app = $block['ob']->getApp();
-            // TODO: Fix CSS.
-            $content = $block['ob']->getContent();
-            $css_load[$app] = true;
-            // Don't do substitutions on our own blocks.
-            if ($app != 'imp') {
-                $content = preg_replace('/<a href="([^"]+)"/',
-                                        '<a onclick="DimpBase.go(\'app:' . $app . '\', \'$1\');return false"',
-                                        $content);
-                if (preg_match_all('/<link .*?rel="stylesheet".*?\/>/',
-                                   $content, $links)) {
-                    $content = str_replace($links[0], '', $content);
-                    foreach ($links[0] as $link) {
-                        if (preg_match('/href="(.*?)"/', $link, $href)) {
-                            $linkOb = new stdClass;
-                            $linkOb->href = $href[1];
-                            if (preg_match('/media="(.*?)"/', $link, $media)) {
-                                $linkOb->media = $media[1];
-                            }
-                            $linkTags[] = $linkOb;
-                        }
-                    }
-                }
-            }
-            if (!empty($content)) {
-                $entry = array(
-                    'app' => $app,
-                    'content' => $content,
-                    'title' => $title,
-                    'class' => empty($block['class']) ? 'headerbox' : $block['class'],
-                );
-                if (!empty($block['domid'])) {
-                    $entry['domid'] = $block['domid'];
-                }
-                if (!empty($block['tag'])) {
-                    $entry[$block['tag']] = true;
-                }
-                $blocks[] = $entry;
-            }
-        }
-    }
-
-    $result = new stdClass;
-    $result->portal = '';
-    if (!empty($blocks)) {
-        $t = new Horde_Template(IMP_TEMPLATES . '/imp/');
-        $t->set('block', $blocks);
-        $result->portal = $t->fetch('portal.html');
-    }
-    $result->linkTags = $linkTags;
-    break;
-
-case 'chunkContent':
-    $chunk = basename(Horde_Util::getPost('chunk'));
-    if (!empty($chunk)) {
-        $result = new stdClass;
-        $result->chunk = Horde_Util::bufferOutput('include', IMP_TEMPLATES . '/chunks/' . $chunk . '.php');
-    }
-    break;
-
-case 'PurgeDeleted':
-    $change = _changed($mbox, $cacheid, $action, $indices);
-    if (!is_null($change)) {
-        if (!$change) {
-            $sort = IMP::getSort($mbox);
-            $change = ($sort['by'] == Horde_Imap_Client::SORT_THREAD);
-        }
-        $imp_message = IMP_Message::singleton();
-        $expunged = $imp_message->expungeMailbox(array($mbox => 1), array('list' => true));
-        if (!empty($expunged[$mbox])) {
-            $expunge_count = count($expunged[$mbox]);
-            $display_folder = IMP::displayFolder($mbox);
-            if ($expunge_count == 1) {
-                $notification->push(sprintf(_("1 message was purged from \"%s\"."), $display_folder), 'horde.success');
-            } else {
-                $notification->push(sprintf(_("%s messages were purged from \"%s\"."), $expunge_count, $display_folder), 'horde.success');
-            }
-            $result = _generateDeleteResult($mbox, $expunged, $change);
-            // Need to manually set remove to true since we want to remove
-            // message from the list no matter the current pref settings.
-            $result->deleted->remove = 1;
-        }
-    }
-    break;
-
-case 'ModifyPoll':
-    if (empty($mbox)) {
-        break;
-    }
-
-    $add = Horde_Util::getPost('add');
-    $display_folder = IMP::displayFolder($mbox);
-
-    $imptree = IMP_Imap_Tree::singleton();
-
-    $result = new stdClass;
-    $result->add = intval($add);
-    $result->folder = $mbox;
-
-    if ($add) {
-        $imptree->addPollList($mbox);
-        if ($info = $imptree->getElementInfo($mbox)) {
-            $result->poll = array($mbox => intval($info['unseen']));
-        }
-        $notification->push(sprintf(_("\"%s\" mailbox now polled for new mail."), $display_folder), 'horde.success');
-    } else {
-        $imptree->removePollList($mbox);
-        $notification->push(sprintf(_("\"%s\" mailbox no longer polled for new mail."), $display_folder), 'horde.success');
-    }
-    break;
-
-case 'SendMDN':
-    $uid = Horde_Util::getPost('uid');
-    if (empty($mbox) || empty($uid)) {
-        break;
-    }
-
-    /* Get the IMP_Headers:: object. */
-    try {
-        $fetch_ret = $imp_imap->ob()->fetch($mbox, array(
-            Horde_Imap_Client::FETCH_HEADERTEXT => array(array('parse' => true, 'peek' => false))
-        ), array('ids' => array($uid)));
-    } catch (Horde_Imap_Client_Exception $e) {
-        break;
-    }
-
-    $imp_ui = new IMP_Ui_Message();
-    $imp_ui->MDNCheck($mbox, $uid, $reset($fetch_ret[$uid]['headertext']), true);
-    break;
-
-case 'PGPSymmetric':
-case 'PGPPersonal':
-case 'SMIMEPersonal':
-    $result = new stdClass;
-    $result->success = false;
-
-    $passphrase = Horde_Util::getFormData('dialog_input');
-
-    if ($action == 'SMIMEPersonal') {
-        $imp_smime = Horde_Crypt::singleton(array('IMP', 'Smime'));
-        try {
-            Horde::requireSecureConnection();
-            if ($passphrase) {
-                if ($imp_smime->storePassphrase($passphrase)) {
-                    $result->success = 1;
-                } else {
-                    $result->error = _("Invalid passphrase entered.");
-                }
-            } else {
-                $result->error = _("No passphrase entered.");
-            }
-        } catch (Horde_Exception $e) {
-            $result->error = $e->getMessage();
-        }
-    } else {
-        $imp_pgp = Horde_Crypt::singleton(array('IMP', 'Pgp'));
-        try {
-            Horde::requireSecureConnection();
-            if ($passphrase) {
-                if ($imp_pgp->storePassphrase(($action == 'PGPSymmetric') ? 'symmetric' : 'personal', $passphrase, Horde_Util::getFormData('symmetricid'))) {
-                    $result->success = 1;
-                } else {
-                    $result->error = _("Invalid passphrase entered.");
-                }
-            } else {
-                $result->error = _("No passphrase entered.");
-            }
-        } catch (Horde_Exception $e) {
-            $result->error = $e->getMessage();
-        }
-    }
-
-    if ($_SESSION['imp']['view'] != 'dimp') {
-        $notify = false;
-    }
-
-    break;
-}
-
-if ($check_uidvalidity) {
-    try {
-        $imp_imap->checkUidvalidity($mbox);
-    } catch (Horde_Exception $e) {
-        if (!is_object($result)) {
-            $result = new stdClass;
-        }
-        $result->ViewPort = _getListMessages($mbox, true);
-    }
-}
-
-// Clear the output buffer that we started above, and log any unexpected
-// output at a DEBUG level.
-if (ob_get_length()) {
-    Horde::logMessage('DIMP: unexpected output: ' . ob_get_clean(), __FILE__, __LINE__, PEAR_LOG_DEBUG);
-}
-
-// Send the final result.
-Horde::sendHTTPResponse(Horde::prepareResponse($result, $notify ? $GLOBALS['imp_notify'] : null), 'json');
index dddc680..bb0aef3 100644 (file)
@@ -2,6 +2,7 @@
 v5.0-git
 --------
 
+[mms] Move AJAX processing framework to Horde (Request #4561).
 [mms] If selected message(s) disappear from mailbox, gracefully handle in the
       user interface (DIMP).
 [mms] Only update search mailbox on explicit user action (Request #7297)
index 568787b..d54a398 100644 (file)
@@ -369,7 +369,7 @@ var DimpBase = {
 
         this.viewport = new ViewPort({
             // Mandatory config
-            ajax_url: DIMP.conf.URI_AJAX + '/ViewPort',
+            ajax_url: DIMP.conf.URI_AJAX + 'ViewPort',
             container: container,
             onContent: function(r, mode) {
                 var bg, re, u,
@@ -717,7 +717,7 @@ var DimpBase = {
         case 'ctx_folder_empty':
             tmp = baseelt.up('LI');
             if (window.confirm(DIMP.text.empty_folder.replace(/%s/, tmp.readAttribute('title')))) {
-                DimpCore.doAction('EmptyFolder', { view: tmp.retrieve('mbox') }, { callback: this._emptyFolderCallback.bind(this) });
+                DimpCore.doAction('EmptyFolder', { mbox: tmp.retrieve('mbox') }, { callback: this._emptyFolderCallback.bind(this) });
             }
             break;
 
@@ -725,7 +725,7 @@ var DimpBase = {
         case 'ctx_vfolder_delete':
             tmp = baseelt.up('LI');
             if (window.confirm(DIMP.text.delete_folder.replace(/%s/, tmp.readAttribute('title')))) {
-                DimpCore.doAction('DeleteFolder', { view: tmp.retrieve('mbox') }, { callback: this._folderCallback.bind(this) });
+                DimpCore.doAction('DeleteMailbox', { mbox: tmp.retrieve('mbox') }, { callback: this._folderCallback.bind(this) });
             }
             break;
 
@@ -1569,7 +1569,7 @@ var DimpBase = {
             dropbase = (drop == $('dropbase'));
             if (dropbase ||
                 (ftype != 'special' && !this.isSubfolder(drag, drop))) {
-                DimpCore.doAction('RenameFolder', { old_name: drag.retrieve('mbox'), new_parent: dropbase ? '' : foldername, new_name: drag.retrieve('l') }, { callback: this._folderCallback.bind(this) });
+                DimpCore.doAction('RenameMailbox', { old_name: drag.retrieve('mbox'), new_parent: dropbase ? '' : foldername, new_name: drag.retrieve('l') }, { callback: this._folderCallback.bind(this) });
             }
         } else if (ftype != 'container') {
             sel = this.viewport.getSelected();
@@ -1584,11 +1584,11 @@ var DimpBase = {
 
             if (uids.size()) {
                 if (e.memo.dragevent.ctrlKey) {
-                    DimpCore.doAction('CopyMessage', this.viewport.addRequestParams({ tofld: foldername }), { uids: uids });
+                    DimpCore.doAction('CopyMessages', this.viewport.addRequestParams({ mboxto: foldername }), { uids: uids });
                 } else if (this.folder != foldername) {
                     // Don't allow drag/drop to the current folder.
                     this.updateFlag(uids, '\\deleted', true);
-                    DimpCore.doAction('MoveMessage', this.viewport.addRequestParams({ tofld: foldername }), { uids: uids });
+                    DimpCore.doAction('MoveMessages', this.viewport.addRequestParams({ mboxto: foldername }), { uids: uids });
                 }
             }
         }
@@ -2171,17 +2171,19 @@ var DimpBase = {
             case 'rename':
                 folder = folder.up('LI');
                 if (folder.retrieve('l') != val) {
-                    action = 'RenameFolder';
-                    params = { old_name: folder.retrieve('mbox'),
-                               new_parent: folder.up().hasClassName('folderlist') ? '' : folder.up(1).previous().retrieve('mbox'),
-                               new_name: val };
+                    action = 'RenameMailbox';
+                    params = {
+                        old_name: folder.retrieve('mbox'),
+                        new_parent: folder.up().hasClassName('folderlist') ? '' : folder.up(1).previous().retrieve('mbox'),
+                        new_name: val
+                    };
                 }
                 break;
 
             case 'create':
             case 'createsub':
-                action = 'CreateFolder';
-                params = { view: val };
+                action = 'CreateMailbox';
+                params = { mbox: val };
                 if (mode == 'createsub') {
                     params.parent = folder.up('LI').retrieve('mbox');
                 }
@@ -2271,12 +2273,11 @@ var DimpBase = {
 
     _flagAllCallback: function(r)
     {
-        if (r.response) {
-            if (r.response.mbox == this.folder) {
-                r.response.flags.each(function(f) {
-                    this.updateFlag(this.viewport.createSelection('rownum', $A($R(1, this.viewport.getMetaData('total_rows')))), f, r.response.set);
-                }, this);
-            }
+        if (r.response &&
+            r.response.mbox == this.folder) {
+            r.response.flags.each(function(f) {
+                this.updateFlag(this.viewport.createSelection('rownum', $A($R(1, this.viewport.getMetaData('total_rows')))), f, r.response.set);
+            }, this);
         }
     },
 
@@ -2377,7 +2378,7 @@ var DimpBase = {
                 this._listFolders({
                     all: Number(mode == 'expall'),
                     callback: this._toggleSubFolder.bind(this, base, mode, noeffect),
-                    view: need
+                    mboxes: need
                 });
                 return;
             } else if (mode == 'tog') {
@@ -2414,10 +2415,10 @@ var DimpBase = {
 
         params = params || {};
         params.unsub = Number(this.showunsub);
-        if (!Object.isArray(params.view)) {
-            params.view = [ params.view ];
+        if (!Object.isArray(params.mboxes)) {
+            params.mboxes = [ params.mboxes ];
         }
-        params.view = params.view.toJSON();
+        params.mboxes = params.mboxes.toJSON();
 
         if (params.callback) {
             cback = function(func, r) { this._folderLoadCallback(r, func); }.bind(this, params.callback);
@@ -2426,7 +2427,7 @@ var DimpBase = {
             cback = this._folderLoadCallback.bind(this);
         }
 
-        DimpCore.doAction('ListFolders', params, { callback: cback });
+        DimpCore.doAction('ListMailboxes', params, { callback: cback });
     },
 
     // Folder actions.
@@ -2643,13 +2644,13 @@ var DimpBase = {
             this.deleteFolderElt(elt.readAttribute('id'), true);
         }, this);
 
-        this._listFolders({ reload: 1, view: this.folder });
+        this._listFolders({ reload: 1, mboxes: this.folder });
     },
 
     subscribeFolder: function(f, sub)
     {
         var fid = this.getFolderId(f);
-        DimpCore.doAction('Subscribe', { view: f, sub: Number(sub) });
+        DimpCore.doAction('Subscribe', { mbox: f, sub: Number(sub) });
 
         if (this.showunsub) {
             [ $(fid) ].invoke(sub ? 'removeClassName' : 'addClassName', 'unsubFolder');
@@ -2733,7 +2734,7 @@ var DimpBase = {
 
         opts.vs = vs;
 
-        this._doMsgAction('DeleteMessage', opts, {});
+        this._doMsgAction('DeleteMessages', opts, {});
         this.updateFlag(vs, '\\deleted', true);
     },
 
@@ -2771,7 +2772,7 @@ var DimpBase = {
 
         this.updateFlag(vs, flag, set);
         if (!opts.noserver) {
-            DimpCore.doAction('FlagMessage', { flags: flags.toJSON(), view: this.folder }, { uids: vs });
+            DimpCore.doAction('FlagMessages', { flags: flags.toJSON(), view: this.folder }, { uids: vs });
         }
     },
 
@@ -2779,7 +2780,7 @@ var DimpBase = {
     // mbox = (string) The mailbox to flag
     flagAll: function(type, set, mbox)
     {
-        DimpCore.doAction('FlagAll', { flags: [ type ].toJSON(), set: Number(set), view: mbox }, { callback: this._flagAllCallback.bind(this) });
+        DimpCore.doAction('FlagAll', { flags: [ type ].toJSON(), set: Number(set), mbox: mbox }, { callback: this._flagAllCallback.bind(this) });
     },
 
     hasFlag: function(f, r)
@@ -2835,13 +2836,13 @@ var DimpBase = {
 
     modifyPoll: function(folder, add)
     {
-        DimpCore.doAction('ModifyPoll', { view: folder, add: Number(add) }, { callback: this._modifyPollCallback.bind(this) });
+        DimpCore.doAction('ModifyPoll', { add: Number(add), mbox: folder }, { callback: this._modifyPollCallback.bind(this) });
     },
 
     _modifyPollCallback: function(r)
     {
         r = r.response;
-        var f = r.folder, fid, p = { response: { poll: {} } };
+        var f = r.mbox, fid, p = { response: { poll: {} } };
         fid = $(this.getFolderId(f));
 
         if (r.add) {
@@ -2853,7 +2854,7 @@ var DimpBase = {
 
         if (!r.add) {
             fid.store('u', null);
-            this.updateUnseenStatus(r.folder, 0);
+            this.updateUnseenStatus(f, 0);
         }
     },
 
@@ -2941,7 +2942,7 @@ var DimpBase = {
 
         /* Create the folder list. Any pending notifications will be caught
          * via the return from this call. */
-        this._listFolders({ initial: 1, view: this.folder} );
+        this._listFolders({ initial: 1, mboxes: this.folder} );
 
         this._setQsearchText(true);
 
index f0dcc62..d8d5115 100644 (file)
@@ -109,7 +109,7 @@ var DimpCore = {
     {
         action = action.startsWith('*')
             ? action.substring(1)
-            : DIMP.conf.URI_AJAX + '/' + action;
+            : DIMP.conf.URI_AJAX + action;
         params = $H(params);
         opts = opts || {};
 
@@ -208,7 +208,7 @@ var DimpCore = {
             var log = 0;
 
             switch (m.type) {
-            case 'dimp.timeout':
+            case 'horde.ajaxtimeout':
                 this.logout(m.message);
                 return true;
 
@@ -267,7 +267,7 @@ var DimpCore = {
     logout: function(url)
     {
         this.is_logout = true;
-        this.redirect(url || (DIMP.conf.URI_AJAX + '/LogOut'));
+        this.redirect(url || (DIMP.conf.URI_AJAX + 'LogOut'));
     },
 
     redirect: function(url, force)
diff --git a/imp/lib/Ajax/Application.php b/imp/lib/Ajax/Application.php
new file mode 100644 (file)
index 0000000..181fc0e
--- /dev/null
@@ -0,0 +1,1518 @@
+<?php
+/**
+ * Defines the AJAX interface for IMP.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @package IMP
+ */
+class IMP_Ajax_Application extends Horde_Ajax_Application_Base
+{
+    /**
+     * The list of actions that require readonly access to the session.
+     *
+     * @var array
+     */
+    protected $_readOnly = array(
+        'GetReplyData', 'Html2Text', 'Text2Html'
+    );
+
+    /**
+     * Returns a notification handler object to use to output any
+     * notification messages triggered by the AJAX action.
+     *
+     * @return Horde_Notification_Handler_Base  The notification handler.
+     */
+    public function notificationHandler()
+    {
+        return $GLOBALS['imp_notify'];
+    }
+
+    /**
+     * AJAX action: Create a mailbox.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'mbox' - (string) The name of the new mailbox.
+     * 'parent' - (string) The parent mailbox.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * </pre>
+     */
+    public function CreateMailbox($vars)
+    {
+        if (!$vars->mbox) {
+            return false;
+        }
+
+        $imptree = IMP_Imap_Tree::singleton();
+        $imptree->eltDiffStart();
+
+        $imp_folder = IMP_Folder::singleton();
+
+        $new = Horde_String::convertCharset($vars->mbox, Horde_Nls::getCharset(), 'UTF7-IMAP');
+        try {
+            $new = $imptree->createMailboxName($vars->parent, $new);
+            $result = $imp_folder->create($new, $GLOBALS['prefs']->getValue('subscribe'));
+            if ($result) {
+                $result = IMP_Dimp::getFolderResponse($imptree);
+            }
+        } catch (Horde_Exception $e) {
+            $GLOBALS['notification']->push($e, 'horde.error');
+            $result = false;
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Delete a mailbox.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'mbox' - (string) The full mailbox name to delete.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * </pre>
+     */
+    public function DeleteMailbox($vars)
+    {
+        if (!$vars->mbox) {
+            return false;
+        }
+
+        $imptree = IMP_Imap_Tree::singleton();
+        $imptree->eltDiffStart();
+
+        if ($imp_search->isEditableVFolder($vars->mbox)) {
+            $GLOBALS['notification']->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $imp_search->getLabel($vars->mbox)), 'horde.success');
+            $imp_search->deleteSearchQuery($vars->mbox);
+            $result = true;
+        } else {
+            $imp_folder = IMP_Folder::singleton();
+            $result = $imp_folder->delete(array($vars->mbox));
+        }
+
+        return $result
+            ? IMP_Dimp::getFolderResponse($imptree)
+            : $result;
+    }
+
+    /**
+     * AJAX action: Rename a mailbox.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'new_name' - (string) New mailbox name (child node).
+     * 'new_parent' - (string) New parent name.
+     * 'old_name' - (string) Full name of old mailbox.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * </pre>
+     */
+    public function RenameMailbox($vars)
+    {
+        if (!$vars->old_name || !$vars->new_name) {
+            return false;
+        }
+
+        $imptree = IMP_Imap_Tree::singleton();
+        $imptree->eltDiffStart();
+
+        $imp_folder = IMP_Folder::singleton();
+        $result = false;
+
+        try {
+            $new = Horde_String::convertCharset($imptree->createMailboxName($vars->new_parent, $vars->new_name), Horde_Nls::getCharset(), 'UTF7-IMAP');
+
+            if (($vars->old_name != $new) &&
+                $imp_folder->rename($vars->old_name, $new)) {
+                $result = IMP_Dimp::getFolderResponse($imptree);
+            }
+        } catch (Horde_Exception $e) {
+            $GLOBALS['notification']->push($e, 'horde.error');
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Empty a mailbox.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'mbox' - (string) The full mailbox name to empty.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'mbox' - (string) The mailbox that was emptied.
+     * </pre>
+     */
+    public function EmptyMailbox($vars)
+    {
+        if (!$vars->mbox) {
+            return false;
+        }
+
+        $imp_message = IMP_Message::singleton();
+        $imp_message->emptyMailbox(array($vars->mbox));
+
+        $result = new stdClass;
+        $result->mbox = $vars->mbox;
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Flag all messages in a mailbox.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'flags' - (string) The flags to set (JSON serialized array).
+     * 'mbox' - (string) The full malbox name.
+     * 'set' - (integer) Set the flags?
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'flags' - (array) The list of flags that were set.
+     * 'mbox' - (string) The full mailbox name.
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * 'set' - (integer) 1 if the flag was set. Unset otherwise.
+     * </pre>
+     */
+    public function FlagAll($vars)
+    {
+        $flags = Horde_Serialize::unserialize($vars->flags, Horde_Serialize::JSON);
+        if (!$vars->mbox || empty($flags)) {
+            return false;
+        }
+
+        $imp_message = IMP_Message::singleton();
+        $result = $imp_message->flagAllInMailbox($flags, array($vars->mbox), $vars->set);
+
+        if ($result) {
+            $result = new stdClass;
+            $result->flags = $flags;
+            $result->mbox = $vars->mbox;
+            if ($vars->set) {
+                $result->set = 1;
+            }
+
+            $poll = $this->_getPollInformation($vars->mbox);
+            if (!empty($poll)) {
+                $result->poll = array($vars->mbox => $poll[$vars->mbox]);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: List mailboxes.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'all' - (integer) 1 to show all mailboxes.
+     * 'initial' - (string) 1 to indicate the initial request for mailbox
+     *             list.
+     * 'mboxes' - (string) The list of mailboxes to process (JSON encoded
+     *            array).
+     * 'reload' - (integer) 1 to force reload of mailboxes.
+     * 'unsub' - (integer) 1 to show unsubscribed mailboxes.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * 'quota' - (array) See _getQuota().
+     * </pre>
+     */
+    public function ListMailboxes($vars)
+    {
+        $imptree = IMP_Imap_Tree::singleton();
+        $mask = IMP_Imap_Tree::FLIST_CONTAINER | IMP_Imap_Tree::FLIST_VFOLDER | IMP_Imap_Tree::FLIST_ELT;
+        if ($vars->unsub) {
+            $mask |= IMP_Imap_Tree::FLIST_UNSUB;
+        }
+
+        if (!$vars->all) {
+            $mask |= IMP_Imap_Tree::FLIST_NOCHILDREN;
+            if ($vars->initial || $vars->reload) {
+                $mask |= IMP_Imap_Tree::FLIST_ANCESTORS | IMP_Imap_Tree::FLIST_SAMELEVEL;
+            }
+        }
+
+        $folder_list = array();
+        foreach (Horde_Serialize::unserialize($vars->mboxes, Horde_Serialize::JSON) as $val) {
+            $folder_list += $imptree->folderList($mask, $val);
+        }
+
+        /* Add special folders explicitly to the initial folder list, since
+         * they are ALWAYS displayed and may appear outside of the folder
+         * slice requested. */
+        if ($vars->initial) {
+            foreach ($imptree->getSpecialMailboxes() as $val) {
+                if (!is_array($val)) {
+                    $val = array($val);
+                }
+
+                foreach ($val as $val2) {
+                    if (!isset($folder_list[$val2]) &&
+                        ($elt = $imptree->element($val2))) {
+                        $folder_list[$val2] = $elt;
+                    }
+                }
+            }
+        }
+
+        $result = IMP_Dimp::getFolderResponse($imptree, array(
+            'a' => array_values($folder_list),
+            'c' => array(),
+            'd' => array()
+        ));
+
+        $quota = $this->_getQuota();
+        if (!is_null($quota)) {
+            $result['quota'] = $quota;
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Poll mailboxes.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed() and _viewPortData().
+     *                               Additional variables used:
+     * <pre>
+     * 'view' - (string) The current view (mailbox).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * 'quota' - (array) See _getQuota().
+     * 'ViewPort' - (object) See _viewPortData().
+     * </pre>
+     */
+    public function Poll($vars)
+    {
+        $changed = false;
+        $imptree = IMP_Imap_Tree::singleton();
+
+        $result = new stdClass;
+        $result->poll = array();
+
+        foreach ($imptree->getPollList() as $val) {
+            if ($info = $imptree->getElementInfo($val)) {
+                $result->poll[$val] = intval($info['unseen']);
+            }
+        }
+
+        if ($vars->view &&
+            ($changed = $this->_changed($vars))) {
+            $result->ViewPort = $this->_viewPortData($vars, true);
+        }
+
+        if (!is_null($changed)) {
+            $quota = $this->_getQuota();
+            if (!is_null($quota)) {
+                $result->quota = $quota;
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Modify list of polled mailboxes.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'add' - (integer) 1 to add to the poll list, 0 to remove.
+     * 'mbox' - (string) The full mailbox name to modify.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'add' - (integer) 1 if added to the poll list, 0 if removed.
+     * 'mbox' - (string) The full mailbox name modified.
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * </pre>
+     */
+    public function ModifyPoll($vars)
+    {
+        if (!$vars->mbox) {
+            return false;
+        }
+
+        $display_folder = IMP::displayFolder($vars->mbox);
+
+        $imptree = IMP_Imap_Tree::singleton();
+
+        $result = new stdClass;
+        $result->add = intval($vars->add);
+        $result->mbox = $vars->mbox;
+
+        if ($vars->add) {
+            $imptree->addPollList($vars->view);
+            if ($info = $imptree->getElementInfo($vars->view)) {
+                $result->poll = array($vars->view => intval($info['unseen']));
+            }
+            $GLOBALS['notification']->push(sprintf(_("\"%s\" mailbox now polled for new mail."), $display_folder), 'horde.success');
+        } else {
+            $imptree->removePollList($vars->view);
+            $GLOBALS['notification']->push(sprintf(_("\"%s\" mailbox no longer polled for new mail."), $display_folder), 'horde.success');
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: [un]Subscribe to a mailbox.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'mbox' - (string) The full mailbox name to [un]subscribe to.
+     * 'sub' - (integer) 1 to subscribe, empty to unsubscribe.
+     * </pre>
+     *
+     * @return boolean  True on success, false on failure.
+     */
+    public function Subscribe($vars)
+    {
+        if (!$GLOBALS['prefs']->getValue('subscribe')) {
+            return false;
+        }
+
+        $imp_folder = IMP_Folder::singleton();
+        return $vars->sub
+            ? $imp_folder->subscribe(array($vars->mbox))
+            : $imp_folder->unsubscribe(array($vars->mbox));
+    }
+
+    /**
+     * AJAX action: Output ViewPort data.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed() and _viewPortData().
+     *                               Additional variables used:
+     * <pre>
+     * 'checkcache' - (integer) If 1, only send data if cache has been
+     *                invalidated.
+     * 'rangeslice' - (string) Range slice. See js/ViewPort.js.
+     * 'requestid' - (string) Request ID. See js/ViewPort.js.
+     * 'sortby' - (integer) The Horde_Imap_Client sort constant.
+     * 'sortdir' - (integer) 0 for ascending, 1 for descending.
+     * 'view' - (string) The current full mailbox name.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'ViewPort' - (object) See _viewPortData().
+     * </pre>
+     */
+    public function ViewPort($vars)
+    {
+        if (!$vars->view) {
+            return false;
+        }
+
+        /* Change sort preferences if necessary. */
+        if (isset($vars->sortby) || isset($vars->sortdir)) {
+            IMP::setSort($vars->sortby, $vars->sortdir, $vars->view);
+        }
+
+        $changed = $this->_changed($vars, false);
+
+        if (is_null($changed)) {
+            $list_msg = new IMP_Views_ListMessages();
+            $result = new stdClass;
+            $result->ViewPort = $list_msg->getBaseOb($vars->view);
+
+            $req_id = $vars->requestid;
+            if (!is_null($req_id)) {
+                $result->ViewPort->requestid = intval($req_id);
+            }
+        } elseif ($changed || $vars->rangeslice || !$vars->checkcache) {
+            $result = new stdClass;
+            $result->ViewPort = $this->_viewPortData($vars, $changed);
+        } else {
+            $result = false;
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Move messages.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed(), _generateDeleteResult(), and
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'mboxto' - (string) Mailbox to move the message to.
+     * 'uid' - (string) Indices of the messages to move (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object (see
+     *                _generateDeleteResult() for format).
+     */
+    public function MoveMessages($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        if (!$vars->tofld || empty($indices)) {
+            return false;
+        }
+
+        $change = $this->_changed($vars, true);
+
+        if (is_null($change)) {
+            return false;
+        }
+
+        $imp_message = IMP_Message::singleton();
+        $result = $imp_message->copy($vars->tofld, 'move', $indices);
+
+        if ($result) {
+            $result = $this->_generateDeleteResult($vars, $indices, $change);
+            /* Need to manually set remove to true since we want to remove
+             * message from the list no matter the current pref
+             * settings. */
+            $result->deleted->remove = 1;
+
+            /* Update poll information for destination folder if necessary.
+             * Poll information for current folder will be added by
+             * _generateDeleteResult() call above. */
+            if ($poll = $this->_getPollInformation($vars->tofld)) {
+                $result->poll = array_merge(isset($result->poll) ? $result->poll : array(), $poll);
+            }
+        } else {
+            $result = $this->_checkUidvalidity($vars);
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Copy messages.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'mboxto' - (string) Mailbox to move the message to.
+     * 'uid' - (string) Indices of the messages to copy (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'ViewPort' - (object) See _viewPortData().
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * </pre>
+     */
+    public function CopyMessages($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        if (!$vars->mboxto || empty($indices)) {
+            return false;
+        }
+
+        $imp_message = IMP_Message::singleton();
+
+        if ($result = $imp_message->copy($vars->mboxto, 'copy', $indices)) {
+            if ($poll = _getPollInformation($vars->mboxto)) {
+                $result->poll = array_merge(isset($result->poll) ? $result->poll : array(), $poll);
+            }
+        } else {
+            $result = $this->_checkUidvalidity($vars);
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Flag messages.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'flags' - (string) The flags to set (JSON serialized array).
+     * 'uid' - (string) Indices of the messages to flag (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  True on success, on failure an object with these
+     *                entries:
+     * <pre>
+     * 'ViewPort' - (object) See _viewPortData().
+     * </pre>
+     */
+    public function FlagMessages($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        if (!$vars->flags || empty($indices)) {
+            return false;
+        }
+
+        $flags = Horde_Serialize::unserialize($vars->flags, Horde_Serialize::JSON);
+        $imp_message = IMP_Message::singleton();
+        $result = false;
+        $set = $notset = array();
+
+        foreach ($flags as $val) {
+            if ($val[0] == '-') {
+                $notset[] = substr($val, 1);
+            } else {
+                $set[] = $val;
+            }
+        }
+
+        if (!empty($set)) {
+            $result = $imp_message->flag($set, $indices, true);
+        }
+
+        if (!empty($notset)) {
+            $result = $imp_message->flag($notset, $indices, false);
+        }
+
+        return $result
+            ? true
+            : $this->_checkUidvalidity($vars);
+    }
+
+    /**
+     * AJAX action: Delete messages.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed(), _generateDeleteResult(), and
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'uid' - (string) Indices of the messages to delete (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object (see
+     *                _generateDeleteResult() for format).
+     */
+    public function DeleteMessages($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        if (empty($indices)) {
+            return false;
+        }
+
+        $imp_message = IMP_Message::singleton();
+        $change = $this->_changed($vars, true);
+
+        if ($imp_message->delete($indices)) {
+            return $this->_generateDeleteResult($vars, $indices, $change, !$GLOBALS['prefs']->getValue('hide_deleted') && !$GLOBALS['prefs']->getValue('use_trash'));
+        }
+
+        return is_null($change)
+            ? false
+            : $this->_checkUidvalidity($vars);
+    }
+
+    /**
+     * AJAX action: Add contact.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'email' - (string) The email address to name.
+     * 'name' - (string) The name associated with the email address.
+     * </pre>
+     *
+     * @return boolean  True on success, false on failure.
+     */
+    public function AddContact($vars)
+    {
+        // Allow name to be empty.
+        if (!$vars->email) {
+            return false;
+        }
+
+        try {
+            IMP::addAddress($vars->email, $vars->name);
+            $GLOBALS['notification']->push(sprintf(_("%s was successfully added to your address book."), $vars->name ? $vars->name : $vars->email), 'horde.success');
+            return true;
+        } catch (Horde_Exception $e) {
+            $GLOBALS['notification']->push($e, 'horde.error');
+            return false;
+        }
+    }
+
+    /**
+     * AJAX action: Report message as [not]spam.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed(), _generateDeleteResult(), and
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'spam' - (integer) 1 to mark as spam, 0 to mark as innocent.
+     * 'uid' - (string) Indices of the messages to report (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object (see
+     *                _generateDeleteResult() for format).
+     */
+    public function ReportSpam($vars)
+    {
+        $change = $this->_changed($vars, false);
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        $result = false;
+
+        if (IMP_Spam::reportSpam($indices, $vars->spam ? 'spam' : 'notspam')) {
+            $result = $this->_generateDeleteResult($vars, $indices, $change);
+            /* If result of reportSpam() is non-zero, then we know the message
+             * has been removed from the current mailbox. */
+            $result->deleted->remove = 1;
+        } elseif (!is_null($change)) {
+            $result = $this->_checkUidvalidity($vars);
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Blacklist/whitelist addresses from messages.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed(), _generateDeleteResult(), and
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'blacklist' - (integer) 1 to blacklist, 0 to whitelist.
+     * 'uid' - (string) Indices of the messages to report (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object (see
+     *                _generateDeleteResult() for format).
+     */
+    public function Blacklist($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        if (empty($indices)) {
+            return false;
+        }
+
+        $imp_filter = new IMP_Filter();
+        $result = false;
+
+        if ($vars->blacklist) {
+            $change = $this->_changed($vars, false);
+            if (!is_null($change)) {
+                try {
+                    if ($imp_filter->blacklistMessage($indices, false)) {
+                        $result = $this->_generateDeleteResult($vars, $indices, $change);
+                    }
+                } catch (Horde_Exception $e) {
+                    $result = $this->_checkUidvalidity($vars);
+                }
+            }
+        } else {
+            try {
+                $imp_filter->whitelistMessage($indices, false);
+            } catch (Horde_Exception $e) {
+                $result = $this->_checkUidvalidity($vars);
+            }
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Generate data necessary to display preview message.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'uid' - (string) Index of the messages to preview (IMAP sequence
+     *         string) - must be single index.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the keys defined
+     *                in IMP_View_ShowMessage::showMessage().
+     */
+    public function ShowPreview($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        if (count($indices) != 1) {
+            return false;
+        }
+
+        $ptr = each($indices);
+        $args = array(
+            'mailbox' => $ptr['key'],
+            'preview' => true,
+            'uid' => intval(reset($ptr['value']))
+        );
+
+        try {
+            /* We know we are going to be exclusively dealing with this
+             * mailbox, so select it on the IMAP server (saves some STATUS
+             * calls). Open R/W to clear the RECENT flag. */
+            $GLOBALS['imp_imap']->ob()->openMailbox($ptr['key'], Horde_Imap_Client::OPEN_READWRITE);
+            $show_msg = new IMP_Views_ShowMessage();
+            $result = (object)$show_msg->showMessage($args);
+            if (isset($result->error)) {
+                $result = $this->_checkUidvalidity($vars, $result);
+            }
+        } catch (Horde_Imap_Client_Exception $e) {
+            $result = new stdClass;
+            $result->error = $e->getMessage();
+            $result->errortype = 'horde.error';
+            $result->mailbox = $args['mailbox'];
+            $result->uid = $args['uid'];
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Convert HTML to text.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'text' - (string) The text to convert.
+     * </pre>
+     *
+     * @return object  An object with the following entries:
+     * <pre>
+     * 'text' - (string) The converted text.
+     * </pre>
+     */
+    public function Html2Text($vars)
+    {
+        $result = new stdClass;
+        // Need to replace line endings or else IE won't display line endings
+        // properly.
+        $result->text = str_replace("\n", "\r\n", Horde_Text_Filter::filter($vars->text, 'html2text', array('charset' => Horde_Nls::getCharset())));
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Convert text to HTML.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'text' - (string) The text to convert.
+     * </pre>
+     *
+     * @return object  An object with the following entries:
+     * <pre>
+     * 'text' - (string) The converted text.
+     * </pre>
+     */
+    public function Text2Html($vars)
+    {
+        $result = new stdClass;
+        $result->text = Horde_Text_Filter::filter($vars->text, 'text2html', array('parselevel' => Horde_Text_Filter_Text2html::MICRO_LINKURL, 'class' => null, 'callback' => null));
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Get forward compose data.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * 'uid' - (string) Indices of the messages to forward (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'body' - (string) The body text of the message.
+     * 'format' - (string) Either 'text' or 'html'.
+     * 'fwd_list' - (array) See IMP_Dimp::getAttachmentInfo().
+     * 'header' - (array) The headers of the message.
+     * 'identity' - (integer) The identity ID to use for this message.
+     * 'imp_compose'- (string) The IMP_Compose cache identifier.
+     * 'ViewPort' - (object) See _viewPortData().
+     * </pre>
+     */
+    public function GetForwardData($vars)
+    {
+        $header = array();
+        $msg = $header = null;
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+
+        $i = each($indices);
+        $idx_string = reset($i['value']) . IMP::IDX_SEP . $i['key'];
+
+        try {
+            $imp_contents = IMP_Contents::singleton($idx_string);
+            $imp_compose = IMP_Compose::singleton($vars->imp_compose);
+            $imp_ui = new IMP_Ui_Compose();
+            $fwd_msg = $imp_ui->getForwardData($imp_compose, $imp_contents, $idx_string);
+            $header = $fwd_msg['headers'];
+            $header['replytype'] = 'forward';
+
+            $result = new stdClass;
+            /* Can't open session read-only since we need to store the message
+             * cache id. */
+            $result->imp_compose = $imp_compose->getCacheId();
+            $result->fwd_list = IMP_Dimp::getAttachmentInfo($imp_compose);
+            $result->body = $fwd_msg['body'];
+            $result->header = $header;
+            $result->format = $fwd_msg['format'];
+            $result->identity = $fwd_msg['identity'];
+        } catch (Horde_Exception $e) {
+            $GLOBALS['notification']->push($e, 'horde.error');
+            $result = $this->_checkUidvalidity($vars);
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Get reply data.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _checkUidvalidity(). Additional variables
+     *                               used:
+     * <pre>
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * 'type' - (string) See IMP_Compose::replyMessage().
+     * 'uid' - (string) Indices of the messages to reply to (IMAP sequence
+     *         string).
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'body' - (string) The body text of the message.
+     * 'format' - (string) Either 'text' or 'html'.
+     * 'header' - (array) The headers of the message.
+     * 'identity' - (integer) The identity ID to use for this message.
+     * 'imp_compose'- (string) The IMP_Compose cache identifier.
+     * 'ViewPort' - (object) See _viewPortData().
+     * </pre>
+     */
+    public function GetReplyData($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        $i = each($indices);
+        $idx_string = reset($i['value']) . IMP::IDX_SEP . $i['key'];
+
+        try {
+            $imp_contents = IMP_Contents::singleton($idx_string);
+            $imp_compose = IMP_Compose::singleton($vars->imp_compose);
+            $reply_msg = $imp_compose->replyMessage($vars->type, $imp_contents);
+            $header = $reply_msg['headers'];
+            $header['replytype'] = 'reply';
+
+            $result = new stdClass;
+            $result->imp_compose = $imp_compose->getCacheId();
+            $result->format = $reply_msg['format'];
+            $result->body = $reply_msg['body'];
+            $result->header = $header;
+            $result->identity = $reply_msg['identity'];
+        } catch (Horde_Exception $e) {
+            $GLOBALS['notification']->push($e, 'horde.error');
+            $result = $this->_checkUidvalidity($vars);
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Cancel compose.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * </pre>
+     *
+     * @return boolean  True.
+     */
+    public function CancelCompose($vars)
+    {
+        $imp_compose = IMP_Compose::singleton($vars->imp_compose);
+        $imp_compose->destroy(false);
+
+        return true;
+    }
+
+    /**
+     * AJAX action: Delete a draft.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * </pre>
+     *
+     * @return boolean  True.
+     */
+    public function DeleteDraft($vars)
+    {
+        $imp_compose = IMP_Compose::singleton($vars->imp_compose);
+        $imp_compose->destroy(false);
+
+        if ($draft_uid = $imp_compose->getMetadata('draft_uid')) {
+            $imp_message = IMP_Message::singleton();
+            $idx_array = array($draft_uid . IMP::IDX_SEP . IMP::folderPref($GLOBALS['prefs']->getValue('drafts_folder'), true));
+            $imp_message->delete($idx_array, array('nuke' => true));
+        }
+
+        return true;
+    }
+
+    /**
+     * AJAX action: Delete an attachment from compose data.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'atc_indices' - (string) Attachment IDs to delete.
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * </pre>
+     *
+     * @return boolean  True.
+     */
+    public function DeleteAttach($vars)
+    {
+        if ($vars->atc_indices) {
+            $imp_compose = IMP_Compose::singleton($vars->imp_compose);
+            foreach ($imp_compose->deleteAttachment($vars->atc_indices) as $val) {
+                $GLOBALS['notification']->push(sprintf(_("Deleted attachment \"%s\"."), Horde_Mime::decode($val)), 'horde.success');
+            }
+        }
+
+        return true;
+    }
+
+    /**
+     * AJAX action: Generate data necessary to display preview message.
+     *
+     * @param Horde_Variables $vars  Variables used: NONE.
+     *
+     * @return mixed  False on failure, or an object with the following
+     *                entries:
+     * <pre>
+     * 'linkTags' - (array) TODO
+     * 'portal' - (string) The portal HTML data.
+     * </pre>
+     */
+    public function ShowPortal($vars)
+    {
+        // Load the block list. Blocks are located in $dimp_block_list.
+        // KEY: Block label; VALUE: Horde_Block object
+        require IMP_BASE . '/config/portal.php';
+
+        $blocks = $linkTags = array();
+        $css_load = array('imp' => true);
+
+        foreach ($dimp_block_list as $title => $block) {
+            if ($block['ob'] instanceof Horde_Block) {
+                $app = $block['ob']->getApp();
+                // TODO: Fix CSS.
+                $content = $block['ob']->getContent();
+                $css_load[$app] = true;
+                // Don't do substitutions on our own blocks.
+                if ($app != 'imp') {
+                    $content = preg_replace('/<a href="([^"]+)"/',
+                                            '<a onclick="DimpBase.go(\'app:' . $app . '\', \'$1\');return false"',
+                                            $content);
+                    if (preg_match_all('/<link .*?rel="stylesheet".*?\/>/',
+                                       $content, $links)) {
+                        $content = str_replace($links[0], '', $content);
+                        foreach ($links[0] as $link) {
+                            if (preg_match('/href="(.*?)"/', $link, $href)) {
+                                $linkOb = new stdClass;
+                                $linkOb->href = $href[1];
+                                if (preg_match('/media="(.*?)"/', $link, $media)) {
+                                    $linkOb->media = $media[1];
+                                }
+                                $linkTags[] = $linkOb;
+                            }
+                        }
+                    }
+                }
+                if (!empty($content)) {
+                    $entry = array(
+                        'app' => $app,
+                        'content' => $content,
+                        'title' => $title,
+                        'class' => empty($block['class']) ? 'headerbox' : $block['class'],
+                    );
+                    if (!empty($block['domid'])) {
+                        $entry['domid'] = $block['domid'];
+                    }
+                    if (!empty($block['tag'])) {
+                        $entry[$block['tag']] = true;
+                    }
+                    $blocks[] = $entry;
+                }
+            }
+        }
+
+        $result = new stdClass;
+        $result->portal = '';
+        if (!empty($blocks)) {
+            $t = new Horde_Template(IMP_TEMPLATES . '/imp/');
+            $t->set('block', $blocks);
+            $result->portal = $t->fetch('portal.html');
+        }
+        $result->linkTags = $linkTags;
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Purge deleted messages.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _changed(), and _generateDeleteResult().
+     *                               Additional variables used:
+     * <pre>
+     * 'uid' - (string) Indices of the messages to purge (IMAP sequence
+     *         string).
+     * 'view' - (string) The current full mailbox.
+     * </pre>
+     *
+     * @return mixed  False on failure, or an object (see
+     *                _generateDeleteResult() for format).
+     */
+    public function PurgeDeleted($vars)
+    {
+        $indices = $GLOBALS['imp_imap']->ob()->utils->fromSequenceString($vars->uid);
+        $change = $this->_changed($vars, $indices);
+
+        if (is_null($change)) {
+            return false;
+        }
+
+        if (!$change) {
+            $sort = IMP::getSort($vars->view);
+            $change = ($sort['by'] == Horde_Imap_Client::SORT_THREAD);
+        }
+
+        $imp_message = IMP_Message::singleton();
+        $expunged = $imp_message->expungeMailbox(array($vars->view => 1), array('list' => true));
+
+        if (empty($expunged[$mbox])) {
+            return false;
+        }
+
+        $expunge_count = count($expunged[$mbox]);
+        $display_folder = IMP::displayFolder($mbox);
+        if ($expunge_count == 1) {
+            $GLOBALS['notification']->push(sprintf(_("1 message was purged from \"%s\"."), $display_folder), 'horde.success');
+        } else {
+            $GLOBALS['notification']->push(sprintf(_("%s messages were purged from \"%s\"."), $expunge_count, $display_folder), 'horde.success');
+        }
+        $result = $this->_generateDeleteResult($vars, $expunged, $change);
+
+        /* Need to manually set remove to true since we want to remove message
+         * from the list no matter the current pref settings. */
+        $result->deleted->remove = 1;
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Send a Message Disposition Notification (MDN).
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'uid' - (string) Indices of the messages to isend MDN for (IMAP sequence
+     *         string).
+     * 'view' - (string) The current full mailbox.
+     * </pre>
+     *
+     * @return boolean  True on success, false on failure.
+     */
+    public function SendMDN($vars)
+    {
+        if (!$vars->view || !$vars->uid) {
+            return false;
+        }
+
+        try {
+            $fetch_ret = $GLOBALS['imp_imap']->ob()->fetch($vars->view, array(
+                Horde_Imap_Client::FETCH_HEADERTEXT => array(array('parse' => true, 'peek' => false))
+            ), array('ids' => array($vars->uid)));
+        } catch (Horde_Imap_Client_Exception $e) {
+            return false;
+        }
+
+        $imp_ui = new IMP_Ui_Message();
+        $imp_ui->MDNCheck($vars->view, $vars->uid, reset($fetch_ret[$vars->uid]['headertext']), true);
+
+        return true;
+    }
+
+    /**
+     * AJAX action: Process a user-supplied PGP symmetric passphrase.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'dialog_input' - (string) Input from the dialog screen.
+     * 'symmetricid' - (string) The symmetric ID to process.
+     * </pre>
+     *
+     * @return object  An object with the following entries:
+     * <pre>
+     * 'error' - (string) An error message.
+     * 'success' - (integer) 1 on success, 0 on failure.
+     * </pre>
+     */
+    public function PGPSymmetric($vars)
+    {
+        $result = new stdClass;
+        $result->success = 0;
+
+        try {
+            $imp_pgp = Horde_Crypt::singleton(array('IMP', 'Pgp'));
+            Horde::requireSecureConnection();
+            if ($vars->dialog_input) {
+                if ($imp_pgp->storePassphrase('symmetric', $vars->dialog_input, $vars->symmetricid)) {
+                    $result->success = 1;
+                } else {
+                    $result->error = _("Invalid passphrase entered.");
+                }
+            } else {
+                $result->error = _("No passphrase entered.");
+            }
+        } catch (Horde_Exception $e) {
+            $result->error = $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Process a user-supplied passphrase for the PGP personal
+     * key.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'dialog_input' - (string) Input from the dialog screen.
+     * </pre>
+     *
+     * @return object  An object with the following entries:
+     * <pre>
+     * 'error' - (string) An error message.
+     * 'success' - (integer) 1 on success, 0 on failure.
+     * </pre>
+     */
+    public function PGPPersonal($vars)
+    {
+        $result = new stdClass;
+        $result->success = false;
+
+        try {
+            $imp_pgp = Horde_Crypt::singleton(array('IMP', 'Pgp'));
+            Horde::requireSecureConnection();
+            if ($vars->dialog_input) {
+                if ($imp_pgp->storePassphrase('personal', $vars->dialog_input)) {
+                    $result->success = 1;
+                } else {
+                    $result->error = _("Invalid passphrase entered.");
+                }
+            } else {
+                $result->error = _("No passphrase entered.");
+            }
+        } catch (Horde_Exception $e) {
+            $result->error = $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * AJAX action: Process a user-supplied passphrase for the S/MIME
+     * personal key.
+     *
+     * @param Horde_Variables $vars  Variables used:
+     * <pre>
+     * 'dialog_input' - (string) Input from the dialog screen.
+     * </pre>
+     *
+     * @return object  An object with the following entries:
+     * <pre>
+     * 'error' - (string) An error message.
+     * 'success' - (integer) 1 on success, 0 on failure.
+     * </pre>
+     */
+    public function SMIMEPersonal($vars)
+    {
+        $result = new stdClass;
+        $result->success = false;
+
+        try {
+            $imp_smime = Horde_Crypt::singleton(array('IMP', 'Smime'));
+            Horde::requireSecureConnection();
+            if ($vars->dialog_input) {
+                if ($imp_smime->storePassphrase($vars->dialog_input)) {
+                    $result->success = 1;
+                } else {
+                    $result->error = _("Invalid passphrase entered.");
+                }
+            } else {
+                $result->error = _("No passphrase entered.");
+            }
+        } catch (Horde_Exception $e) {
+            $result->error = $e->getMessage();
+        }
+
+        return $result;
+    }
+
+    /**
+     * Check the UID validity of the mailbox.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _viewPortData().
+     *
+     * @return mixed  The JSON result, possibly with ViewPort information
+     *                added if UID validity has changed.
+     */
+    protected function _checkUidvalidity($vars, $result = false)
+    {
+        try {
+            $GLOBALS['imp_imap']->checkUidvalidity($vars->view);
+        } catch (Horde_Exception $e) {
+            if (!is_object($result)) {
+                $result = new stdClass;
+            }
+            $result->ViewPort = $this->_viewPortData($vars, true);
+        }
+
+        return $result;
+    }
+
+    /**
+     * Generates the delete data needed for DimpBase.js.
+     *
+     * @param Horde_Variables $vars  See the list of variables needed for
+     *                               _viewPortData().
+     * @param array $indices         The list of indices that were deleted.
+     * @param boolean $changed       If true, add ViewPort information.
+     * @param boolean $nothread      If true, don't do thread sort check.
+     *
+     * @return object  An object with the following entries:
+     * <pre>
+     * 'deleted' - (object) See _generateDeleteResult().
+     * 'ViewPort' - (object) See _viewPortData().
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * </pre>
+     */
+    protected function _generateDeleteResult($vars, $indices, $change,
+                                             $nothread = false)
+    {
+        $imp_mailbox = IMP_Mailbox::singleton($vars->view);
+
+        $del = new stdClass;
+        $del->folder = $vars->view;
+        $del->uids = $GLOBALS['imp_imap']->ob()->utils->toSequenceString($indices, array('mailbox' => true));
+        $del->remove = intval($GLOBALS['prefs']->getValue('hide_deleted') ||
+                              $GLOBALS['prefs']->getValue('use_trash'));
+        $del->cacheid = $imp_mailbox->getCacheID($vars->view);
+
+        $result = new stdClass;
+        $result->deleted = $del;
+
+        /* Check if we need to update thread information. */
+        if (!$change && !$nothread) {
+            $sort = IMP::getSort($vars->view);
+            $change = ($sort['by'] == Horde_Imap_Client::SORT_THREAD);
+        }
+
+        if ($change) {
+            $result->ViewPort = $this->_viewPortData($vars, true);
+        }
+
+        $poll = $this->_getPollInformation($vars->view);
+        if (!empty($poll)) {
+            $result->poll = $poll;
+        }
+
+        return $result;
+    }
+
+    /**
+     * Determine if the cache information has changed.
+     *
+     * @param Horde_Variables $vars  The following variables:
+     * <pre>
+     * 'cacheid' - (string) The browser (ViewPort) cache identifier.
+     * 'forceUpdate' - (integer) If 1, forces an update,
+     * 'view' - (string) The current ViewPort view (mailbox).
+     * </pre>
+     * @param boolean $rw            Open mailbox as READ+WRITE?
+     *
+     * @return boolean  True if the cache information has changed.
+     */
+    protected function _changed($vars, $rw = null)
+    {
+        /* Only update search mailboxes on forced refreshes. */
+        if ($GLOBALS['imp_search']->isSearchMbox($vars->view)) {
+            return ($this->_action == 'ViewPort') || $vars->forceUpdate;
+        }
+
+        /* We know we are going to be dealing with this mailbox, so select it
+         * on the IMAP server (saves some STATUS calls). */
+        if (!is_null($rw) &&
+            !$GLOBALS['imp_search']->isSearchMbox($vars->view)) {
+            try {
+                $GLOBALS['imp_imap']->ob()->openMailbox($vars->view, $rw ? Horde_Imap_Client::OPEN_READWRITE : Horde_Imap_Client::OPEN_AUTO);
+            } catch (Horde_Imap_Client_Exception $e) {
+                $GLOBALS['notification']->push($e, 'horde.error');
+                return null;
+            }
+        }
+
+        $imp_mailbox = IMP_Mailbox::singleton($vars->view);
+        return ($imp_mailbox->getCacheID($vars->view) != $vars->cacheid);
+    }
+
+    /**
+     * Generate the information necessary for a ViewPort request from/to the
+     * browser.
+     *
+     * @param Horde_Variables $vars  The following variables:
+     * <pre>
+     * TODO
+     * </pre>
+     * @param boolean $change        True if cache information has changed.
+     *
+     * @return TODO
+     */
+    protected function _viewPortData($vars, $change)
+    {
+        $args = array(
+            'applyfilter' => $vars->applyfilter,
+            'cache' => $vars->cache,
+            'cacheid' => $vars->cacheid,
+            'change' => $change,
+            'initial' => $vars->initial,
+            'mbox' => $vars->view,
+            'rangeslice' => $vars->rangeslice,
+            'requestid' => $vars->requestid,
+            'qsearch' => $vars->qsearch,
+            'qsearchflag' => $vars->qsearchflag,
+            'qsearchmbox' => $vars->qsearchmbox,
+            'qsearchflagnot' => $vars->qsearchflagnot,
+            'sortby' => $vars->sortby,
+            'sortdir' => $vars->sortdir
+        );
+
+        if (!$vars->search || $args['initial']) {
+            $args += array(
+                'after' => intval($vars->after),
+                'before' => intval($vars->before)
+            );
+        }
+
+        if (!$vars->search) {
+            list($slice_start, $slice_end) = explode(':', $vars->slice, 2);
+            $args += array(
+                'slice_start' => intval($slice_start),
+                'slice_end' => intval($slice_end)
+            );
+        } else {
+            $search = Horde_Serialize::unserialize($vars->search, Horde_Serialize::JSON);
+            $args += array(
+                'search_uid' => isset($search->imapuid) ? $search->imapuid : null,
+                'search_unseen' => isset($search->unseen) ? $search->unseen : null
+            );
+        }
+
+        $list_msg = new IMP_Views_ListMessages();
+        return $list_msg->listMessages($args);
+    }
+
+    /**
+     * Generate poll information for a single mailbox.
+     *
+     * @param string $mbox  The full mailbox name.
+     *
+     * @return array  Key is the mailbox name, value is the number of unseen
+     *                messages.
+     */
+    protected function _getPollInformation($mbox)
+    {
+        $imptree = IMP_Imap_Tree::singleton();
+        $elt = $imptree->get($mbox);
+        if (!$imptree->isPolled($elt)) {
+            return array();
+        }
+
+        $info = $imptree->getElementInfo($mbox);
+        return array($mbox => isset($info['unseen']) ? intval($info['unseen']) : 0);
+    }
+
+    /**
+     * Generate quota information.
+     *
+     * @return array  'p': Quota percentage; 'm': Quota message
+     */
+    protected function _getQuota()
+    {
+        if (isset($_SESSION['imp']['quota']) &&
+            is_array($_SESSION['imp']['quota'])) {
+            $quotadata = IMP::quotaData(false);
+            if (!empty($quotadata)) {
+                return array('p' => round($quotadata['percent']), 'm' => $quotadata['message']);
+            }
+        }
+
+        return null;
+    }
+
+}
index e2cc37f..678c7a7 100644 (file)
@@ -1104,7 +1104,7 @@ class IMP
             'password' => true,
             'text' => $text,
             'type' => $type,
-            'uri' => Horde::applicationUrl('ajax.php', true, -1) . '/' . $type
+            'uri' => Horde::getServiceLink('ajax', 'imp') . '/' . $type
         );
 
         return 'IMPDialog.display(' . Horde::escapeJson($js_params, array('urlencode' => true)) . ')';
index 25ec372..3ef1440 100644 (file)
@@ -43,7 +43,7 @@ foreach ($imp_flags->getList(array('fgcolor' => true)) as $val) {
 /* Variables used in core javascript files. */
 $code['conf'] = array_filter(array(
     // URL variables
-    'URI_AJAX' => (string) Horde::applicationUrl('ajax.php'),
+    'URI_AJAX' => (string) Horde::getServiceLink('ajax', 'imp'),
     'URI_COMPOSE' => (string) Horde::applicationUrl('compose-dimp.php'),
     'URI_DIMP' => (string) Horde::applicationUrl('index-dimp.php'),
     'URI_MESSAGE' => (string) Horde::applicationUrl('message-dimp.php'),
diff --git a/kronolith/ajax.php b/kronolith/ajax.php
deleted file mode 100644 (file)
index 6b1c1e5..0000000
+++ /dev/null
@@ -1,689 +0,0 @@
-<?php
-/**
- * Performs the AJAX-requested action.
- *
- * Copyright 2005-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author  Michael Slusarz <slusarz@horde.org>
- * @author  Jan Schneider <jan@horde.org>
- * @author  Gonçalo Queirós <mail@goncaloqueiros.net>
- * @package Kronolith
- */
-
-function getDriver($cal)
-{
-    list($driver, $calendar) = explode('|', $cal);
-    if ($driver == 'internal' &&
-        !array_key_exists($calendar,
-                          Kronolith::listCalendars(false, Horde_Perms::SHOW))) {
-        $GLOBALS['notification']->push(_("Permission Denied"), 'horde.error');
-        return false;
-    }
-    $kronolith_driver = Kronolith::getDriver($driver, $calendar);
-    if ($kronolith_driver instanceof PEAR_Error) {
-        $GLOBALS['notification']->push($kronolith_driver, 'horde.error');
-        return false;
-    }
-    if ($driver == 'remote') {
-        $kronolith_driver->setParam('timeout', 15);
-    }
-    return $kronolith_driver;
-}
-
-function saveEvent($event)
-{
-    $result = $event->save();
-    if ($result instanceof PEAR_Error) {
-        $GLOBALS['notification']->push($result, 'horde.error');
-        return true;
-    }
-    $start = new Horde_Date(Horde_Util::getFormData('view_start'));
-    $end   = new Horde_Date(Horde_Util::getFormData('view_end'));
-    $end->hour = 23;
-    $end->min = $end->sec = 59;
-    Kronolith::addEvents($events, $event, $start, $end, true, true);
-    $result = new stdClass;
-    $result->cal = $event->calendarType . '|' . $event->calendar;
-    $result->view = Horde_Util::getFormData('view');
-    $result->sig = $start->dateString() . $end->dateString();
-    if (count($events)) {
-        $result->events = $events;
-    }
-    return $result;
-}
-
-require_once dirname(__FILE__) . '/lib/Application.php';
-$action = basename(Horde_Util::getPathInfo());
-if (empty($action)) {
-    // This is the only case where we really don't return anything, since
-    // the frontend can be presumed not to make this request on purpose.
-    // Other missing data cases we return a response of boolean false.
-    exit;
-}
-
-// The following actions do not need write access to the session and
-// should be opened read-only for performance reasons.
-$session_control = null;
-if (in_array($action, array())) {
-    $session_control = 'readonly';
-}
-
-try {
-    Horde_Registry::appInit('kronolith', array('authentication' => 'throw', 'session_control' => $session_control));
-} catch (Horde_Exception $e) {
-    /* Handle session timeouts when they come from an AJAX request. */
-    if (($e->getCode() == Horde_Registry::AUTH_FAILURE) &&
-        ($action != 'LogOut')) {
-        $notification = Horde_Notification::singleton();
-        $k_notify = $notification->attach('status', array(), 'Kronolith_Notification_Listener_Status');
-        $notification->push(str_replace('&amp;', '&', Horde_Auth::getLogoutUrl(array('reason' => Horde_Auth::REASON_SESSION))), 'kronolith.timeout', array('content.raw'));
-        Horde::sendHTTPResponse(Horde::prepareResponse(null, $k_notify), 'json');
-        exit;
-    }
-
-    Horde_Auth::authenticateFailure('kronolith', $e);
-}
-
-// Process common request variables.
-$cacheid = Horde_Util::getPost('cacheid');
-
-// Open an output buffer to ensure that we catch errors that might break JSON
-// encoding.
-ob_start();
-
-try {
-    $result = true;
-
-    switch ($action) {
-    case 'ListEvents':
-        $start = new Horde_Date(Horde_Util::getFormData('start'));
-        $end   = new Horde_Date(Horde_Util::getFormData('end'));
-        $cal   = Horde_Util::getFormData('cal');
-        $result = new stdClass;
-        $result->cal = $cal;
-        $result->view = Horde_Util::getFormData('view');
-        $result->sig = $start->dateString() . $end->dateString();
-        if (!($kronolith_driver = getDriver($cal))) {
-            break;
-        }
-        $events = $kronolith_driver->listEvents($start, $end, true, false, true);
-        if ($events instanceof PEAR_Error) {
-            $notification->push($events, 'horde.error');
-            break;
-        }
-        if (count($events)) {
-            $result->events = $events;
-        }
-        break;
-
-    case 'GetEvent':
-        if (!($kronolith_driver = getDriver(Horde_Util::getFormData('cal')))) {
-            break;
-        }
-        if (is_null($id = Horde_Util::getFormData('id'))) {
-            break;
-        }
-        $event = $kronolith_driver->getEvent($id, Horde_Util::getFormData('date'));
-        if ($event instanceof PEAR_Error) {
-            $notification->push($event, 'horde.error');
-            break;
-        }
-        if (!$event) {
-            $notification->push(_("The requested event was not found."), 'horde.error');
-            break;
-        }
-        $result = new stdClass;
-        $result->event = $event->toJson(null, true, $prefs->getValue('twentyFour') ? 'H:i' : 'h:i A');
-        break;
-
-    case 'SaveEvent':
-        if (!($kronolith_driver = getDriver(Horde_Util::getFormData('targetcalendar')))) {
-            break;
-        }
-        $event = $kronolith_driver->getEvent(Horde_Util::getFormData('id'));
-        if ($event instanceof PEAR_Error) {
-            $notification->push($event, 'horde.error');
-            break;
-        }
-        if (!$event) {
-            $notification->push(_("The requested event was not found."), 'horde.error');
-            break;
-        }
-        if (!$event->hasPermission(Horde_Perms::EDIT)) {
-            $notification->push(_("You do not have permission to edit this event."), 'horde.warning');
-            break;
-        }
-        $event->readForm();
-        $result = saveEvent($event);
-        if ($result !== true && Horde_Util::getFormData('sendupdates')) {
-            Kronolith::sendITipNotifications($event, $notification, Kronolith::ITIP_REQUEST);
-        }
-        break;
-
-    case 'QuickSaveEvent':
-        $kronolith_driver = Kronolith::getDriver();
-        try {
-            $event = Kronolith::quickAdd(Horde_Util::getFormData('text'),
-                                         Kronolith::getDefaultCalendar(Horde_Perms::EDIT));
-            if ($event instanceof PEAR_Error) {
-                $notification->push($event, 'horde.error');
-                break;
-            }
-            $result = saveEvent($event);
-        } catch (Horde_Exception $e) {
-            $notification->push($e);
-        }
-        break;
-
-    case 'UpdateEvent':
-        if (!($kronolith_driver = getDriver(Horde_Util::getFormData('cal')))) {
-            break;
-        }
-        if (is_null($id = Horde_Util::getFormData('id'))) {
-            break;
-        }
-        $event = $kronolith_driver->getEvent($id);
-        if ($event instanceof PEAR_Error) {
-            $notification->push($event, 'horde.error');
-            break;
-        }
-        if (!$event) {
-            $notification->push(_("The requested event was not found."), 'horde.error');
-            break;
-        }
-        if (!$event->hasPermission(Horde_Perms::EDIT)) {
-            $notification->push(_("You do not have permission to edit this event."), 'horde.warning');
-            break;
-        }
-        $attributes = Horde_Serialize::unserialize(Horde_Util::getFormData('att'), Horde_Serialize::JSON);
-        foreach ($attributes as $attribute => $value) {
-            switch ($attribute) {
-            case 'start_date':
-                $start = new Horde_Date($value);
-                $event->start->year = $start->year;
-                $event->start->month = $start->month;
-                $event->start->mday = $start->mday;
-                $event->end = $event->start->add(array('min' => $event->durMin));
-                break;
-
-            case 'start':
-                $event->start = new Horde_Date($value);
-                break;
-
-            case 'end':
-                $event->end = new Horde_Date($value);
-                if ($event->end->hour == 23 &&
-                    $event->end->min == 59 &&
-                    $event->end->sec == 59) {
-                    $event->end->mday++;
-                    $event->end->hour = $event->end->min = $event->end->sec = 0;
-                }
-                break;
-
-            case 'offDays':
-                $event->start->mday += $value;
-                $event->end->mday += $value;
-                break;
-
-            case 'offMins':
-                $event->start->min += $value;
-                $event->end->min += $value;
-                break;
-            }
-        }
-        $result = saveEvent($event);
-        break;
-
-    case 'DeleteEvent':
-        if (!($kronolith_driver = getDriver(Horde_Util::getFormData('cal')))) {
-            break;
-        }
-        if (is_null($id = Horde_Util::getFormData('id'))) {
-            break;
-        }
-        $event = $kronolith_driver->getEvent($id);
-        if ($event instanceof PEAR_Error) {
-            $notification->push($event, 'horde.error');
-            break;
-        }
-        if (!$event) {
-            $notification->push(_("The requested event was not found."), 'horde.error');
-            break;
-        }
-        if (!$event->hasPermission(Horde_Perms::DELETE)) {
-            $notification->push(_("You do not have permission to delete this event."), 'horde.warning');
-            break;
-        }
-        $deleted = $kronolith_driver->deleteEvent($event->id);
-        if ($deleted instanceof PEAR_Error) {
-            $notification->push($deleted, 'horde.error');
-            break;
-        }
-        if (Horde_Util::getFormData('sendupdates', false)) {
-            Kronolith::sendITipNotifications($event, $notification, Kronolith::ITIP_CANCEL);
-        }
-        $result = new stdClass;
-        $result->deleted = true;
-        break;
-
-    case 'SearchEvents':
-        $query = Horde_Serialize::unserialize(Horde_Util::getFormData('query'), Horde_Serialize::JSON);
-        if (!isset($query->start)) {
-            $query->start = new Horde_Date($_SERVER['REQUEST_TIME']);
-        }
-        if (!isset($query->end)) {
-            $query->end = null;
-        }
-        $cals   = Horde_Serialize::unserialize(Horde_Util::getFormData('cals'), Horde_Serialize::JSON);
-        $events = array();
-        foreach ($cals as $cal) {
-            if (!($kronolith_driver = getDriver($cal))) {
-                break;
-            }
-            $result = $kronolith_driver->search($query, true);
-            if ($result instanceof PEAR_Error) {
-                $notification->push($result, 'horde.error');
-                break;
-            }
-            if ($result) {
-                $events[$cal] = $result;
-            }
-        }
-        $result = new stdClass;
-        $result->view = 'search';
-        $result->query = Horde_Util::getFormData('query');
-        if ($events) {
-            $result->events = $events;
-        }
-        break;
-
-    case 'ListTasks':
-        if (!$registry->hasMethod('tasks/listTasks')) {
-            break;
-        }
-
-        $tasklist = Horde_Util::getFormData('list');
-        $tasktype = Horde_Util::getFormData('type');
-        $tasks = $registry->call('tasks/listTasks',
-                                 array(null, null, null, $tasklist, $tasktype == 'incomplete' ? 'future_incomplete' : $tasktype, true));
-        if ($tasks instanceof PEAR_Error) {
-            $notification->push($tasks, 'horde.error');
-            break;
-        }
-
-        $result = new stdClass;
-        $result->list = $tasklist;
-        $result->type = $tasktype;
-        $result->sig = Horde_Util::getFormData('sig');
-        if (count($tasks)) {
-            $result->tasks = $tasks;
-        }
-        break;
-
-    case 'GetTask':
-        if (!$registry->hasMethod('tasks/getTask')) {
-            break;
-        }
-        if (is_null($id = Horde_Util::getFormData('id')) ||
-            is_null($list = Horde_Util::getFormData('list'))) {
-            break;
-        }
-        $task = $registry->tasks->getTask($list, $id);
-        if ($task instanceof PEAR_Error) {
-            $notification->push($task, 'horde.error');
-            break;
-        }
-        if (!$task) {
-            $notification->push(_("The requested task was not found."), 'horde.error');
-            break;
-        }
-        $result = new stdClass;
-        $result->task = $task->toJson(true, $prefs->getValue('twentyFour') ? 'H:i' : 'h:i A');
-        break;
-
-    case 'SaveTask':
-        if (!$registry->hasMethod('tasks/updateTask') ||
-            !$registry->hasMethod('tasks/addTask')) {
-            break;
-        }
-
-        $id = Horde_Util::getFormData('task_id');
-        $list = Horde_Util::getFormData('old_tasklist');
-        $task = Horde_Util::getFormData('task');
-
-        $due = trim($task['due_date'] . ' ' . $task['due_time']);
-        if (!empty($due)) {
-            // 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.
-            $date_format = Horde_Nls::getLangInfo(D_FMT);
-            $old_locale = setlocale(LC_TIME, 0);
-            setlocale(LC_TIME, 'C');
-            $format = $date_format . ' '
-                . ($prefs->getValue('twentyFour') ? '%H:%M' : '%I:%M %p');
-
-            // Try exact format match first.
-            if ($date_arr = strptime($due, $format)) {
-                $task['due'] = 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']));
-            } else {
-                $task['due'] = new Horde_Date($due);
-            }
-            setlocale(LC_TIME, $old_locale);
-        }
-
-        if ($task['alarm']['on']) {
-            $task['alarm'] = $task['alarm']['value'] * $task['alarm']['unit'];
-        } else {
-            $task['alarm'] = 0;
-        }
-
-        if ($id && $list) {
-            $result = $registry->tasks->updateTask($list, $id, $task);
-        } else {
-            $result = $registry->tasks->addTask($task);
-        }
-        if ($result instanceof PEAR_Error) {
-            $notification->push($result, 'horde.error');
-            break;
-        }
-        if (!$id) {
-            $id = $result[0];
-        }
-        $task = $registry->tasks->getTask($task['tasklist'], $id);
-        if ($task instanceof PEAR_Error) {
-            $notification->push($task, 'horde.error');
-            break;
-        }
-        $result = new stdClass;
-        $result->type = $task->completed ? 'complete' : 'incomplete';
-        $result->list = $task->tasklist;
-        $result->sig = Horde_Util::getFormData('sig');
-        $result->tasks = array($id => $task->toJson(false, $prefs->getValue('twentyFour') ? 'H:i' : 'h:i A'));
-        break;
-
-    case 'DeleteTask':
-        if (!$registry->hasMethod('tasks/deleteTask')) {
-            break;
-        }
-        if (is_null($id = Horde_Util::getFormData('id')) ||
-            is_null($list = Horde_Util::getFormData('list'))) {
-            break;
-        }
-        $result = $registry->tasks->deleteTask($list, $id);
-        if ($result instanceof PEAR_Error) {
-            $notification->push($result, 'horde.error');
-            break;
-        }
-        $result = new stdClass;
-        $result->deleted = true;
-        break;
-
-    case 'ToggleCompletion':
-        if (!$registry->hasMethod('tasks/toggleCompletion')) {
-            break;
-        }
-        $tasklist = Horde_Util::getFormData('list');
-        $taskid = Horde_Util::getFormData('id');
-        $saved = $registry->call('tasks/toggleCompletion',
-                                 array($taskid, $tasklist));
-        if ($saved instanceof PEAR_Error) {
-            $notification->push($saved, 'horde.error');
-            break;
-        }
-
-        $result = new stdClass;
-        $result->toggled = true;
-        break;
-
-    case 'ListTopTags':
-        $tagger = new Kronolith_Tagger();
-        $result = new stdClass;
-        $result->tags = array();
-        $tags = $tagger->getCloud(Horde_Auth::getAuth(), 10);
-        foreach ($tags as $tag) {
-            $result->tags[] = $tag['tag_name'];
-        }
-        break;
-
-    case 'GetFreeBusy':
-        $fb = Kronolith_FreeBusy::get(Horde_Util::getFormData('email'), true);
-        if ($fb instanceof PEAR_Error) {
-            $notification->push($fb->getMessage(), 'horde.warning');
-            break;
-        }
-        $result = new stdClass;
-        $result->fb = $fb;
-        break;
-
-    case 'SearchCalendars':
-        $result = new stdClass;
-        $result->events = 'Searched for calendars: ' . Horde_Util::getFormData('title');
-        break;
-
-    case 'SaveCalendar':
-        $calendar_id = Horde_Util::getFormData('calendar');
-        $result = new stdClass;
-
-        switch (Horde_Util::getFormData('type')) {
-        case 'internal':
-            $info = array();
-            foreach (array('name', 'color', 'description', 'tags') as $key) {
-                $info[$key] = Horde_Util::getFormData($key);
-            }
-
-            // Create a calendar.
-            if (!$calendar_id) {
-                if (!Horde_Auth::getAuth() || $prefs->isLocked('default_share')) {
-                    break 2;
-                }
-                $calendar = Kronolith::addShare($info);
-                if ($calendar instanceof PEAR_Error) {
-                    $notification->push($calendar, 'horde.error');
-                    break 2;
-                }
-                $notification->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success');
-                $result->calendar = $calendar->getName();
-                break;
-            }
-
-            // Update a calendar.
-            $calendar = $kronolith_shares->getShare($calendar_id);
-            if ($calendar instanceof PEAR_Error) {
-                $notification->push($calendar, 'horde.error');
-                break 2;
-            }
-            $original_name = $calendar->get('name');
-            $updated = Kronolith::updateShare($calendar, $info);
-            if ($updated instanceof PEAR_Error) {
-                $notification->push($updated, 'horde.error');
-                break 2;
-            }
-            if ($calendar->get('name') != $original_name) {
-                $notification->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success');
-            } else {
-                $notification->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success');
-            }
-
-            break;
-
-        case 'tasklists':
-            $calendar = array();
-            foreach (array('name', 'color', 'description') as $key) {
-                $calendar[$key] = Horde_Util::getFormData($key);
-            }
-
-            // Create a task list.
-            if (!$calendar_id) {
-                if (!Horde_Auth::getAuth() || $prefs->isLocked('default_share')) {
-                    break 2;
-                }
-                $tasklist = $registry->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']);
-                if ($tasklist instanceof PEAR_Error) {
-                    $notification->push($tasklist, 'horde.error');
-                    break 2;
-                }
-                $notification->push(sprintf(_("The task list \"%s\" has been created."), $calendar['name']), 'horde.success');
-                $result->calendar = $tasklist;
-                break;
-            }
-
-            // Update a task list.
-            $calendar_id = substr($calendar_id, 6);
-            $tasklists = $registry->tasks->listTasklists(true, Horde_Perms::EDIT);
-            if (!isset($tasklists[$calendar_id])) {
-                $notification->push(_("You are not allowed to change this task list."), 'horde.error');
-                break 2;
-            }
-            $updated = $registry->tasks->updateTasklist($calendar_id, $calendar);
-            if ($updated instanceof PEAR_Error) {
-                $notification->push($updated, 'horde.error');
-                break 2;
-            }
-            if ($tasklists[$calendar_id]->get('name') != $calendar['name']) {
-                $notification->push(sprintf(_("The task list \"%s\" has been renamed to \"%s\"."), $tasklists[$calendar_id]->get('name'), $calendar['name']), 'horde.success');
-            } else {
-                $notification->push(sprintf(_("The task list \"%s\" has been saved."), $tasklists[$calendar_id]->get('name')), 'horde.success');
-            }
-
-            break;
-
-        case 'remote':
-            $calendar = array();
-            foreach (array('name', 'description', 'url', 'color', 'username', 'password') as $key) {
-                $calendar[$key] = Horde_Util::getFormData($key);
-            }
-            $subscribed = Kronolith::subscribeRemoteCalendar($calendar);
-            if ($subscribed instanceof PEAR_Error) {
-                $notification->push($subscribed, 'horde.error');
-                break 2;
-            }
-            if ($calendar_id) {
-                $notification->push(sprintf(_("The calendar \"%s\" has been saved."), $calendar['name']), 'horde.success');
-            } else {
-                $notification->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success');
-            }
-            break;
-        }
-
-        $result->saved = true;
-        $result->color = Kronolith::foregroundColor($calendar);
-        break;
-
-    case 'DeleteCalendar':
-        $calendar_id = Horde_Util::getFormData('calendar');
-
-        switch (Horde_Util::getFormData('type')) {
-        case 'internal':
-            $calendar = $kronolith_shares->getShare($calendar_id);
-            if ($calendar instanceof PEAR_Error) {
-                $notification->push($calendar, 'horde.error');
-                break 2;
-            }
-            $deleted = Kronolith::deleteShare($calendar);
-            if ($deleted instanceof PEAR_Error) {
-                $notification->push(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $deleted->getMessage()), 'horde.error');
-                break 2;
-            }
-            $notification->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success');
-            break;
-
-        case 'tasklists':
-            $calendar_id = substr($calendar_id, 6);
-            $tasklists = $registry->tasks->listTasklists(true);
-            if (!isset($tasklists[$calendar_id])) {
-                $notification->push(_("You are not allowed to delete this task list."), 'horde.error');
-                break 2;
-            }
-            $deleted = $registry->tasks->deleteTasklist($calendar_id);
-            if ($deleted instanceof PEAR_Error) {
-                $notification->push(sprintf(_("Unable to delete \"%s\": %s"), $tasklists[$calendar_id]->get('name'), $deleted->getMessage()), 'horde.error');
-                break 2;
-            }
-            $notification->push(sprintf(_("The task list \"%s\" has been deleted."), $tasklists[$calendar_id]->get('name')), 'horde.success');
-            break;
-
-        case 'remote':
-            $deleted = Kronolith::unsubscribeRemoteCalendar($calendar_id);
-            if ($deleted instanceof PEAR_Error) {
-                $notification->push($deleted, 'horde.error');
-                break 2;
-            }
-            $notification->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $deleted['name'], $deleted['url']), 'horde.success');
-            break;
-        }
-
-        $result = new stdClass;
-        $result->deleted = true;
-        break;
-
-    case 'GetRemoteInfo':
-        $params = array();
-        if ($user = Horde_Util::getFormData('username')) {
-            $params['user'] = $user;
-            $params['password'] = Horde_Util::getFormData('password');
-        }
-        if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
-            $params['proxy'] = $GLOBALS['conf']['http']['proxy'];
-        }
-        $driver = Kronolith_Driver::factory('Ical', $params);
-        $driver->open(Horde_Util::getFormData('url'));
-        try {
-            $ical = $driver->getRemoteCalendar(false);
-        } catch (Kronolith_Exception $e) {
-            if ($e->getCode() == 401) {
-                $result = new stdClass;
-                $result->auth = true;
-                break;
-            }
-            throw $e;
-        }
-        $result = new stdClass;
-        $result->success = true;
-        $name = $ical->getAttribute('X-WR-CALNAME');
-        if (!($name instanceof PEAR_Error)) {
-            $result->name = $name;
-        }
-        $desc = $ical->getAttribute('X-WR-CALDESC');
-        if (!($desc instanceof PEAR_Error)) {
-            $result->desc = $desc;
-        }
-        break;
-
-    case 'SaveCalPref':
-        break;
-
-    case 'ChunkContent':
-        $chunk = basename(Horde_Util::getPost('chunk'));
-        if (!empty($chunk)) {
-            $result = new stdClass;
-            $result->chunk = Horde_Util::bufferOutput('include', KRONOLITH_TEMPLATES . '/chunks/' . $chunk . '.php');
-        }
-        break;
-
-    default:
-        $notification->push('Unknown action ' . $action, 'horde.error');
-        break;
-    }
-} catch (Exception $e) {
-    $notification->push($e->getMessage(), 'horde.error');
-}
-
-// Clear the output buffer that we started above, and log any unexpected
-// output at a DEBUG level.
-$errors = ob_get_clean();
-if ($errors) {
-    Horde::logMessage('Kronolith: unexpected output: ' .
-                      $errors, __FILE__, __LINE__, PEAR_LOG_DEBUG);
-}
-
-// Send the final result.
-Horde::sendHTTPResponse(Horde::prepareResponse($result, $GLOBALS['kronolith_notify']), 'json');
index ba82f8b..0b67c04 100644 (file)
@@ -132,7 +132,7 @@ KronolithCore = {
 
         msgs.find(function(m) {
             switch (m.type) {
-            case 'kronolith.timeout':
+            case 'horde.ajaxtimeout':
                 this.logout(Kronolith.conf.timeout_url);
                 return true;
 
diff --git a/kronolith/lib/Ajax/Application.php b/kronolith/lib/Ajax/Application.php
new file mode 100644 (file)
index 0000000..097174b
--- /dev/null
@@ -0,0 +1,741 @@
+<?php
+/**
+ * Defines the AJAX interface for Kronolith.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @author  Jan Schneider <jan@horde.org>
+ * @author  Gonçalo Queirós <mail@goncaloqueiros.net>
+ * @package Kronolith
+ */
+class Kronolith_Ajax_Application extends Horde_Ajax_Application_Base
+{
+    /**
+     * Returns a notification handler object to use to output any
+     * notification messages triggered by the action.
+     *
+     * @return Horde_Notification_Handler_Base   The notification handler.
+     */
+    public function notificationHandler()
+    {
+        return $GLOBALS['kronolith_notify'];
+    }
+
+    /**
+     * TODO
+     */
+    public function ListEvents($vars)
+    {
+        $start = new Horde_Date($vars->start);
+        $end   = new Horde_Date($vars->end);
+
+        $result = new stdClass;
+        $result->cal = $vars->cal;
+        $result->view = $vars->view;
+        $result->sig = $start->dateString() . $end->dateString();
+        if (!($kronolith_driver = $this->_getDriver($vars->cal))) {
+            return $result;
+        }
+        $events = $kronolith_driver->listEvents($start, $end, true, false, true);
+        if ($events instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($events, 'horde.error');
+            return $result;
+            break;
+        }
+
+        if (count($events)) {
+            $result->events = $events;
+        }
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function GetEvent($vars)
+    {
+        if (!($kronolith_driver = $this->_getDriver($vars->cal)) ||
+            !isset($vars->id)) {
+            return false;
+        }
+
+        $event = $kronolith_driver->getEvent($vars->id, $vars->date);
+        if (!$event) {
+            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
+            return false;
+        } elseif ($event instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($event, 'horde.error');
+            return false;
+        }
+
+        $result = new stdClass;
+        $result->event = $event->toJson(null, true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A');
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function SaveEvent($vars)
+    {
+        if (!($kronolith_driver = $this->_getDriver($vars->targetcalendar))) {
+            return false;
+        }
+
+        $event = $kronolith_driver->getEvent($vars->id);
+        if (!$event) {
+            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
+            return false;
+        } elseif ($event instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($event, 'horde.error');
+            return false;
+        } elseif (!$event->hasPermission(Horde_Perms::EDIT)) {
+            $notification->push(_("You do not have permission to edit this event."), 'horde.warning');
+            return false;
+        }
+
+        $event->readForm();
+        $result = $this->_saveEvent($event);
+        if (($result !== true) && $vars->sendupdates) {
+            Kronolith::sendITipNotifications($event, $GLOBALS['notification'], Kronolith::ITIP_REQUEST);
+        }
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function QuickSaveEvent($vars)
+    {
+        $kronolith_driver = Kronolith::getDriver();
+        try {
+            $event = Kronolith::quickAdd($vars->text, Kronolith::getDefaultCalendar(Horde_Perms::EDIT));
+            if ($event instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($event, 'horde.error');
+                return false;
+            }
+            return $this->_saveEvent($event);
+        } catch (Horde_Exception $e) {
+            $GLOBALS['notification']->push($e);
+            return false;
+        }
+    }
+
+    /**
+     * TODO
+     */
+    public function UpdateEvent($vars)
+    {
+        if (!($kronolith_driver = $this->_getDriver($vars->cal)) ||
+            !isset($vars->id)) {
+            return false;
+        }
+
+        $event = $kronolith_driver->getEvent($vars->id);
+        if (!$event) {
+            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
+            return false;
+        } elseif ($event instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($event, 'horde.error');
+            return false;
+        } elseif (!$event->hasPermission(Horde_Perms::EDIT)) {
+            $GLOBALS['notification']->push(_("You do not have permission to edit this event."), 'horde.warning');
+            return false;
+        }
+
+        $attributes = Horde_Serialize::unserialize($vars->att, Horde_Serialize::JSON);
+        foreach ($attributes as $attribute => $value) {
+            switch ($attribute) {
+            case 'start_date':
+                $start = new Horde_Date($value);
+                $event->start->year = $start->year;
+                $event->start->month = $start->month;
+                $event->start->mday = $start->mday;
+                $event->end = $event->start->add(array('min' => $event->durMin));
+                break;
+
+            case 'start':
+                $event->start = new Horde_Date($value);
+                break;
+
+            case 'end':
+                $event->end = new Horde_Date($value);
+                if ($event->end->hour == 23 &&
+                    $event->end->min == 59 &&
+                    $event->end->sec == 59) {
+                    $event->end->mday++;
+                    $event->end->hour = $event->end->min = $event->end->sec = 0;
+                }
+                break;
+
+            case 'offDays':
+                $event->start->mday += $value;
+                $event->end->mday += $value;
+                break;
+
+            case 'offMins':
+                $event->start->min += $value;
+                $event->end->min += $value;
+                break;
+            }
+        }
+
+        return $this->_saveEvent($event);
+    }
+
+    /**
+     * TODO
+     */
+    public function DeleteEvent($vars)
+    {
+        if (!($kronolith_driver = $this->_getDriver($vars->cal)) ||
+            !isset($vars->id)) {
+            return false;
+        }
+
+        $event = $kronolith_driver->getEvent($id);
+        if (!$event) {
+            $GLOBALS['notification']->push(_("The requested event was not found."), 'horde.error');
+            return false;
+        } elseif ($event instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($event, 'horde.error');
+            return false;
+        } elseif (!$event->hasPermission(Horde_Perms::DELETE)) {
+            $GLOBALS['notification']->push(_("You do not have permission to delete this event."), 'horde.warning');
+            return false;
+        }
+
+        $deleted = $kronolith_driver->deleteEvent($event->id);
+        if ($deleted instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($deleted, 'horde.error');
+            return false;
+        }
+
+        if ($vars->sendupdates) {
+            Kronolith::sendITipNotifications($event, $GLOBALS['notification'], Kronolith::ITIP_CANCEL);
+        }
+
+        $result = new stdClass;
+        $result->deleted = true;
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function SearchEvents($vars)
+    {
+        $query = Horde_Serialize::unserialize($vars->query, Horde_Serialize::JSON);
+        if (!isset($query->start)) {
+            $query->start = new Horde_Date($_SERVER['REQUEST_TIME']);
+        }
+        if (!isset($query->end)) {
+            $query->end = null;
+        }
+
+        $cals = Horde_Serialize::unserialize($vars->cals, Horde_Serialize::JSON);
+        $events = array();
+        foreach ($cals as $cal) {
+            if (!($kronolith_driver = $this->_getDriver($cal))) {
+                break;
+            }
+            $result = $kronolith_driver->search($query, true);
+            if ($result instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($result, 'horde.error');
+                break;
+            }
+            if ($result) {
+                $events[$cal] = $result;
+            }
+        }
+
+        $result = new stdClass;
+        $result->view = 'search';
+        $result->query = $vars->query;
+        if ($events) {
+            $result->events = $events;
+        }
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function ListTasks($vars)
+    {
+        if (!$GLOBALS['registry']->hasMethod('tasks/listTasks')) {
+            return false;
+        }
+
+        $tasks = $GLOBALS['registry']->call('tasks/listTasks', array(null, null, null, $vars->list, $vars->type == 'incomplete' ? 'future_incomplete' : $vars->type, true));
+        if ($tasks instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($tasks, 'horde.error');
+            return false;
+        }
+
+        $result = new stdClass;
+        $result->list = $vars->list;
+        $result->type = $vars->type;
+        $result->sig = $vars->sig;
+        if (count($tasks)) {
+            $result->tasks = $tasks;
+        }
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function GetTask($vars)
+    {
+        if (!$GLOBALS['registry']->hasMethod('tasks/getTask') ||
+            !isset($vars->id) ||
+            !isset($vars->list)) {
+            return false;
+        }
+
+        $task = $registry->tasks->getTask($vars->list, $vars->id);
+        if (!$task) {
+            $GLOBALS['notification']->push(_("The requested task was not found."), 'horde.error');
+            return false;
+        } elseif ($task instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($task, 'horde.error');
+            return false;
+        }
+
+        $result = new stdClass;
+        $result->task = $task->toJson(true, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A');
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function SaveTask($vars)
+    {
+        if (!$GLOBALS['registry']->hasMethod('tasks/updateTask') ||
+            !$GLOBALS['registry']->hasMethod('tasks/addTask')) {
+            return false;
+        }
+
+        $id = $vars->task_id;
+        $list = $vars->old_tasklist;
+        $task = $vars->task;
+
+        $due = trim($task['due_date'] . ' ' . $task['due_time']);
+        if (!empty($due)) {
+            // 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.
+            $date_format = Horde_Nls::getLangInfo(D_FMT);
+            $old_locale = setlocale(LC_TIME, 0);
+            setlocale(LC_TIME, 'C');
+            $format = $date_format . ' ' . ($GLOBALS['prefs']->getValue('twentyFour') ? '%H:%M' : '%I:%M %p');
+
+            // Try exact format match first.
+            if ($date_arr = strptime($due, $format)) {
+                $task['due'] = 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']));
+            } else {
+                $task['due'] = new Horde_Date($due);
+            }
+            setlocale(LC_TIME, $old_locale);
+        }
+
+        $task['alarm'] = $task['alarm']['on']
+            ? $task['alarm']['value'] * $task['alarm']['unit']
+            : 0;
+
+        $result = ($id && $list)
+            ? $GLOBALS['registry']->tasks->updateTask($list, $id, $task)
+            : $GLOBALS['registry']->tasks->addTask($task);
+        if ($result instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($result, 'horde.error');
+            return false;
+        }
+
+        if (!$id) {
+            $id = $result[0];
+        }
+        $task = $GLOBALS['registry']->tasks->getTask($task['tasklist'], $id);
+        if ($task instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($task, 'horde.error');
+            return false;
+        }
+
+        $result = new stdClass;
+        $result->type = $task->completed ? 'complete' : 'incomplete';
+        $result->list = $task->tasklist;
+        $result->sig = $vars->sig;
+        $result->tasks = array($id => $task->toJson(false, $GLOBALS['prefs']->getValue('twentyFour') ? 'H:i' : 'h:i A'));
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function DeleteTask($vars)
+    {
+        if (!$GLOBALS['registry']->hasMethod('tasks/deleteTask') ||
+            !isset($vars->id) ||
+            !isset($vars->list)) {
+            return false;
+        }
+
+        $result = $GLOBALS['registry']->tasks->deleteTask($vars->list, $vars->id);
+        if ($result instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($result, 'horde.error');
+            return false;
+        }
+
+        $result = new stdClass;
+        $result->deleted = true;
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function ToggleCompletion($vars)
+    {
+        if (!$GLOBALS['registry']->hasMethod('tasks/toggleCompletion')) {
+            return false;
+        }
+
+        $saved = $GLOBALS['registry']->call('tasks/toggleCompletion', array($vars->id, $vars->list));
+        if ($saved instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($saved, 'horde.error');
+            return false;
+        }
+
+        $result = new stdClass;
+        $result->toggled = true;
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function ListTopTags($vars)
+    {
+        $tagger = new Kronolith_Tagger();
+        $result = new stdClass;
+        $result->tags = array();
+        $tags = $tagger->getCloud(Horde_Auth::getAuth(), 10);
+        foreach ($tags as $tag) {
+            $result->tags[] = $tag['tag_name'];
+        }
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function GetFreeBusy($vars)
+    {
+        $fb = Kronolith_FreeBusy::get($vars->email, true);
+        if ($fb instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($fb->getMessage(), 'horde.warning');
+            return false;
+        }
+        $result = new stdClass;
+        $result->fb = $fb;
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function SearchCalendars($vars)
+    {
+        $result = new stdClass;
+        $result->events = 'Searched for calendars: ' . $vars->title;
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function SaveCalendar($vars)
+    {
+        $calendar_id = $vars->calendar;
+        $result = new stdClass;
+
+        switch ($vars->type) {
+        case 'internal':
+            $info = array();
+            foreach (array('name', 'color', 'description', 'tags') as $key) {
+                $info[$key] = $vars->$key;
+            }
+
+            // Create a calendar.
+            if (!$calendar_id) {
+                if (!Horde_Auth::getAuth() ||
+                    $GLOBALS['prefs']->isLocked('default_share')) {
+                    return false;
+                }
+                $calendar = Kronolith::addShare($info);
+                if ($calendar instanceof PEAR_Error) {
+                    $GLOBALS['notification']->push($calendar, 'horde.error');
+                    return false;
+                }
+                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been created."), $info['name']), 'horde.success');
+                $result->calendar = $calendar->getName();
+                break;
+            }
+
+            // Update a calendar.
+            $calendar = $GLOBALS['kronolith_shares']->getShare($calendar_id);
+            if ($calendar instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($calendar, 'horde.error');
+                return false;
+            }
+            $original_name = $calendar->get('name');
+            $updated = Kronolith::updateShare($calendar, $info);
+            if ($updated instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($updated, 'horde.error');
+                return false;
+
+            }
+            if ($calendar->get('name') != $original_name) {
+                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been renamed to \"%s\"."), $original_name, $calendar->get('name')), 'horde.success');
+            } else {
+                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $original_name), 'horde.success');
+            }
+            break;
+
+        case 'tasklists':
+            $calendar = array();
+            foreach (array('name', 'color', 'description') as $key) {
+                $calendar[$key] = $vars->$key;
+            }
+
+            // Create a task list.
+            if (!$calendar_id) {
+                if (!Horde_Auth::getAuth() ||
+                    $GLOBALS['prefs']->isLocked('default_share')) {
+                    return false;
+                }
+                $tasklist = $GLOBALS['registry']->tasks->addTasklist($calendar['name'], $calendar['description'], $calendar['color']);
+                if ($tasklist instanceof PEAR_Error) {
+                    $GLOBALS['notification']->push($tasklist, 'horde.error');
+                    return false;
+                }
+                $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been created."), $calendar['name']), 'horde.success');
+                $result->calendar = $tasklist;
+                break;
+            }
+
+            // Update a task list.
+            $calendar_id = substr($calendar_id, 6);
+            $tasklists = $GLOBALS['registry']->tasks->listTasklists(true, Horde_Perms::EDIT);
+            if (!isset($tasklists[$calendar_id])) {
+                $GLOBALS['notification']->push(_("You are not allowed to change this task list."), 'horde.error');
+                return false;
+            }
+            $updated = $GLOBALS['registry']->tasks->updateTasklist($calendar_id, $calendar);
+            if ($updated instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($updated, 'horde.error');
+                return false;
+            }
+            if ($tasklists[$calendar_id]->get('name') != $calendar['name']) {
+                $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been renamed to \"%s\"."), $tasklists[$calendar_id]->get('name'), $calendar['name']), 'horde.success');
+            } else {
+                $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been saved."), $tasklists[$calendar_id]->get('name')), 'horde.success');
+            }
+            break;
+
+        case 'remote':
+            $calendar = array();
+            foreach (array('name', 'description', 'url', 'color', 'username', 'password') as $key) {
+                $calendar[$key] = $vars->$key;
+            }
+            $subscribed = Kronolith::subscribeRemoteCalendar($calendar);
+            if ($subscribed instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($subscribed, 'horde.error');
+                return false;
+            }
+            if ($calendar_id) {
+                $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been saved."), $calendar['name']), 'horde.success');
+            } else {
+                $GLOBALS['notification']->push(sprintf(_("You have been subscribed to \"%s\" (%s)."), $calendar['name'], $calendar['url']), 'horde.success');
+            }
+            break;
+        }
+
+        $result->saved = true;
+        $result->color = Kronolith::foregroundColor($calendar);
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function DeleteCalendar($vars)
+    {
+        $calendar_id = $vars->calendar;
+
+        switch ($vars->type) {
+        case 'internal':
+            $calendar = $GLOBALS['kronolith_shares']->getShare($calendar_id);
+            if ($calendar instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($calendar, 'horde.error');
+                return false;
+            }
+            $deleted = Kronolith::deleteShare($calendar);
+            if ($deleted instanceof PEAR_Error) {
+                $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $calendar->get('name'), $deleted->getMessage()), 'horde.error');
+                return false;
+            }
+            $GLOBALS['notification']->push(sprintf(_("The calendar \"%s\" has been deleted."), $calendar->get('name')), 'horde.success');
+            break;
+
+        case 'tasklists':
+            $calendar_id = substr($calendar_id, 6);
+            $tasklists = $GLOBALS['registry']->tasks->listTasklists(true);
+            if (!isset($tasklists[$calendar_id])) {
+                $GLOBALS['notification']->push(_("You are not allowed to delete this task list."), 'horde.error');
+                return false;
+            }
+            $deleted = $GLOBALS['registry']->tasks->deleteTasklist($calendar_id);
+            if ($deleted instanceof PEAR_Error) {
+                $GLOBALS['notification']->push(sprintf(_("Unable to delete \"%s\": %s"), $tasklists[$calendar_id]->get('name'), $deleted->getMessage()), 'horde.error');
+                return false;
+            }
+            $GLOBALS['notification']->push(sprintf(_("The task list \"%s\" has been deleted."), $tasklists[$calendar_id]->get('name')), 'horde.success');
+            break;
+
+        case 'remote':
+            $deleted = Kronolith::unsubscribeRemoteCalendar($calendar_id);
+            if ($deleted instanceof PEAR_Error) {
+                $GLOBALS['notification']->push($deleted, 'horde.error');
+                return false;
+            }
+            $GLOBALS['notification']->push(sprintf(_("You have been unsubscribed from \"%s\" (%s)."), $deleted['name'], $deleted['url']), 'horde.success');
+            break;
+        }
+
+        $result = new stdClass;
+        $result->deleted = true;
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function GetRemoteInfo($vars)
+    {
+        $params = array();
+        if ($user = $vars->username) {
+            $params['user'] = $user;
+            $params['password'] = $vars->password;
+        }
+        if (!empty($GLOBALS['conf']['http']['proxy']['proxy_host'])) {
+            $params['proxy'] = $GLOBALS['conf']['http']['proxy'];
+        }
+        $driver = Kronolith_Driver::factory('Ical', $params);
+        $driver->open($vars->url);
+        try {
+            $ical = $driver->getRemoteCalendar(false);
+        } catch (Kronolith_Exception $e) {
+            if ($e->getCode() == 401) {
+                $result = new stdClass;
+                $result->auth = true;
+                return $result;
+            }
+            throw $e;
+        }
+        $result = new stdClass;
+        $result->success = true;
+        $name = $ical->getAttribute('X-WR-CALNAME');
+        if (!($name instanceof PEAR_Error)) {
+            $result->name = $name;
+        }
+        $desc = $ical->getAttribute('X-WR-CALDESC');
+        if (!($desc instanceof PEAR_Error)) {
+            $result->desc = $desc;
+        }
+
+        return $result;
+    }
+
+    /**
+     * TODO
+     */
+    public function SaveCalPref($vars)
+    {
+        return false;
+    }
+
+    /**
+     * TODO
+     */
+    protected function _getDriver($cal)
+    {
+        list($driver, $calendar) = explode('|', $cal);
+        if ($driver == 'internal' &&
+            !array_key_exists($calendar,
+                              Kronolith::listCalendars(false, Horde_Perms::SHOW))) {
+            $GLOBALS['notification']->push(_("Permission Denied"), 'horde.error');
+            return false;
+        }
+        $kronolith_driver = Kronolith::getDriver($driver, $calendar);
+        if ($kronolith_driver instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($kronolith_driver, 'horde.error');
+            return false;
+        }
+        if ($driver == 'remote') {
+            $kronolith_driver->setParam('timeout', 15);
+        }
+        return $kronolith_driver;
+    }
+
+    /**
+     * TODO
+     */
+    protected function _saveEvent($event)
+    {
+        $result = $event->save();
+        if ($result instanceof PEAR_Error) {
+            $GLOBALS['notification']->push($result, 'horde.error');
+            return true;
+        }
+        $start = new Horde_Date(Horde_Util::getFormData('view_start'));
+        $end   = new Horde_Date(Horde_Util::getFormData('view_end'));
+        $end->hour = 23;
+        $end->min = $end->sec = 59;
+        Kronolith::addEvents($events, $event, $start, $end, true, true);
+        $result = new stdClass;
+        $result->cal = $event->calendarType . '|' . $event->calendar;
+        $result->view = Horde_Util::getFormData('view');
+        $result->sig = $start->dateString() . $end->dateString();
+        if (count($events)) {
+            $result->events = $events;
+        }
+        return $result;
+    }
+
+}
index d7393b2..b14c172 100644 (file)
@@ -180,7 +180,7 @@ class Kronolith
 
         /* Variables used in core javascript files. */
         $code['conf'] = array(
-            'URI_AJAX' => (string)Horde::url($kronolith_webroot . '/ajax.php', true, -1),
+            'URI_AJAX' => Horde::getServiceLink('ajax', 'kronolith'),
             'URI_IMG' => $registry->getImageDir() . '/',
             'URI_SNOOZE' => (string)Horde::url($registry->get('webroot', 'horde') . '/services/snooze.php', true, -1),
             'SESSION_ID' => defined('SID') ? SID : '',
diff --git a/shout/ajax.php b/shout/ajax.php
deleted file mode 100644 (file)
index cf0a4e1..0000000
+++ /dev/null
@@ -1,89 +0,0 @@
-<?php
-/**
- * ajax.php defines an set of brower procedure mechanisms
- * for the Shout application.
- *
- * Copyright 2010 Alkaloid Networks LLC (http://projects.alkaloid.net)
- *
- * See the enclosed file COPYING for license information (BSD). If you
- * did not receive this file, see
- * http://www.opensource.org/licenses/bsd-license.php.
- *
- * @author  Ben Klang <ben@alkaloid.net>
- * @package Shout
- */
-
-require_once dirname(__FILE__) . '/lib/Application.php';
-
-$action = Horde_Util::getFormData('action');
-if (empty($action)) {
-    // This is the only case where we really don't return anything, since
-    // the frontend can be presumed not to make this request on purpose.
-    // Other missing data cases we return a response of boolean false.
-    exit;
-}
-
-try {
-    $shout = Horde_Registry::appInit('shout', array('authentication' => 'throw'));
-} catch (Horde_Exception $e) {
-    /* Handle session timeouts when they come from an AJAX request. */
-    if (($e->getCode() == Horde_Registry::AUTH_FAILURE) &&
-        ($action != 'LogOut')) {
-        //FIXME: The below is certain to break since it relies on classes I did
-        //       not yet copy from IMP.
-        $notification = &Horde_Notification::singleton();
-        $shout_notify = $notification->attach('status', array('viewmode' => 'dimp'), 'Shout_Notification_Listener_Status');
-        $notification->push(str_replace('&amp;', '&', Horde_Auth::getLogoutUrl(array('reason' => Horde_Auth::REASON_SESSION))), 'shout.timeout', array('content.raw'));
-        Horde::sendHTTPResponse(Horde::prepareResponse(null, $shout_notify), 'json');
-        exit;
-    }
-
-    Horde_Auth::authenticateFailure('shout', $e);
-}
-
-$context = $_SESSION['shout']['context'];
-
-switch($action) {
-case 'addDestination':
-    try {
-        // FIXME: Use Form?
-        $exten = Horde_Util::getFormData('extension');
-        $type = Horde_Util::getFormData('type');
-        $dest = Horde_Util::getFormData('destination');
-        $shout->extensions->addDestination($context, $exten, $type, $dest);
-
-        $extensions = $shout->extensions->getExtensions($context);
-        Horde::sendHTTPResponse(Horde::prepareResponse($extensions), 'json');
-    } catch (Exception $e) {
-        Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
-        //FIXME: Create a way to notify the user of the failure.
-    }
-
-    break;
-
-case 'deleteDestination':
-    try {
-        // FIXME: Use Form?
-        $exten = Horde_Util::getFormData('extension');
-        $type = Horde_Util::getFormData('type');
-        $dest = Horde_Util::getFormData('destination');
-        $shout->extensions->deleteDestination($context, $exten, $type, $dest);
-
-        $extensions = $shout->extensions->getExtensions($context);
-        Horde::sendHTTPResponse(Horde::prepareResponse($extensions), 'json');
-    } catch (Exception $e) {
-        Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
-        //FIXME: Create a way to notify the user of the failure.
-    }
-    break;
-
-case 'getDestinations':
-    try {
-        $extensions = $shout->extensions->getExtensions($context);
-        Horde::sendHTTPResponse(Horde::prepareResponse($extensions), 'json');
-    } catch (Exception $e) {
-        Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
-        //FIXME: Create a way to notify the user of the failure.
-    }
-    break;
-}
diff --git a/shout/lib/Ajax/Application.php b/shout/lib/Ajax/Application.php
new file mode 100644 (file)
index 0000000..5c3e5e4
--- /dev/null
@@ -0,0 +1,81 @@
+<?php
+/**
+ * Defines the AJAX interface for Shout.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author  Michael Slusarz <slusarz@horde.org>
+ * @author  Ben Klang <ben@alkaloid.net>
+ * @package Shout
+ */
+class Shout_Ajax_Application extends Horde_Ajax_Application_Base
+{
+    /**
+     * Returns a notification handler object to use to output any
+     * notification messages triggered by the action.
+     *
+     * @return Horde_Notification_Handler_Base   The notification handler.
+     */
+    public function notificationHandler()
+    {
+        return $GLOBALS['kronolith_notify'];
+    }
+
+    /**
+     * TODO
+     */
+    public function addDestination($vars)
+    {
+        $context = $_SESSION['shout']['context'];
+        try {
+            // FIXME: Use Form?
+            $shout = $GLOBALS['registry']->getApiInstance('shout', 'application');
+            $shout->extensions->addDestination($context, $vars->extension, $vars->type, $vars->destination);
+
+            return $shout->extensions->getExtensions($context);
+        } catch (Exception $e) {
+            //FIXME: Create a way to notify the user of the failure.
+            Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+            return false;
+        }
+    }
+
+    /**
+     * TODO
+     */
+    public function deleteDestination($vars)
+    {
+        $context = $_SESSION['shout']['context'];
+        try {
+            // FIXME: Use Form?
+            $shout = $GLOBALS['registry']->getApiInstance('shout', 'application');
+            $shout->extensions->deleteDestination($context, $vars->extension, $vars->type, $vars->destination);
+
+            return $shout->extensions->getExtensions($context);
+        } catch (Exception $e) {
+            //FIXME: Create a way to notify the user of the failure.
+            Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+            return false;
+        }
+    }
+
+    /**
+     * TODO
+     */
+    public function getDestinations($vars)
+    {
+        $context = $_SESSION['shout']['context'];
+        try {
+            return $shout->extensions->getExtensions($context);
+        } catch (Exception $e) {
+            //FIXME: Create a way to notify the user of the failure.
+            Horde::logMessage($e->getMessage(), __FILE__, __LINE__, PEAR_LOG_ERR);
+            return false;
+        }
+    }
+
+}
+
index 1d2b7c5..1ca3a6d 100644 (file)
@@ -246,7 +246,7 @@ function processForm(event)
     }
 
     // FIXME: Better error handling
-    new Ajax.Request('<?php echo Horde::applicationUrl('ajax.php'); ?>',
+    new Ajax.Request('<?php echo Horde::serviceLink('ajax', 'shout') ?>',
     {
         method: 'post',
         parameters: form.serialize(true),
@@ -366,7 +366,7 @@ function delDest(exten, type, dest)
     }
 
     // FIXME: Better error handling
-    new Ajax.Request('<?php echo Horde::applicationUrl('ajax.php'); ?>',
+    new Ajax.Request('<?php echo Horde::serviceLink('ajax', 'shout') ?>',
     {
         method: 'post',
         parameters: params,
@@ -381,7 +381,7 @@ function delDest(exten, type, dest)
 
 destinations = $H();
 
-new Ajax.Request('<?php echo Horde::applicationUrl('ajax.php'); ?>',
+new Ajax.Request('<?php echo Horde::serviceLink('ajax', 'shout') ?>',
 {
     method: 'post',
     parameters: $H({'action': 'getDestinations'}),