From: Michael M Slusarz Date: Wed, 20 Jan 2010 20:51:11 +0000 (-0700) Subject: Ticket #4561: Abstract ajax interface to Horde X-Git-Url: https://git.internetallee.de/?a=commitdiff_plain;h=13879630963e68a2bfe3f85e37082bd6d5ac6631;p=horde.git Ticket #4561: Abstract ajax interface to Horde --- diff --git a/framework/Ajax/lib/Horde/Ajax.php b/framework/Ajax/lib/Horde/Ajax.php new file mode 100644 index 000000000..6c50d91dc --- /dev/null +++ b/framework/Ajax/lib/Horde/Ajax.php @@ -0,0 +1,36 @@ + + * @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 index 000000000..bf7d022ee --- /dev/null +++ b/framework/Ajax/lib/Horde/Ajax/Application/Base.php @@ -0,0 +1,76 @@ + + * @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 index 000000000..b6ca2921f --- /dev/null +++ b/framework/Ajax/lib/Horde/Ajax/Exception.php @@ -0,0 +1,16 @@ + + * @category Horde + * @package Horde_Ajax + */ +class Horde_Ajax_Exception extends Horde_Exception +{ +} diff --git a/framework/Ajax/package.xml b/framework/Ajax/package.xml index 39e2dfac2..02fabc68b 100644 --- a/framework/Ajax/package.xml +++ b/framework/Ajax/package.xml @@ -24,7 +24,8 @@ http://pear.php.net/dtd/package-2.0.xsd"> beta LGPL - * Add AutoCompleter driver. + * Add Application framework. + * Add AutoCompleter driver. * Initial release. @@ -32,6 +33,9 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + @@ -40,8 +44,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + @@ -58,6 +64,10 @@ http://pear.php.net/dtd/package-2.0.xsd"> Core pear.horde.org + + Util + pear.horde.org + @@ -72,11 +82,14 @@ http://pear.php.net/dtd/package-2.0.xsd"> + + + diff --git a/framework/Core/lib/Horde.php b/framework/Core/lib/Horde.php index 16aa95382..ca54bb440 100644 --- a/framework/Core/lib/Horde.php +++ b/framework/Core/lib/Horde.php @@ -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' * * @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; diff --git a/framework/Core/lib/Horde/Registry.php b/framework/Core/lib/Horde/Registry.php index 36072a800..e5d692003 100644 --- a/framework/Core/lib/Horde/Registry.php +++ b/framework/Core/lib/Horde/Registry.php @@ -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 diff --git a/horde/docs/CHANGES b/horde/docs/CHANGES index d60cbc392..e15343e85 100644 --- a/horde/docs/CHANGES +++ b/horde/docs/CHANGES @@ -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 index 000000000..7854f6a96 --- /dev/null +++ b/horde/services/ajax.php @@ -0,0 +1,72 @@ + + * @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('&', '&', 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('&', '&', 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 index 920bba6c5..000000000 --- a/imp/ajax.php +++ /dev/null @@ -1,882 +0,0 @@ - - * @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('&', '&', 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('&', '&', 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('//', - $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'); diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index dddc68095..bb0aef326 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -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) diff --git a/imp/js/DimpBase.js b/imp/js/DimpBase.js index 568787b7d..d54a39853 100644 --- a/imp/js/DimpBase.js +++ b/imp/js/DimpBase.js @@ -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); diff --git a/imp/js/DimpCore.js b/imp/js/DimpCore.js index f0dcc6283..d8d511557 100644 --- a/imp/js/DimpCore.js +++ b/imp/js/DimpCore.js @@ -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 index 000000000..181fc0ee6 --- /dev/null +++ b/imp/lib/Ajax/Application.php @@ -0,0 +1,1518 @@ + + * @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: + *
+     * 'mbox' - (string) The name of the new mailbox.
+     * 'parent' - (string) The parent mailbox.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * 
+ */ + 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: + *
+     * 'mbox' - (string) The full mailbox name to delete.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * 
+ */ + 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: + *
+     * 'new_name' - (string) New mailbox name (child node).
+     * 'new_parent' - (string) New parent name.
+     * 'old_name' - (string) Full name of old mailbox.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * 
+ */ + 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: + *
+     * 'mbox' - (string) The full mailbox name to empty.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'mbox' - (string) The mailbox that was emptied.
+     * 
+ */ + 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: + *
+     * 'flags' - (string) The flags to set (JSON serialized array).
+     * 'mbox' - (string) The full malbox name.
+     * 'set' - (integer) Set the flags?
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * '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.
+     * 
+ */ + 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: + *
+     * '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.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'a' - (array) Mailboxes that were added.
+     * 'c' - (array) Mailboxes that were changed.
+     * 'd' - (array) Mailboxes that were deleted.
+     * 'quota' - (array) See _getQuota().
+     * 
+ */ + 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: + *
+     * 'view' - (string) The current view (mailbox).
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * 'quota' - (array) See _getQuota().
+     * 'ViewPort' - (object) See _viewPortData().
+     * 
+ */ + 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: + *
+     * 'add' - (integer) 1 to add to the poll list, 0 to remove.
+     * 'mbox' - (string) The full mailbox name to modify.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * '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.
+     * 
+ */ + 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: + *
+     * 'mbox' - (string) The full mailbox name to [un]subscribe to.
+     * 'sub' - (integer) 1 to subscribe, empty to unsubscribe.
+     * 
+ * + * @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: + *
+     * '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.
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'ViewPort' - (object) See _viewPortData().
+     * 
+ */ + 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: + *
+     * 'mboxto' - (string) Mailbox to move the message to.
+     * 'uid' - (string) Indices of the messages to move (IMAP sequence
+     *         string).
+     * 
+ * + * @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: + *
+     * 'mboxto' - (string) Mailbox to move the message to.
+     * 'uid' - (string) Indices of the messages to copy (IMAP sequence
+     *         string).
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * 'ViewPort' - (object) See _viewPortData().
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * 
+ */ + 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: + *
+     * 'flags' - (string) The flags to set (JSON serialized array).
+     * 'uid' - (string) Indices of the messages to flag (IMAP sequence
+     *         string).
+     * 
+ * + * @return mixed True on success, on failure an object with these + * entries: + *
+     * 'ViewPort' - (object) See _viewPortData().
+     * 
+ */ + 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: + *
+     * 'uid' - (string) Indices of the messages to delete (IMAP sequence
+     *         string).
+     * 
+ * + * @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: + *
+     * 'email' - (string) The email address to name.
+     * 'name' - (string) The name associated with the email address.
+     * 
+ * + * @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: + *
+     * 'spam' - (integer) 1 to mark as spam, 0 to mark as innocent.
+     * 'uid' - (string) Indices of the messages to report (IMAP sequence
+     *         string).
+     * 
+ * + * @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: + *
+     * 'blacklist' - (integer) 1 to blacklist, 0 to whitelist.
+     * 'uid' - (string) Indices of the messages to report (IMAP sequence
+     *         string).
+     * 
+ * + * @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: + *
+     * 'uid' - (string) Index of the messages to preview (IMAP sequence
+     *         string) - must be single index.
+     * 
+ * + * @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: + *
+     * 'text' - (string) The text to convert.
+     * 
+ * + * @return object An object with the following entries: + *
+     * 'text' - (string) The converted text.
+     * 
+ */ + 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: + *
+     * 'text' - (string) The text to convert.
+     * 
+ * + * @return object An object with the following entries: + *
+     * 'text' - (string) The converted text.
+     * 
+ */ + 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: + *
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * 'uid' - (string) Indices of the messages to forward (IMAP sequence
+     *         string).
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * '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().
+     * 
+ */ + 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: + *
+     * '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).
+     * 
+ * + * @return mixed False on failure, or an object with the following + * entries: + *
+     * '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().
+     * 
+ */ + 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: + *
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * 
+ * + * @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: + *
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * 
+ * + * @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: + *
+     * 'atc_indices' - (string) Attachment IDs to delete.
+     * 'imp_compose' - (string) The IMP_Compose cache identifier.
+     * 
+ * + * @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: + *
+     * 'linkTags' - (array) TODO
+     * 'portal' - (string) The portal HTML data.
+     * 
+ */ + 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('/
/', + $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: + *
+     * 'uid' - (string) Indices of the messages to purge (IMAP sequence
+     *         string).
+     * 'view' - (string) The current full mailbox.
+     * 
+ * + * @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: + *
+     * 'uid' - (string) Indices of the messages to isend MDN for (IMAP sequence
+     *         string).
+     * 'view' - (string) The current full mailbox.
+     * 
+ * + * @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: + *
+     * 'dialog_input' - (string) Input from the dialog screen.
+     * 'symmetricid' - (string) The symmetric ID to process.
+     * 
+ * + * @return object An object with the following entries: + *
+     * 'error' - (string) An error message.
+     * 'success' - (integer) 1 on success, 0 on failure.
+     * 
+ */ + 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: + *
+     * 'dialog_input' - (string) Input from the dialog screen.
+     * 
+ * + * @return object An object with the following entries: + *
+     * 'error' - (string) An error message.
+     * 'success' - (integer) 1 on success, 0 on failure.
+     * 
+ */ + 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: + *
+     * 'dialog_input' - (string) Input from the dialog screen.
+     * 
+ * + * @return object An object with the following entries: + *
+     * 'error' - (string) An error message.
+     * 'success' - (integer) 1 on success, 0 on failure.
+     * 
+ */ + 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: + *
+     * 'deleted' - (object) See _generateDeleteResult().
+     * 'ViewPort' - (object) See _viewPortData().
+     * 'poll' - (array) Mailbox names as the keys, number of unseen messages
+     *          as the values.
+     * 
+ */ + 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: + *
+     * 'cacheid' - (string) The browser (ViewPort) cache identifier.
+     * 'forceUpdate' - (integer) If 1, forces an update,
+     * 'view' - (string) The current ViewPort view (mailbox).
+     * 
+ * @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: + *
+     * TODO
+     * 
+ * @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; + } + +} diff --git a/imp/lib/IMP.php b/imp/lib/IMP.php index e2cc37ffe..678c7a799 100644 --- a/imp/lib/IMP.php +++ b/imp/lib/IMP.php @@ -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)) . ')'; diff --git a/imp/templates/javascript_defs_dimp.php b/imp/templates/javascript_defs_dimp.php index 25ec37265..3ef144058 100644 --- a/imp/templates/javascript_defs_dimp.php +++ b/imp/templates/javascript_defs_dimp.php @@ -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 index 6b1c1e545..000000000 --- a/kronolith/ajax.php +++ /dev/null @@ -1,689 +0,0 @@ - - * @author Jan Schneider - * @author Gonçalo Queirós - * @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('&', '&', 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'); diff --git a/kronolith/js/kronolith.js b/kronolith/js/kronolith.js index ba82f8b9d..0b67c0434 100644 --- a/kronolith/js/kronolith.js +++ b/kronolith/js/kronolith.js @@ -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 index 000000000..097174b6d --- /dev/null +++ b/kronolith/lib/Ajax/Application.php @@ -0,0 +1,741 @@ + + * @author Jan Schneider + * @author Gonçalo Queirós + * @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; + } + +} diff --git a/kronolith/lib/Kronolith.php b/kronolith/lib/Kronolith.php index d7393b242..b14c1727a 100644 --- a/kronolith/lib/Kronolith.php +++ b/kronolith/lib/Kronolith.php @@ -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 index cf0a4e171..000000000 --- a/shout/ajax.php +++ /dev/null @@ -1,89 +0,0 @@ - - * @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('&', '&', 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 index 000000000..5c3e5e49c --- /dev/null +++ b/shout/lib/Ajax/Application.php @@ -0,0 +1,81 @@ + + * @author Ben Klang + * @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; + } + } + +} + diff --git a/shout/templates/extensions/list.inc b/shout/templates/extensions/list.inc index 1d2b7c59d..1ca3a6ddc 100644 --- a/shout/templates/extensions/list.inc +++ b/shout/templates/extensions/list.inc @@ -246,7 +246,7 @@ function processForm(event) } // FIXME: Better error handling - new Ajax.Request('', + new Ajax.Request('', { method: 'post', parameters: form.serialize(true), @@ -366,7 +366,7 @@ function delDest(exten, type, dest) } // FIXME: Better error handling - new Ajax.Request('', + new Ajax.Request('', { method: 'post', parameters: params, @@ -381,7 +381,7 @@ function delDest(exten, type, dest) destinations = $H(); -new Ajax.Request('', +new Ajax.Request('', { method: 'post', parameters: $H({'action': 'getDestinations'}),