Rewrite of IMP search handling.
authorMichael M Slusarz <slusarz@curecanti.org>
Mon, 20 Sep 2010 05:33:16 +0000 (23:33 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Mon, 20 Sep 2010 05:43:06 +0000 (23:43 -0600)
Total rewrite of search system.

Search queries, elements, and virtual folders broken into separate
classes for easier generation and better encapsulation of the data.
Also allows for more complex search types - for example, the "To:"
header search has been replaced by the Recipients search, which searches
all of To/Cc/Bcc. Another example: the bulk search query has been
created (although it is not yet implemented in the UI).

Virtual folder maintenance is now handled within a prefs groups to
consolidate duplicate code in both imp and dimp.

Internal storage of virtual folders has changed. IMP 4 virtual folders
will be automatically converted, but any virtual folders created using
git code in the last year or so is now invalid and no upgrade path is
available.

Removed the use_vinbox, use_vtrash, vinbox_id, and vtrash_id prefs.

59 files changed:
imp/config/prefs.php.dist
imp/docs/UPGRADING
imp/folders.php
imp/js/dimpbase.js
imp/js/search.js
imp/js/searchesprefs.js [new file with mode: 0644]
imp/lib/Ajax/Application.php
imp/lib/Api.php
imp/lib/Application.php
imp/lib/Auth.php
imp/lib/Block/Newmail.php
imp/lib/Dimp.php
imp/lib/Filter.php
imp/lib/Folder.php
imp/lib/IMP.php
imp/lib/Imap/Flags.php
imp/lib/Imap/Tree.php
imp/lib/Imap/Tree/Element.php
imp/lib/LoginTasks/SystemTask/GarbageCollection.php
imp/lib/LoginTasks/SystemTask/UpgradeFromImp4.php
imp/lib/LoginTasks/Task/PurgeSentmail.php
imp/lib/LoginTasks/Task/PurgeSpam.php
imp/lib/LoginTasks/Task/PurgeTrash.php
imp/lib/Mailbox.php
imp/lib/Message.php
imp/lib/Mime/Viewer/Partial.php
imp/lib/Prefs/Ui.php
imp/lib/Search.php
imp/lib/Search/Element.php [new file with mode: 0644]
imp/lib/Search/Element/Bulk.php [new file with mode: 0644]
imp/lib/Search/Element/Date.php [new file with mode: 0644]
imp/lib/Search/Element/Flag.php [new file with mode: 0644]
imp/lib/Search/Element/Header.php [new file with mode: 0644]
imp/lib/Search/Element/Or.php [new file with mode: 0644]
imp/lib/Search/Element/Recipient.php [new file with mode: 0644]
imp/lib/Search/Element/Size.php [new file with mode: 0644]
imp/lib/Search/Element/Text.php [new file with mode: 0644]
imp/lib/Search/Element/Within.php [new file with mode: 0644]
imp/lib/Search/Query.php [new file with mode: 0644]
imp/lib/Search/Vfolder.php [new file with mode: 0644]
imp/lib/Search/Vfolder/Vinbox.php [new file with mode: 0644]
imp/lib/Search/Vfolder/Vtrash.php [new file with mode: 0644]
imp/lib/Tree/Flist.php
imp/lib/Ui/Search.php [deleted file]
imp/lib/Views/ListMessages.php
imp/mailbox-mimp.php
imp/mailbox.php
imp/rss.php
imp/search-basic.php
imp/search.php
imp/templates/dimp/index.inc
imp/templates/imp/mailbox/header.html
imp/templates/imp/search/search-basic.html
imp/templates/imp/search/search.html
imp/templates/prefs/searches.html [new file with mode: 0644]
imp/templates/prefs/trash.html
imp/themes/dimp/screen.css
imp/themes/screen.css
imp/themes/silver/dimp/screen.css

index b626775..46e9d5e 100644 (file)
@@ -113,22 +113,10 @@ $prefGroups['server'] = array(
     'label' => _("Server and Folder Information"),
     'desc' => _("Change mail server and folder settings."),
     'members' => array(
-        'use_vinbox', 'subscribe', 'draftsselect', 'trashselect', 'spamselect'
+        'subscribe', 'draftsselect', 'trashselect', 'spamselect'
     )
 );
 
-// display Virtual INBOX?
-$_prefs['use_vinbox'] = array(
-    'value' => 1,
-    'type' => 'checkbox',
-    'desc' => _("Display Virtual Inbox?")
-);
-
-// virtual inbox identifier
-$_prefs['vinbox_id'] = array(
-    'value' => ''
-);
-
 // use IMAP subscribe?
 $_prefs['subscribe'] = array(
     'value' => 1,
@@ -189,10 +177,6 @@ $_prefs['spam_folder'] = array(
     // 'value' => Horde_String::convertCharset('Spam', null, 'UTF7-IMAP')
 );
 
-$_prefs['vfolder'] = array(
-    'value' => ''
-);
-
 
 
 // *** ACL Preferences ***
@@ -383,6 +367,38 @@ $_prefs['accounts'] = array(
 
 
 
+// *** Saved Searches Preferences ***
+
+$prefGroups['searches'] = array(
+    'column' => _("General Preferences"),
+    'label' => _("Saved Searches"),
+    'desc' => _("Manage your saved searches"),
+    'members' => array(
+        'searchesmanagement'
+    )
+);
+
+// UI for saved searches management.
+$_prefs['searchesmanagement'] = array(
+    'type' => 'special'
+);
+
+$_prefs['vfolder'] = array(
+    // 'value' => serialize(array(
+    //     // Virtual Inbox, enabled by default.
+    //     new IMP_Search_Vfolder_Vinbox(array(
+    //         'disabled' => false
+    //     )),
+    //     // Virtual Trash, disabled by default.
+    //     new IMP_Search_Vfolder_Vtrash(array(
+    //         'disabled' => true
+    //     ))
+    // ))
+    'value' => 'a:1:{i:0;C:25:"IMP_Search_Vfolder_Vinbox":175:{a:3:{s:1:"c";a:2:{i:0;C:23:"IMP_Search_Element_Flag":24:{[1,{"f":"\\seen","s":0}]}i:1;C:23:"IMP_Search_Element_Flag":27:{[1,{"f":"\\deleted","s":0}]}}s:1:"e";i:1;s:1:"v";i:1;}}}'
+);
+
+
+
 // *** Compose Preferences ***
 
 $prefGroups['compose'] = array(
@@ -921,16 +937,6 @@ $_prefs['use_trash'] = array(
     'desc' => _("When deleting messages, move them to your Trash folder instead of marking them as deleted?")
 );
 
-// use Virtual Trash folder
-$_prefs['use_vtrash'] = array(
-    'value' => 0
-);
-
-// virtual trash folder identifier
-$_prefs['vtrash_id'] = array(
-    'value' => ''
-);
-
 // display the 'Empty Trash' link in the menubar?
 $_prefs['empty_trash_menu'] = array(
     'value' => 0,
index aee8964..902851c 100644 (file)
@@ -68,6 +68,10 @@ your ``config/prefs.php`` file and your preferences backend::
    filter_on_sidebar
    forward_bodytext
    nav_expanded_sidebar
+   use_vinbox
+   use_vtrash
+   vinbox_id
+   vtrash_id
 
 
 Hooks
index f60d45e..412d585 100644 (file)
@@ -100,14 +100,6 @@ case 'delete_folder':
     }
     break;
 
-case 'delete_search_query':
-    if ($vars->queryid) {
-        $imp_search = $injector->getInstance('IMP_Search');
-        $notification->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $imp_search->getLabel($vars->queryid)), 'horde.success');
-        $imp_search->deleteSearchQuery($vars->queryid);
-    }
-    break;
-
 case 'download_folder':
 case 'download_folder_zip':
     if (!empty($folder_list)) {
index ba56fc3..c088abb 100644 (file)
@@ -157,13 +157,12 @@ var DimpBase = {
 
         /* If switching from options, we need to reload page to pick up any
          * prefs changes. */
-        if (this.folder === null &&
-            loc != 'options' &&
-            $('appoptions') &&
-            $('appoptions').hasClassName('on')) {
+        if (loc != 'prefs' &&
+            $('appprefs') &&
+            $('appprefs').hasClassName('on')) {
             $('dimpPage').hide();
             $('dimpLoading').show();
-            return DimpCore.redirect(DIMP.conf.URI_DIMP + '#' + loc, true);
+            return DimpCore.redirect(DIMP.conf.URI_DIMP + '#' + escape(loc), true);
         }
 
         if (loc.startsWith('compose:')) {
@@ -180,6 +179,10 @@ var DimpBase = {
 
         if (loc.startsWith('folder:')) {
             f = loc.substring(7);
+            if (f.empty()) {
+                f = this.folder;
+            }
+
             if (this.folder != f || !$('dimpmain_folder').visible()) {
                 this.highlightSidebar(this.getFolderId(f));
                 if (!$('dimpmain_folder').visible()) {
@@ -200,7 +203,6 @@ var DimpBase = {
         }
 
         f = this.folder;
-        this.folder = null;
         $('dimpmain_folder').hide();
         $('dimpmain_portal').update(DIMP.text.loading).show();
 
@@ -239,11 +241,12 @@ var DimpBase = {
             DimpCore.doAction('showPortal', {}, { callback: this._portalCallback.bind(this) });
             break;
 
-        case 'options':
-            this.highlightSidebar('appoptions');
+        case 'prefs':
+            // data: Extra parameters to add to prefs URL.
+            this.highlightSidebar('appprefs');
             this.setHash(loc);
             DimpCore.setTitle(DIMP.text.prefs);
-            this.iframeContent(loc, DIMP.conf.URI_PREFS_IMP);
+            this.iframeContent(loc, DimpCore.addURLParam(DIMP.conf.URI_PREFS_IMP, data));
             break;
         }
     },
@@ -871,10 +874,14 @@ var DimpBase = {
             this.go('search', tmp);
             break;
 
+        case 'ctx_vcontainer_edit':
+            this.go('prefs', { group: 'searches' });
+            break;
+
         case 'ctx_qsearchby_all':
         case 'ctx_qsearchby_body':
         case 'ctx_qsearchby_from':
-        case 'ctx_qsearchby_to':
+        case 'ctx_qsearchby_recip':
         case 'ctx_qsearchby_subject':
             DIMP.conf.qsearchfield = id.substring(14);
             this._updatePrefs('dimp_qsearch_field', DIMP.conf.qsearchfield);
@@ -2008,7 +2015,7 @@ var DimpBase = {
                 return;
 
             case 'appportal':
-            case 'appoptions':
+            case 'appprefs':
                 this.go(id.substring(3));
                 e.stop();
                 return;
@@ -2365,12 +2372,13 @@ var DimpBase = {
             switch (li.retrieve('ftype')) {
             case 'container':
             case 'scontainer':
+            case 'vcontainer':
                 e.stop();
                 break;
 
             case 'folder':
             case 'special':
-            case 'virtual':
+            case 'vfolder':
                 e.stop();
                 return this.go('folder:' + li.retrieve('mbox'));
             }
@@ -2498,7 +2506,7 @@ var DimpBase = {
         }
 
         if (ob.v) {
-            ftype = ob.co ? 'scontainer' : 'virtual';
+            ftype = ob.co ? 'vcontainer' : 'vfolder';
             title = label;
         } else if (ob.co) {
             if (ob.n) {
@@ -2612,20 +2620,23 @@ var DimpBase = {
         case 'container':
         case 'folder':
             new Drag(li, this._folderDragConfig);
-            DimpCore.addContextMenu({
-                id: fid,
-                type: ftype
-            });
             break;
 
         case 'scontainer':
-        case 'virtual':
-            DimpCore.addContextMenu({
-                id: fid,
-                type: (ob.v == 2) ? 'vfolder' : 'noactions'
-            });
+            ftype = 'noactions';
+            break;
+
+        case 'vfolder':
+            if (ob.v == 1) {
+                ftype = 'noactions';
+            }
             break;
         }
+
+        DimpCore.addContextMenu({
+            id: fid,
+            type: ftype
+        });
     },
 
     deleteFolder: function(folder)
index 50ce93b..d5fada4 100644 (file)
@@ -7,7 +7,7 @@
 
 var ImpSearch = {
     // The following variables are defined in search.php:
-    //   data, text
+    //   data, i_criteria, recent, selected, text
     criteria: {},
     saved_searches: {},
 
@@ -45,36 +45,51 @@ var ImpSearch = {
         this.resetCriteria();
 
         criteria.each(function(c) {
-            if (c.t == 'or') {
-                this.insertOr();
-                return;
-            }
+            var crit = c.criteria;
 
-            switch (this.data.types[c.t]) {
-            case 'header':
-            case 'body':
-            case 'text':
-                this.insertText(c.t, c.v, c.n);
+            switch (c.element) {
+            case 'IMP_Search_Element_Date':
+                this.insertDate(this.data.constants.index(crit.t), new Date(crit.d));
                 break;
 
-            case 'customhdr':
-                this.insertCustomHdr(c.v, c.n);
+            case 'IMP_Search_Element_Flag':
+                this.insertFlag(crit.f, !crit.s);
                 break;
 
-            case 'size':
-                this.insertSize(c.t, c.v);
+            case 'IMP_Search_Element_Header':
+                switch (crit.h) {
+                case 'from':
+                case 'to':
+                case 'cc':
+                case 'bcc':
+                case 'subject':
+                    this.insertText(crit.h.capitalize(), crit.t, crit.n);
+                    break;
+
+                default:
+                    this.insertCustomHdr({ h: crit.h.capitalize(), s: crit.t }, crit.n);
+                    break;
+                }
                 break;
 
-            case 'date':
-                this.insertDate(c.t, c.v);
+            case 'IMP_Search_Element_Or':
+                this.insertOr();
                 break;
 
-            case 'within':
-                this.insertWithin(c.t, c.v);
+            case 'IMP_Search_Element_Recipient':
+                this.insertText('recip', crit.t, crit.n);
                 break;
 
-            case 'flag':
-                this.insertFlag(c.v);
+            case 'IMP_Search_Element_Size':
+                this.insertSize(crit.s ? 'size_larger' : 'size_smaller', crit.l);
+                break;
+
+            case 'IMP_Search_Element_Text':
+                this.insertText(crit.b ? 'body' : 'text', crit.t, crit.n);
+                break;
+
+            case 'IMP_Search_Element_Within':
+                this.insertWithin(crit.o ? 'older' : 'younger', { l: this.data.constants.index(crit.t), v: crit.v });
                 break;
             }
         }, this);
@@ -92,12 +107,6 @@ var ImpSearch = {
         });
     },
 
-    updateSavedSearches: function(label, type)
-    {
-        $('search_label').setValue(label);
-        // TODO: type
-    },
-
     changeHandler: function(e)
     {
         var elt = e.element(), val = $F(elt);
@@ -119,7 +128,6 @@ var ImpSearch = {
 
             switch (this.data.types[val]) {
             case 'header':
-            case 'body':
             case 'text':
                 this.insertText(val);
                 break;
@@ -242,12 +250,15 @@ var ImpSearch = {
 
     insertDate: function(id, data)
     {
-        var d = (data ? new Date(data.y, data.m, data.d) : new Date()),
-            tmp = [
+        if (!data) {
+            data = new Date();
+        }
+
+        var tmp = [
                 new Element('EM').insert(this.getLabel(id)),
                 new Element('SPAN').insert(new Element('SPAN')).insert(new Element('A', { href: '#', className: 'calendarPopup', title: this.text.dateselection }).insert(new Element('SPAN', { className: 'searchuiImg searchuiCalendar' })))
             ];
-        this.replaceDate(this.insertCriteria(tmp), id, d);
+        this.replaceDate(this.insertCriteria(tmp), id, data);
     },
 
     replaceDate: function(id, type, d)
@@ -271,11 +282,12 @@ var ImpSearch = {
         tmp[1].activate();
     },
 
-    insertFlag: function(id)
+    insertFlag: function(id, not)
     {
         var tmp = [
             new Element('EM').insert(this.text.flag),
-            this.getLabel(id).slice(0, -2)
+            this.getLabel(id).slice(0, -2),
+            new Element('SPAN').insert(new Element('INPUT', { checked: Boolean(not), className: 'checkbox', type: 'checkbox' })).insert(this.text.not_match)
         ];
         this.criteria[this.insertCriteria(tmp)] = { t: id };
     },
@@ -301,7 +313,6 @@ var ImpSearch = {
 
                     switch (this.data.types[this.criteria[c].t]) {
                     case 'header':
-                    case 'body':
                     case 'text':
                         this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]'))));
                         this.criteria[c].v = $F($(c).down('INPUT[type=text]'));
@@ -332,7 +343,12 @@ var ImpSearch = {
                         break;
 
                     case 'flag':
-                        data.push({ t: 'flag', v: this.criteria[c].t });
+                        this.criteria[c].n = Number(Boolean($F($(c).down('INPUT[type=checkbox]'))));
+                        data.push({
+                            n: this.criteria[c].n,
+                            t: 'flag',
+                            v: this.criteria[c].t
+                        });
                         break;
                     }
                 }, this);
@@ -378,6 +394,15 @@ var ImpSearch = {
                 e.stop();
                 return;
 
+            case 'search_edit_query_cancel':
+                e.stop();
+                if (this.data.dimp) {
+                    window.parent.DimpBase.go('folder:');
+                } else {
+                    document.location.href = this.prefsurl;
+                }
+                return;
+
             default:
                 if (elt.hasClassName('arrowExpanded') ||
                     elt.hasClassName('arrowCollapsed')) {
@@ -413,10 +438,37 @@ var ImpSearch = {
     {
         var id = e.findElement('TR').identify();
         this.replaceDate(id, this.criteria[id].t, e.memo);
+    },
+
+    onDomLoad: function()
+    {
+        if (!this.data) {
+            this.onDomLoad.bind(this).defer();
+            return;
+        }
+
+        this.data.constants.date = $H(this.data.constants.date);
+        this.data.constants.within = $H(this.data.constants.within);
+
+        if (this.recent) {
+            this.updateRecentSearches(this.recent);
+            this.recent = null;
+        }
+
+        if (this.selected) {
+            this.updateSelectedFolders(this.selected);
+            this.selected = null;
+        }
+
+        if (this.i_criteria) {
+            this.updateSearchCriteria(this.i_criteria);
+            this.i_criteria = null;
+        }
     }
 
 };
 
 document.observe('change', ImpSearch.changeHandler.bindAsEventListener(ImpSearch));
 document.observe('click', ImpSearch.clickHandler.bindAsEventListener(ImpSearch));
+document.observe('dom:loaded', ImpSearch.onDomLoad.bindAsEventListener(ImpSearch));
 document.observe('Horde_Calendar:select', ImpSearch.calendarSelectHandler.bindAsEventListener(ImpSearch));
diff --git a/imp/js/searchesprefs.js b/imp/js/searchesprefs.js
new file mode 100644 (file)
index 0000000..9153b85
--- /dev/null
@@ -0,0 +1,45 @@
+/**
+ * Provides the javascript for managing saved searches.
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ */
+
+var ImpSearchesPrefs = {
+    // Variables set by other code: confirm_delete_vfolder, mailboxids
+
+    clickHandler: function(e)
+    {
+        if (e.isRightClick()) {
+            return;
+        }
+
+        var elt = e.element();
+
+        while (Object.isElement(elt)) {
+            if (elt.hasClassName('vfolderdelete')) {
+                if (window.confirm(this.confirm_delete_vfolder)) {
+                    this._sendData('delete', elt.up().previous('.enabled').down('INPUT').readAttribute('name'));
+                }
+                e.stop();
+                return;
+            } else if (elt.match('SPAN.vfolderenabled')) {
+                e.stop();
+                window.parent.DimpBase.go('folder:' + this.mailboxids[elt.up().next('.enabled').down('INPUT').readAttribute('name')]);
+                return;
+            }
+
+            elt = elt.up();
+        }
+    },
+
+    _sendData: function(a, d)
+    {
+        $('searches_action').setValue(a)
+        $('searches_data').setValue(d);
+        $('prefs').submit();
+    }
+
+};
+
+document.observe('click', ImpSearchesPrefs.clickHandler.bindAsEventListener(ImpSearchesPrefs));
index b7a3209..8b212af 100644 (file)
@@ -117,9 +117,9 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application
 
         $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
 
-        if ($imp_search->isEditableVFolder($this->_vars->mbox)) {
-            $GLOBALS['notification']->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $imp_search->getLabel($this->_vars->mbox)), 'horde.success');
-            $imp_search->deleteSearchQuery($this->_vars->mbox);
+        if ($imp_search->isVFolder($this->_vars->mbox, true)) {
+            $GLOBALS['notification']->push(sprintf(_("Deleted Virtual Folder \"%s\"."), $imp_search[$this->_vars->mbox]->label), 'horde.success');
+            unset($imp_search[$this->_vars->mbox]);
             $result = true;
         } else {
             $result = $GLOBALS['injector']->getInstance('IMP_Folder')->delete(array($this->_vars->mbox));
@@ -2122,7 +2122,7 @@ class IMP_Ajax_Application extends Horde_Core_Ajax_Application
             $ob->po = 1;
         }
         if ($elt->vfolder) {
-            $ob->v = $GLOBALS['injector']->getInstance('IMP_Search')->isEditableVFolder($elt->value) ? 2 : 1;
+            $ob->v = $GLOBALS['injector']->getInstance('IMP_Search')->isVFolder($elt->value, true) ? 2 : 1;
         }
         if (!$elt->sub) {
             $ob->un = 1;
index 11ec79b..908cfa0 100644 (file)
@@ -162,7 +162,7 @@ class IMP_Api extends Horde_Registry_Api
      */
     public function searchMailbox($mailbox, $query)
     {
-        $results = $GLOBALS['injector']->getInstance('IMP_Search')->runSearchQuery($query, $mailbox)->indices();
+        $results = $GLOBALS['injector']->getInstance('IMP_Search')->runQuery($query, $mailbox)->indices();
         return isset($results[$mailbox])
             ? $results[$mailbox]
             : array();
index 004bc73..9dad2a7 100644 (file)
@@ -227,25 +227,19 @@ class IMP_Application extends Horde_Registry_Application
 
         if ($_SESSION['imp']['protocol'] != 'pop') {
             if ($prefs->getValue('use_trash') &&
+                ($trash_folder = $prefs->getValue('trash_folder')) &&
                 $prefs->getValue('empty_trash_menu')) {
-                $mailbox = null;
-                if ($prefs->getValue('use_vtrash')) {
-                    $mailbox = $injector->getInstance('IMP_Search')->createSearchID($prefs->getValue('vtrash_id'));
-                } else {
-                    $trash_folder = IMP::folderPref($prefs->getValue('trash_folder'), true);
-                    if (!is_null($trash_folder)) {
-                        $mailbox = $trash_folder;
-                    }
-                }
+                $imp_search = $injector->getInstance('IMP_Search');
+                $trash_folder = IMP::folderPref($trash_folder, true);
 
-                if (!empty($mailbox) &&
-                    !$injector->getInstance('IMP_Imap')->getOb()->isReadOnly($mailbox)) {
+                if ($injector->getInstance('IMP_Search')->isVTrash($trash_folder) ||
+                    !$injector->getInstance('IMP_Imap')->getOb()->isReadOnly($trash_folder)) {
                     $menu->addArray(array(
                         'class' => '__noselection',
                         'icon' => 'empty_trash.png',
                         'onclick' => 'return window.confirm(' . Horde_Serialize::serialize(_("Are you sure you wish to empty your trash folder?"), Horde_Serialize::JSON, $registry->getCharset()) . ')',
                         'text' => _("Empty _Trash"),
-                        'url' => IMP::generateIMPUrl($menu_mailbox_url, $mailbox)->add(array('actionID' => 'empty_mailbox', 'mailbox_token' => Horde::getRequestToken('imp.mailbox')))
+                        'url' => IMP::generateIMPUrl($menu_mailbox_url, $trash_folder)->add(array('actionID' => 'empty_mailbox', 'mailbox_token' => Horde::getRequestToken('imp.mailbox')))
                     ));
                 }
             }
@@ -645,7 +639,6 @@ class IMP_Application extends Horde_Registry_Application
     public function mailboxesChanged()
     {
         $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->init();
-        $GLOBALS['injector']->getInstance('IMP_Search')->init(true);
     }
 
 }
index 25b0468..ffafa0b 100644 (file)
@@ -348,16 +348,9 @@ class IMP_Auth
                 : $GLOBALS['prefs']->getValue('initial_page');
 
             $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
-
-            if (!$GLOBALS['prefs']->getValue('use_vinbox') &&
-                $imp_search->isVINBOXFolder($init_url)) {
-                $init_url = 'folders.php';
-            } elseif (($imp_search->createSearchId($init_url) == $init_url) &&
-                      !$imp_search->isVFolder($init_url)) {
+            if ($imp_search->isSearchMbox($init_url) &&
+                (!$imp_search[$init_url]->enabled)) {
                 $init_url = 'INBOX';
-                if (!$GLOBALS['prefs']->isLocked('initial_page')) {
-                    $GLOBALS['prefs']->setValue('initial_page', $init_url);
-                }
             }
 
             switch ($init_url) {
index 1246f3f..f70521f 100644 (file)
@@ -25,7 +25,7 @@ class IMP_Block_Newmail extends Horde_Block
 
         $query = new Horde_Imap_Client_Search_Query();
         $query->flag('\\seen', false);
-        $ids = $GLOBALS['injector']->getInstance('IMP_Search')->runSearchQuery($query, 'INBOX', Horde_Imap_Client::SORT_SEQUENCE, 1);
+        $ids = $GLOBALS['injector']->getInstance('IMP_Search')->runQuery($query, 'INBOX', Horde_Imap_Client::SORT_SEQUENCE, 1);
         $indices = reset($ids);
 
         $html = '<table cellspacing="0" width="100%">';
index 6d34002..01a2cd5 100644 (file)
@@ -162,4 +162,17 @@ class IMP_Dimp
         return $ret;
     }
 
+    /**
+     * Return to main dimp mailbox page from within IFRAME.
+     *
+     * @var string $mailbox  The mailbox to load.
+     */
+    static public function returnToDimp($mailbox = '')
+    {
+        print '<html><head>' .
+            Horde::wrapInlineScript(array('window.parent.DimpBase.go(' . Horde_Serialize::serialize('folder:' . strval($mailbox), Horde_Serialize::JSON, $GLOBALS['registry']->getCharset()) . ')')) .
+            '</head></html>';
+        exit;
+    }
+
 }
index ff111c1..caf580e 100644 (file)
@@ -39,7 +39,7 @@ class IMP_Filter
 
         $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
         $mbox_list = $imp_search->isSearchMbox($mbox)
-            ? $imp_search->getSearchFolders($mbox)
+            ? $imp_search->getSearchMailboxes($mbox)
             : array($mbox);
 
         foreach ($mbox_list as $val) {
index 05d60d2..944cd4f 100644 (file)
@@ -78,9 +78,6 @@ class IMP_Folder
      */
     protected function _onDelete($deleted)
     {
-        /* Recreate Virtual Folders. */
-        $GLOBALS['injector']->getInstance('IMP_Search')->init(true);
-
         /* Clear the folder from the sort prefs. */
         foreach ($deleted as $val) {
             IMP::setSort(null, null, $val, true);
@@ -163,9 +160,6 @@ class IMP_Folder
         /* Update the mailbox tree. */
         $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->insert($folder);
 
-        /* Recreate Virtual Folders. */
-        $GLOBALS['injector']->getInstance('IMP_Search')->init(true);
-
         return true;
     }
 
index c54cfbb..34b566b 100644 (file)
@@ -106,9 +106,11 @@ class IMP
      */
     static public function getLabel($mbox)
     {
-        if (!($label = $GLOBALS['injector']->getInstance('IMP_Search')->getLabel($mbox))) {
-            $label = self::displayFolder($mbox);
-        }
+        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
+
+        $label = ($ob = $imp_search[$mbox])
+            ? $ob->label
+            : self::displayFolder($mbox);
 
         try {
             return Horde::callHook('mbox_label', array($mbox, $label), 'imp');
@@ -642,16 +644,22 @@ class IMP
      */
     static public function hideDeletedMsgs($mbox, $force = false)
     {
+        global $injector, $prefs;
+
         $delhide = &self::$_delhide;
 
         if (is_null($delhide) || $force) {
-            if ($GLOBALS['prefs']->getValue('use_vtrash')) {
-                $delhide = !$GLOBALS['injector']->getInstance('IMP_Search')->isVTrashFolder($mbox);
+            $imp_search = $injector->getInstance('IMP_Search');
+            $use_trash = $prefs->getValue('use_trash');
+
+            if ($use_trash &&
+                $imp_search->isVTrash($prefs->getValue('trash_folder'))) {
+                $delhide = !$imp_search->isVTrash($mbox);
             } else {
                 $sortpref = self::getSort();
-                $delhide = ($GLOBALS['prefs']->getValue('delhide') &&
-                            !$GLOBALS['prefs']->getValue('use_trash') &&
-                            ($GLOBALS['injector']->getInstance('IMP_Search')->isSearchMbox($mbox) ||
+                $delhide = ($prefs->getValue('delhide') &&
+                            !$use_trash &&
+                            ($injector->getInstance('IMP_Search')->isSearchMbox($mbox) ||
                              ($sortpref['by'] != Horde_Imap_Client::SORT_THREAD)));
             }
         }
@@ -802,11 +810,8 @@ class IMP
          * Horde_Imap_Client_Socket has a built-in ORDEREDSUBJECT
          * implementation. We will always prefer REFERENCES, but will fallback
          * to ORDEREDSUBJECT if the server doesn't support THREAD sorting. */
-        return ($_SESSION['imp']['protocol'] == 'imap') &&
-               !$GLOBALS['injector']->getInstance('IMP_Search')->isSearchMbox($mbox) &&
-               (!$GLOBALS['prefs']->getValue('use_trash') ||
-                !$GLOBALS['prefs']->getValue('use_vtrash') ||
-                $GLOBALS['injector']->getInstance('IMP_Search')->isVTrashFolder($mbox));
+        return (($_SESSION['imp']['protocol'] == 'imap') &&
+                !$GLOBALS['injector']->getInstance('IMP_Search')->isSearchMbox($mbox));
     }
 
     /**
@@ -967,10 +972,11 @@ class IMP
             }
             $t->set('folders', $folders);
 
+            $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
             if (($_SESSION['imp']['protocol'] != 'pop') &&
-                $GLOBALS['prefs']->getValue('use_vinbox') &&
-                ($vinbox_id = $GLOBALS['prefs']->getValue('vinbox_id'))) {
-                $t->set('vinbox', Horde::link(self::generateIMPUrl('mailbox.php', $GLOBALS['injector']->getInstance('IMP_Search')->createSearchId($vinbox_id))));
+                ($vinbox = $imp_search['vinbox']) &&
+                $vinbox->enabled) {
+                $t->set('vinbox', self::generateIMPUrl('mailbox.php', strval($vinbox))->link());
             }
         } else {
             $t->set('msg', ($var == 1) ? _("You have 1 new message.") : sprintf(_("You have %s new messages."), $var));
index e7a81e9..219d0ac 100644 (file)
@@ -438,6 +438,31 @@ class IMP_Imap_Flags
     }
 
     /**
+     * Given a flag/set combo, returns the text label.
+     *
+     * @param string $name  Flag name.
+     * @param boolean $set  Search for set flag?
+     *
+     * @return string  The text label.
+     */
+    public function getLabel($name, $set)
+    {
+        $flist = $this->getList();
+
+        if (!isset($flist[$name])) {
+            return '';
+        }
+
+        if (!empty($flist[$name]['n'])) {
+            $set = !$set;
+        }
+
+        return $set
+            ? $flist[$name]['l']
+            : sprintf(_("Not %s"), $flist[$name]['l']);
+    }
+
+    /**
      * Determines the colors for an entry.
      *
      * @param string $key  The flag key.
index f0a3acd..9ad3fb7 100644 (file)
@@ -235,10 +235,11 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable
         $this->_insert($this->_getList($this->_showunsub), $this->_showunsub ? null : true);
 
         /* Add virtual folders to the tree. */
-        $this->insertVFolders($GLOBALS['injector']->getInstance('IMP_Search')->listQueries(IMP_Search::LIST_VFOLDER));
+        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
+        $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER);
+        $this->updateVFolders(iterator_to_array($imp_search));
     }
 
-
     /**
      * Returns the list of mailboxes on the server.
      *
@@ -1073,7 +1074,6 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable
     protected function _updatePollList()
     {
         $GLOBALS['prefs']->setValue('nav_poll', serialize($this->_cache['poll']));
-        $GLOBALS['injector']->getInstance('IMP_Search')->createVInbox();
         $this->changed = true;
     }
 
@@ -1306,54 +1306,44 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable
     }
 
     /**
-     * Inserts virtual folders into the tree.
+     * Updates the virtual folder list in the tree.
      *
-     * @param array $id_list  An array with the folder IDs to add as the key
-     *                        and the labels as the value.
+     * @param array $vfolders  A list of IMP_Search_VFolder objects.
      */
-    public function insertVFolders($id_list)
+    public function updateVFolders($vfolders)
     {
-        if (empty($id_list) ||
-            empty($GLOBALS['conf']['user']['allow_folders'])) {
-            return;
-        }
-
-        $adds = $id = array();
-
-        foreach ($id_list as $key => $val) {
-            $id[$GLOBALS['injector']->getInstance('IMP_Search')->createSearchId($key)] = $val;
-        }
-
-        foreach (array_keys($id) as $key) {
-            $id_key = self::VFOLDER_KEY . $this->_delimiter . $key;
-            if (!isset($this->_tree[$id_key])) {
-                $adds[] = $id_key;
+        /* Clear old entries. */
+        if (isset($this->_parent[self::VFOLDER_KEY])) {
+            foreach ($this->_parent[self::VFOLDER_KEY] as $key) {
+                unset($this->_tree[$key]);
             }
+            unset($this->_parent[self::VFOLDER_KEY]);
+            $this->changed = true;
         }
 
-        if (empty($adds)) {
+        if (empty($GLOBALS['conf']['user']['allow_folders'])) {
             return;
         }
 
-        $this->insert($adds);
-
-        foreach ($id as $key => $val) {
-            $this->_tree[$key]['l'] = $val;
+        foreach ($vfolders as $val) {
+            if ($val->enabled) {
+                $key = strval($val);
+                $this->insert(self::VFOLDER_KEY . $this->_delimiter . $key);
+                $this->_tree[$key]['l'] = $val->label;
+            }
         }
 
         /* Sort the Virtual Folder list in the object, if necessary. */
-        if (!$this->_needSort($this->_tree[self::VFOLDER_KEY])) {
-            return;
-        }
-
-        $vsort = array();
-        foreach ($this->_parent[self::VFOLDER_KEY] as $val) {
-            $vsort[$val] = $this->_tree[$val]['l'];
+        if (isset($this->_tree[self::VFOLDER_KEY]) &&
+            $this->_needSort($this->_tree[self::VFOLDER_KEY])) {
+            $vsort = array();
+            foreach ($this->_parent[self::VFOLDER_KEY] as $val) {
+                $vsort[$val] = $this->_tree[$val]['l'];
+            }
+            natcasesort($vsort);
+            $this->_parent[self::VFOLDER_KEY] = array_keys($vsort);
+            $this->_setNeedSort($this->_tree[self::VFOLDER_KEY], false);
         }
-        natcasesort($vsort);
-        $this->_parent[self::VFOLDER_KEY] = array_keys($vsort);
-        $this->_setNeedSort($this->_tree[self::VFOLDER_KEY], false);
-        $this->changed = true;
     }
 
     /**
@@ -1558,12 +1548,9 @@ class IMP_Imap_Tree implements ArrayAccess, Iterator, Serializable
             if ($val->vfolder) {
                 $checkbox .= ' disabled="disabled"';
 
-                if (!empty($opts['editvfolder']) && $val->editvfolder) {
-                    $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
+                if (!empty($opts['editvfolder']) && $val->container) {
                     $after = '&nbsp[' .
-                        $imp_search->deleteUrl($val->value)->link(array('title' => _("Delete Virtual Folder"))) . _("Delete") . '</a>'.
-                        ']&nbsp;|&nbsp|[' .
-                        $imp_search->editUrl($val->value)->link(array('title' => _("Edit Virtual Folder"))) . _("Edit") . '</a>'.
+                        Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->link(array('title' => _("Edit Virtual Folder"))) . _("Edit") . '</a>'.
                         ']';
                 }
             }
index 8c0da75..f4b9284 100644 (file)
@@ -116,8 +116,7 @@ class IMP_Imap_Tree_Element
                 : IMP::displayFolder($this->value);
 
         case 'editvfolder':
-            return ($this->vfolder &&
-                $GLOBALS['injector']->getInstance('IMP_Search')->isEditableVFolder($this->value));
+            return $GLOBALS['injector']->getInstance('IMP_Search')->isVFolder($this->value, true);
 
         case 'is_open':
             return $this->_treeob->isOpen($this->_mbox);
@@ -177,10 +176,8 @@ class IMP_Imap_Tree_Element
             case 'INBOX':
             case $this->_eltCache['draft']:
             case $this->_eltCache['spam']:
-                return true;
-
             case $this->_eltCache['trash']:
-                return (!$GLOBALS['prefs']->getValue('use_vtrash'));
+                return true;
 
             default:
                 return in_array($this->value, $this->_eltCache['sent']);
@@ -189,12 +186,7 @@ class IMP_Imap_Tree_Element
             return false;
 
         case 'specialvfolder':
-            if (!$this->vfolder) {
-                return false;
-            }
-            $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
-            return ($imp_search->isVTrashFolder($this->value) ||
-                    $imp_search->isVINBOXFolder($this->value));
+            return !$GLOBALS['injector']->getInstance('IMP_Search')->isVFolder($this->value, true);
 
         case 'sub':
             return $this->_treeob->isSubscribed($this->_mbox);
@@ -243,16 +235,9 @@ class IMP_Imap_Tree_Element
                 break;
 
             case $this->_eltCache['trash']:
-                if ($GLOBALS['prefs']->getValue('use_vtrash')) {
-                    $info->alt = _("Mailbox");
-                    $info->icon = $this->is_open
-                        ? 'folders/open.png'
-                        : 'folders/folder.png';
-                } else {
-                    $info->alt = _("Trash folder");
-                    $info->class = 'trashImg';
-                    $info->icon = 'folders/trash.png';
-                }
+                $info->alt = _("Trash folder");
+                $info->class = 'trashImg';
+                $info->icon = 'folders/trash.png';
                 break;
 
             case $this->_eltCache['draft']:
@@ -288,12 +273,12 @@ class IMP_Imap_Tree_Element
             /* Virtual folders. */
             if ($this->vfolder) {
                 $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
-                if ($imp_search->isVTrashFolder($this->_mbox['v'])) {
-                    $info->alt = _("Virtual Trash Folder");
+                if ($imp_search->isVTrash($this->_mbox['v'])) {
+                    $info->alt = $imp_search[$this->_mbox['v']]->label;
                     $info->class = 'trashImg';
                     $info->icon = 'folders/trash.png';
-                } elseif ($imp_search->isVINBOXFolder($this->_mbox['v'])) {
-                    $info->alt = _("Virtual INBOX Folder");
+                } elseif ($imp_search->isVinbox($this->_mbox['v'])) {
+                    $info->alt = $imp_search[$this->_mbox['v']]->label;
                     $info->class = 'inboxImg';
                     $info->icon = 'folders/inbox.png';
                 }
index 5168671..3ddba8e 100644 (file)
@@ -46,8 +46,7 @@ class IMP_LoginTasks_SystemTask_GarbageCollection extends Horde_LoginTasks_Syste
         $sortpref = @unserialize($GLOBALS['prefs']->getValue('sortpref'));
 
         foreach (array_keys($sortpref) as $key) {
-            if ($imp_search->isSearchMbox($key) &&
-                !$imp_search->isEditableVFolder($key)) {
+            if (!$imp_search[$key]) {
                 unset($sortpref[$key]);
                 $update = true;
             }
index 19a227f..315b2be 100644 (file)
@@ -194,11 +194,12 @@ class IMP_LoginTasks_SystemTask_UpgradeFromImp4 extends Horde_LoginTasks_SystemT
      */
     protected function _upgradeVirtualFolders()
     {
-        if ($GLOBALS['prefs']->isDefault('vfolder')) {
-            return;
-        }
+        global $prefs;
 
-        $vfolders = $GLOBALS['prefs']->getValue('vfolder');
+        $use_vinbox = $prefs->getValue('use_vinbox');
+        $use_vtrash = $prefs->getValue('use_vtrash');
+
+        $vfolders = $prefs->getValue('vfolder');
         if (!empty($vfolders)) {
             $vfolders = @unserialize($vfolders);
         }
@@ -207,15 +208,31 @@ class IMP_LoginTasks_SystemTask_UpgradeFromImp4 extends Horde_LoginTasks_SystemT
             return;
         }
 
-        $imp_ui_search = new IMP_Ui_Search();
-
-        foreach ($vfolders as $id => $vfolder) {
-            /* If this is already a stdClass object, we have already
-             * upgraded. */
-            if (is_object($vfolder)) {
-                return;
+        if ($prefs->isDefault('vfolder') || is_object(reset($vfolders))) {
+            foreach ($vfolders as $val) {
+                if (!is_null($use_vinbox) &&
+                    ($val instanceof IMP_Search_Vfolder_Vinbox)) {
+                    $val->enabled = (bool)$use_vinbox;
+                } elseif (!is_null($use_vtrash) &&
+                          ($val instanceof IMP_Search_Vfolder_Vtrash)) {
+                    $val->enabled = (bool)$use_vtrash;
+                    $prefs->setValue('trash_folder', strval($val));
+                }
             }
+            $prefs->setValue('vfolder', serialize($vfolders));
+            return;
+        }
+
+        $new_vfolders = array();
+        if ($use_vinbox) {
+            $new_vfolders[] = new IMP_Search_Vfolder_Vinbox();
+        }
+        if ($use_vtrash) {
+            $vtrash = $new_vfolders[] = new IMP_Search_Vfolder_Vtrash();
+            $prefs->setValue('trash_folder', strval($vtrash));
+        }
 
+        foreach ($vfolders as $id => $vfolder) {
             $ui = $vfolder['uiinfo'];
 
             $or_match = ($ui['match'] == 'or');
@@ -237,10 +254,12 @@ class IMP_LoginTasks_SystemTask_UpgradeFromImp4 extends Horde_LoginTasks_SystemT
                 }
             }
 
-            $rules = array();
-
             foreach ($ui['field'] as $key => $val) {
-                $tmp = new stdClass;
+                $ob = new IMP_Search_Vfolder(array(
+                    'enabled' => true,
+                    'label' => $ui['vfolder_label'],
+                    'mboxes' => $ui['folders']
+                ));
 
                 switch ($val) {
                 case 'from':
@@ -248,27 +267,44 @@ class IMP_LoginTasks_SystemTask_UpgradeFromImp4 extends Horde_LoginTasks_SystemT
                 case 'cc':
                 case 'bcc':
                 case 'subject':
+                    $ob->add(new IMP_Search_Element_Header(
+                        $ui['text'][$key],
+                        $val,
+                        !empty($ui['text_not'][$key])
+                    ));
+                    break;
+
                 case 'body':
                 case 'text':
-                    $tmp->t = $val;
-                    $tmp->v = $ui['text'][$key];
-                    $tmp->n = !empty($ui['text_not'][$key]);
+                    $ob->add(new IMP_Search_Element_Text(
+                        $ui['text'][$key],
+                        ($val == 'body'),
+                        !empty($ui['text_not'][$key])
+                    ));
                     break;
 
                 case 'date_on':
                 case 'date_until':
                 case 'date_since':
-                    $tmp->t = $val;
-                    $tmp->v = new stdClass;
-                    $tmp->v->y = $ui['date'][$key]['year'];
-                    $tmp->v->m = $ui['date'][$key]['month'] - 1;
-                    $tmp->v->d = $ui['date'][$key]['day'];
+                    if ($val == 'date_on') {
+                        $type = IMP_Search_Element_Date::DATE_ON;
+                    } elseif ($val == 'date_until') {
+                        $type = IMP_Search_Element_Date::DATE_BEFORE;
+                    } else {
+                        $type = IMP_Search_Element_Date::DATE_SINCE;
+                    }
+                    $ob->add(new IMP_Search_Element_Date(
+                        new DateTime($ui['date'][$key]['year'] . '-' . $ui['date'][$key]['month'] . '-' . $ui['date'][$key]['day']),
+                        $type
+                    ));
                     break;
 
                 case 'size_smaller':
                 case 'size_larger':
-                    $tmp->t = $val;
-                    $tmp->v = $ui['text'][$key];
+                    $ob->add(new IMP_Search_Element_Size(
+                        $ui['text'][$key],
+                        $val == 'size_larger'
+                    ));
                     break;
 
                 case 'seen':
@@ -279,26 +315,29 @@ class IMP_LoginTasks_SystemTask_UpgradeFromImp4 extends Horde_LoginTasks_SystemT
                 case 'unflagged':
                 case 'deleted':
                 case 'undeleted':
-                    $tmp->t = 'flag';
-                    $tmp->v = (strpos($val, 'un') === false)
-                        ? '\\' . $val
-                        : '0\\\\' . substr($val, 2);
+                    if (strpos($val, 'un') === false) {
+                        $ob->add(new IMP_Search_Element_Flag(
+                            $val,
+                            true
+                        ));
+                    } else {
+                        $ob->add(new IMP_Search_Element_Flag(
+                            substr($val, 2),
+                            false
+                        ));
+                    }
                     break;
                 }
 
-                $rules[] = $tmp;
-
                 if ($or_match) {
-                    $tmp = new stdClass;
-                    $tmp->t = 'or';
-                    $rules[] = $tmp;
+                    $ob->add(new IMP_Search_Element_Or());
                 }
             }
 
-            /* This will overwrite the existing entry. */
-            $query = $imp_ui_search->createQuery($rules);
-            $GLOBALS['injector']->getInstance('IMP_Search')->addVFolder($query, $ui['folders'], $rules, $ui['vfolder_label'], $id);
+            $new_vfolders[] = $ob;
         }
+
+        $GLOBALS['injector']->getInstance('IMP_Search')->setVFolders($new_vfolders);
     }
 
 }
index 2fe6d6b..6242922 100644 (file)
@@ -57,7 +57,7 @@ class IMP_LoginTasks_Task_PurgeSentmail extends Horde_LoginTasks_Task
              * than 'purge_sentmail_keep' days. */
             $query = new Horde_Imap_Client_Search_Query();
             $query->dateSearch($del_time, Horde_Imap_Client_Search_Query::DATE_BEFORE);
-            $msg_ids = $GLOBALS['injector']->getInstance('IMP_Search')->runSearchQuery($query, $mbox);
+            $msg_ids = $GLOBALS['injector']->getInstance('IMP_Search')->runQuery($query, $mbox);
 
             /* Go through the message list and delete the messages. */
             if ($imp_message->delete($msg_ids, array('nuke' => true))) {
index f9fd554..dbb3713 100644 (file)
@@ -55,7 +55,7 @@ class IMP_LoginTasks_Task_PurgeSpam extends Horde_LoginTasks_Task
         /* Get the list of messages older than 'purge_spam_keep' days. */
         $query = new Horde_Imap_Client_Search_Query();
         $query->dateSearch($del_time, Horde_Imap_Client_Search_Query::DATE_BEFORE);
-        $msg_ids = $GLOBALS['injector']->getInstance('IMP_Search')->runSearchQuery($query, $spam_folder);
+        $msg_ids = $GLOBALS['injector']->getInstance('IMP_Search')->runQuery($query, $spam_folder);
 
         /* Go through the message list and delete the messages. */
         if ($GLOBALS['injector']->getInstance('IMP_Message')->delete($msg_ids, array('nuke' => true))) {
index d72ac16..ee9e51a 100644 (file)
@@ -35,36 +35,41 @@ class IMP_LoginTasks_Task_PurgeTrash extends Horde_LoginTasks_Task
      */
     public function execute()
     {
+        global $injector, $notification, $prefs;
+
         /* If we aren't using a Trash folder or if there is no Trash
            folder set, just return. */
-        $trash_folder = IMP::folderPref($GLOBALS['prefs']->getValue('trash_folder'), true);
-        if (!$GLOBALS['prefs']->getValue('use_trash') || !$trash_folder) {
+        if (!$prefs->getValue('use_trash') ||
+            !($trash_folder = $prefs->getValue('trash_folder'))) {
             return false;
         }
+        $trash_folder = IMP::folderPref($trash_folder, true);
 
         /* Make sure the Trash folder exists. */
-        if (!$GLOBALS['injector']->getInstance('IMP_Folder')->exists($trash_folder)) {
+        $imp_search = $injector->getInstance('IMP_Search');
+        if ($imp_search->isVTrash($trash_folder) ||
+            !$injector->getInstance('IMP_Folder')->exists($trash_folder)) {
             return false;
         }
 
         /* Get the current UNIX timestamp minus the number of days
            specified in 'purge_trash_keep'.  If a message has a
            timestamp prior to this value, it will be deleted. */
-        $del_time = new Horde_Date(time() - ($GLOBALS['prefs']->getValue('purge_trash_keep') * 86400));
+        $del_time = new Horde_Date(time() - ($prefs->getValue('purge_trash_keep') * 86400));
 
         /* Get the list of messages older than 'purge_trash_keep' days. */
         $query = new Horde_Imap_Client_Search_Query();
         $query->dateSearch($del_time, Horde_Imap_Client_Search_Query::DATE_BEFORE);
-        $msg_ids = $GLOBALS['injector']->getInstance('IMP_Search')->runSearchQuery($query, $trash_folder);
+        $msg_ids = $imp_search->runQuery($query, $trash_folder);
 
         /* Go through the message list and delete the messages. */
-        if ($GLOBALS['injector']->getInstance('IMP_Message')->delete($msg_ids, array('nuke' => true))) {
-            $msgcount = count($msg_ids);
-            $GLOBALS['notification']->push(sprintf(ngettext("Purging %d message from Trash folder.", "Purging %d messages from Trash folder.", $msgcount), $msgcount), 'horde.message');
-            return true;
+        if (!$injector->getInstance('IMP_Message')->delete($msg_ids, array('nuke' => true))) {
+            return false;
         }
 
-        return false;
+        $msgcount = count($msg_ids);
+        $notification->push(sprintf(ngettext("Purging %d message from Trash folder.", "Purging %d messages from Trash folder.", $msgcount), $msgcount), 'horde.message');
+        return true;
     }
 
     /**
index 0e49d54..7fd96ec 100644 (file)
@@ -372,7 +372,7 @@ class IMP_Mailbox implements Countable
             if ($count &&
                 $this->_searchmbox &&
                 ($type == 'unseen') &&
-                $GLOBALS['injector']->getInstance('IMP_Search')->isVINBOXFolder($this->_mailbox)) {
+                $GLOBALS['injector']->getInstance('IMP_Search')->isVinbox($this->_mailbox)) {
                 return count($this);
             }
 
index 5fd262e..b2f3803 100644 (file)
@@ -186,17 +186,22 @@ class IMP_Message
 
         $trash = IMP::folderPref($prefs->getValue('trash_folder'), true);
         $use_trash = $prefs->getValue('use_trash');
-        $use_vtrash = $prefs->getValue('use_vtrash');
-        if ($use_trash && !$use_vtrash && empty($trash)) {
+        if ($use_trash && empty($trash)) {
             $notification->push(_("Cannot move messages to Trash - no Trash mailbox set in preferences."), 'horde.error');
             return false;
         }
 
-        $return_value = 0;
+        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
         $maillog_update = (empty($options['keeplog']) && !empty($conf['maillog']['use_maillog']));
+        $return_value = 0;
 
         /* Check for Trash folder. */
-        $use_trash_folder = !$this->_usepop && empty($options['nuke']) && !$use_vtrash && $use_trash;
+        $use_trash_folder = $use_vtrash = false;
+        if (!$this->_usepop && empty($options['nuke']) && $use_trash) {
+            $use_vtrash = $imp_search->isVTrash($trash);
+            $use_trash_folder = !$use_vtrash;
+        }
+
         if ($use_trash_folder) {
             $imp_folder = $GLOBALS['injector']->getInstance('IMP_Folder');
 
@@ -262,20 +267,18 @@ class IMP_Message
                 if ($this->_usepop ||
                     !empty($options['nuke']) ||
                     ($use_trash && ($mbox == $trash)) ||
-                    ($use_vtrash && ($GLOBALS['injector']->getInstance('IMP_Search')->isVTrashFolder($mbox)))) {
+                    ($imp_search->isVTrash($mbox))) {
                     /* Purge messages immediately. */
                     $expunge_now = true;
-                } else {
+                } elseif ($use_vtrash) {
                     /* If we are using virtual trash, we must mark the message
-                     * as seen or else it will appear as an 'unseen' message for
-                     * purposes of new message counts. */
-                    if ($use_vtrash) {
-                        $del_flags[] = '\\seen';
-                    }
+                     * as seen or else it will appear as an 'unseen' message
+                     * for purposes of new message counts. */
+                    $del_flags[] = '\\seen';
                 }
 
                 try {
-                    $imp_imap->store($mbox, array('add' => array('\\deleted'), 'ids' => $msgIndices));
+                    $imp_imap->store($mbox, array('add' => $del_flags, 'ids' => $msgIndices));
                     if ($expunge_now) {
                         $this->expungeMailbox(
                             $imp_indices->indices(),
@@ -564,7 +567,7 @@ class IMP_Message
 
             /* If in Virtual Inbox, we need to reset flag to unseen so that it
              * appears again in the mailbox list. */
-            if ($GLOBALS['injector']->getInstance('IMP_Search')->isVINBOXFolder($mbox) &&
+            if ($GLOBALS['injector']->getInstance('IMP_Search')->isVinbox($mbox) &&
                 ($pos = array_search('\\seen', $res['flags']))) {
                 unset($res['flags'][$pos]);
             }
@@ -723,7 +726,7 @@ class IMP_Message
         foreach (array_keys($mbox_list) as $key) {
             if (!$imp_imap->isReadOnly($key)) {
                 if ($imp_search->isSearchMbox($key)) {
-                    foreach ($imp_search->getSearchFolders($key) as $skey) {
+                    foreach ($imp_search->getSearchMailboxes($key) as $skey) {
                         $process_list[$skey] = $mbox_list[$key];
                     }
                 } else {
@@ -781,8 +784,8 @@ class IMP_Message
                 continue;
             }
 
-            if ($imp_search->isVTrashFolder($mbox)) {
-                $this->expungeMailbox(array_flip($imp_search->getSearchFolders($mbox)));
+            if ($imp_search->isVTrash($mbox)) {
+                $this->expungeMailbox(array_flip($imp_search->getSearchMailboxes($mbox)));
                 $notification->push(_("Emptied all messages from Virtual Trash Folder."), 'horde.success');
                 continue;
             }
index 883c59c..1fb0106 100644 (file)
@@ -87,7 +87,7 @@ class IMP_Mime_Viewer_Partial extends Horde_Mime_Viewer_Base
         /* Perform the search to find the other parts of the message. */
         $query = new Horde_Imap_Client_Search_Query();
         $query->headerText('Content-Type', $id);
-        $indices = $GLOBALS['injector']->getInstance('IMP_Search')->runSearchQuery($query, $this->getConfigParam('imp_contents')->getMailbox());
+        $indices = $GLOBALS['injector']->getInstance('IMP_Search')->runQuery($query, $this->getConfigParam('imp_contents')->getMailbox());
 
         /* If not able to find the other parts of the message, prepare a
          * status message. */
index 3d3206e..e6ad631 100644 (file)
@@ -18,7 +18,6 @@ class IMP_Prefs_Ui
     const PREF_FOLDER_PAGE = 'folders.php';
     const PREF_NO_FOLDER = "nofolder\0";
     const PREF_SPECIALUSE = "specialuse\0";
-    const PREF_VTRASH = "vtrash\0";
 
     /**
      * Cached folder list.
@@ -235,6 +234,14 @@ class IMP_Prefs_Ui
             }
             break;
 
+        case 'searches':
+            if ($prefs->isLocked('vfolder')) {
+                $ui->suppress[] = 'searchesmanagement';
+            } else {
+                Horde::addScriptFile('searchesprefs.js', 'imp');
+            }
+            break;
+
         case 'server':
             $code = array();
 
@@ -250,8 +257,7 @@ class IMP_Prefs_Ui
                 $code['spam'] = _("Enter the name for your new spam folder.");
             }
 
-            if (!$prefs->isLocked('trash_folder') &&
-                !$prefs->isLocked('use_vtrash')) {
+            if (!$prefs->isLocked('trash_folder')) {
                 $code['trash'] = _("Enter the name for your new trash folder.");
             } else {
                 $ui->suppress[] = 'trashselect';
@@ -324,8 +330,9 @@ class IMP_Prefs_Ui
 
         /* Hide appropriate prefGroups. */
         if ($pop3) {
-            $ui->suppressGroups[] = 'server';
             $ui->suppressGroups[] = 'flags';
+            $ui->suppressGroups[] = 'searches';
+            $ui->suppressGroups[] = 'server';
         }
 
         try {
@@ -352,6 +359,10 @@ class IMP_Prefs_Ui
             $ui->suppressGroups[] = 'smime';
         }
 
+        if (empty($conf['user']['allow_folders'])) {
+            $ui->suppressGroups[] = 'searches';
+        }
+
         // TODO: For now, disable this group since accounts code has not
         // yet been fully written.
         $ui->suppressGroups[] = 'accounts';
@@ -392,6 +403,9 @@ class IMP_Prefs_Ui
         case 'pgppublickey':
             return $this->_pgpPublicKey($ui);
 
+        case 'searchesmanagement':
+            return $this->_searchesManagement();
+
         case 'sentmailselect':
             return $this->_sentmail();
 
@@ -469,6 +483,10 @@ class IMP_Prefs_Ui
             $this->_updatePgpPublicKey($ui);
             return false;
 
+        case 'searchesmanagement':
+            $this->_updateSearchesManagement($ui);
+            return false;
+
         case 'sentmailselect':
             return $this->_updateSentmail($ui);
 
@@ -515,8 +533,7 @@ class IMP_Prefs_Ui
          * trash is active. */
         if (($prefs->isDirty('use_trash') || $prefs->isDirty('trash_folder')) &&
             $prefs->getValue('use_trash') &&
-            !$prefs->getValue('trash_folder') &&
-            !$prefs->getValue('use_vtrash')) {
+            !$prefs->getValue('trash_folder')) {
             $GLOBALS['notification']->push(_("You have activated move to Trash but no Trash folder is defined. You will be unable to delete messages until you set a Trash folder in the preferences."), 'horde.warning');
         }
 
@@ -535,9 +552,6 @@ class IMP_Prefs_Ui
             if ($prefs->isDirty('use_trash')) {
                 $ui->suppress = array_diff($ui->suppress, array('trashselect', 'empty_trash_menu'));
             }
-            if ($prefs->isDirty('use_vtrash')) {
-                $GLOBALS['injector']->getInstance('IMP_Search')->init(true);
-            }
             break;
 
         case 'dimp':
@@ -555,10 +569,6 @@ class IMP_Prefs_Ui
             break;
 
         case 'server':
-            if ($prefs->isDirty('use_vinbox')) {
-                $GLOBALS['injector']->getInstance('IMP_Search')->init(true);
-            }
-
             if ($prefs->isDirty('subscribe')) {
                 $GLOBALS['registry']->getApiInstance('imp', 'application')->mailboxesChanged();
             }
@@ -1237,6 +1247,91 @@ class IMP_Prefs_Ui
         }
     }
 
+    /* Saved Searches management. */
+
+    /**
+     * Create code for saved searches management.
+     *
+     * @return string  HTML UI code.
+     */
+    protected function _searchesManagement()
+    {
+        $t = $GLOBALS['injector']->createInstance('Horde_Template');
+        $t->setOption('gettext', true);
+
+        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
+        $mailboxids = $out = array();
+        $view_mode = IMP::getViewMode();
+
+        $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER | IMP_Search::LIST_DISABLED);
+        foreach ($imp_search as $key => $val) {
+            if (!$val->prefDisplay) {
+                continue;
+            }
+
+            $editable = $imp_search->isVFolder($val, true);
+            $m_url = ($val->enabled && ($view_mode == 'imp'))
+                ? IMP::generateIMPUrl('mailbox.php', strval($val))->link(array('class' => 'vfolderenabled'))
+                : null;
+
+            if ($view_mode == 'dimp') {
+                $mailboxids['enable_' . $key] = strval($val);
+            }
+
+            $out[] = array(
+                'description' => Horde_String::truncate($val->querytext, 200),
+                'edit' => ($editable ? $imp_search->editUrl($val) : null),
+                'enabled' => $val->enabled,
+                'key' => $key,
+                'label' => htmlspecialchars($val->label),
+                'm_url' => $m_url
+            );
+
+        }
+        $t->set('vfolders', $out);
+
+        Horde::addInlineJsVars(array(
+            'ImpSearchesPrefs.confirm_delete_vfolder' => _("Are you sure you want to delete this virtual folder?"),
+            'ImpSearchesPrefs.mailboxids' => $mailboxids
+        ));
+
+        return $t->fetch(IMP_TEMPLATES . '/prefs/searches.html');
+    }
+
+    /**
+     * Update Saved Searches related preferences.
+     *
+     * @param Horde_Core_Prefs_Ui $ui  The UI object.
+     */
+    protected function _updateSearchesManagement($ui)
+    {
+        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
+
+        switch ($ui->vars->searches_action) {
+        case 'delete':
+            /* Remove 'enable_' prefix. */
+            $key = substr($ui->vars->searches_data, 7);
+            if ($ob = $imp_search[$key]) {
+                $GLOBALS['notification']->push(sprintf(_("Virtual Folder \"%s\" deleted."), $ob->label), 'horde.success');
+                unset($imp_search[$key]);
+            }
+            break;
+
+        default:
+            /* Update enabled status. */
+            $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER | IMP_Search::LIST_DISABLED);
+            $vfolders = array();
+
+            foreach ($imp_search as $key => $val) {
+                $form_key = 'enable_' . $key;
+                $val->enabled = !empty($ui->vars->$form_key);
+                $vfolders[$key] = $val;
+            }
+            $imp_search->setVFolders($vfolders);
+            break;
+        }
+    }
+
     /* Sentmail selection. */
 
     /**
@@ -1699,22 +1794,28 @@ class IMP_Prefs_Ui
      */
     protected function _trash()
     {
-        $t = $GLOBALS['injector']->createInstance('Horde_Template');
-        $t->setOption('gettext', true);
+        global $injector, $prefs;
 
-        $use_vtrash = $GLOBALS['prefs']->getValue('use_vtrash');
+        $imp_search = $injector->getInstance('IMP_Search');
+        $trash_folder = IMP::folderPref($prefs->getValue('trash_folder'), true);
+
+        $t = $injector->createInstance('Horde_Template');
+        $t->setOption('gettext', true);
 
         $t->set('label', Horde::label('trash', _("Trash folder:")));
         $t->set('nofolder', IMP::formMbox(self::PREF_NO_FOLDER, true));
-        $t->set('vtrash', IMP::formMbox(self::PREF_VTRASH, true));
-        $t->set('vtrash_select', $use_vtrash);
         $t->set('flist', IMP::flistSelect(array(
             'filter' => array('INBOX'),
             'new_folder' => true,
-            'selected' => ($use_vtrash ? null : IMP::folderPref($GLOBALS['prefs']->getValue('trash_folder'), true))
+            'selected' => $trash_folder
         )));
         $t->set('special_use', $this->_getSpecialUse(IMP_Folder::$specialUse['trash']));
 
+        if (!$prefs->isLocked('vfolder') || $imp_search['vtrash']->enabled) {
+            $t->set('vtrash', IMP::formMbox($imp_search->createSearchId('vtrash'), true));
+            $t->set('vtrash_select', $imp_search->isVTrash($trash_folder));
+        }
+
         return $t->fetch(IMP_TEMPLATES . '/prefs/trash.html');
     }
 
@@ -1727,23 +1828,16 @@ class IMP_Prefs_Ui
      */
     protected function _updateTrash($ui)
     {
-        global $prefs;
-
+        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
         $trash = IMP::formMbox($ui->vars->trash, false);
 
-        if ($trash == self::PREF_VTRASH) {
-            if (!$prefs->isLocked('use_vtrash')) {
-                $prefs->setValue('use_vtrash', 1);
-                $prefs->setValue('trash_folder', '');
-                return true;
-            }
-        } elseif ($this->_updateSpecialFolders('trash_folder', $trash, $ui->vars->trash_new, 'trash', $ui)) {
-            $prefs->setValue('use_vtrash', 0);
-            $prefs->setDirty('trash_folder', true);
-            return true;
+        if (!$GLOBALS['prefs']->isLocked('vfolder')) {
+            $vtrash = $imp_search['vtrash'];
+            $vtrash->enabled = $imp_search->isVTrash($trash);
+            $imp_search['vtrash'] = $vtrash;
         }
 
-        return false;
+        return $this->_updateSpecialFolders('trash_folder', $trash, $ui->vars->trash_new, 'trash', $ui);
     }
 
     /* Utility functions. */
index a75f168..db45297 100644 (file)
@@ -13,7 +13,7 @@
  * @license  http://www.fsf.org/copyleft/gpl.html GPL
  * @package  IMP
  */
-class IMP_Search implements Serializable
+class IMP_Search implements ArrayAccess, Iterator, Serializable
 {
     /* The mailbox search prefix. */
     const MBOX_PREFIX = "impsearch\0";
@@ -23,10 +23,14 @@ class IMP_Search implements Serializable
     const DIMP_FILTERSEARCH = 'dimpfsearch';
     const DIMP_QUICKSEARCH = 'dimpqsearch';
 
-    /* Bitmask constants for listQueries(). */
+    /* Bitmask filters for iterator. */
     const LIST_SEARCH = 1;
     const LIST_VFOLDER = 2;
-    const NO_BASIC_SEARCH = 4;
+    const LIST_DISABLED = 4;
+
+    /* Query creation types. */
+    const CREATE_QUERY = 1;
+    const CREATE_VFOLDER = 2;
 
     /**
      * Has the object data changed?
@@ -36,223 +40,39 @@ class IMP_Search implements Serializable
     public $changed = false;
 
     /**
-     * Save Virtual Folder information when adding entries?
+     * Iterator filter mask.
      *
-     * @var boolean
+     * @var integer
      */
-    protected $_saveVFolder = true;
+    protected $_filter = 0;
 
     /**
-     * Cached data.
+     * Iterator pointer.
      *
      * @var array
      */
-    protected $_cache = array();
+    protected $_iteratorPtr;
 
     /**
      * Search queries.
      *
-     * Format:
-     * <pre>
-     * 'id' => array(
-     *     'c' => (array) List of search criteria (the IMP-specific data
-     *            structure that allows recreation of the search query on the
-     *            search page). For virtual folders, this data is stored in
-     *            the preferences,
-     *     'f' => (array) List of folders to search,
-     *     'l' => (string) Description (label) of search,
-     *     'q' => (Horde_Imap_Client_Search_Query) [serialized],
-     *     'v' => (boolean) True if this is a Virtual Folder
-     * )
-     * </pre>
-     *
-     * The object properties for the 'c' (search criteria) object:
-     * <pre>
-     * 't' - (string) 'Type' - The criteria type
-     *       Values: Keys from self::searchFields(), 'flag', and 'or'.
-     * 'v' - (mixed) 'Value' - The data used to build the search
-     *       'header' - (string) The value to search for in the header
-     *       'customhdr' - (object) Contains 2 elements:
-     *                     'h' - (string) The header name
-     *                     's' - (string) The search string
-     *       'body' - (string) The value to search for in the body
-     *       'text' - (string) The value to search for in the entire
-     *                message
-     *       'date' - (object) Contains 3 elements:
-     *                'y' - (integer) The search year
-     *                'm' - (integer) The search month (is 1 less than
-     *                      the actual month)
-     *                'd' - (integer) The search day
-     *       'within' - (object) Contains 2 elements:
-     *                  'l' - (string) The length of time. Either 'y'
-     *                        (years), 'm' (months), or 'd' (days)
-     *                  'v' - (integer) The length of time
-     *       'size' - (integer) The search size in bytes
-     *       'flag' - (string) The flag to search for
-     * 'n' - (boolean) 'Not' - Should we do a not search?
-     *       Only used for the following types: header, customhdr, body, text
-     * </pre>
+     * Each subarray contains:
+     *   Keys: mailbox IDs.
+     *   Values: IMP_Search_Query objects.
      *
      * @var array
      */
-    protected $_search = array();
+    protected $_search = array(
+        'query' => array(),
+        'vfolders' => array()
+    );
 
     /**
-     * Serialize.
-     *
-     * @return string  Serialized representation of this object.
+     * Initialize session search data.
      */
-    public function serialize()
+    public function init()
     {
-        return serialize($this->_search);
-    }
-
-    /**
-     * Unserialize.
-     *
-     * @param string $data  Serialized data.
-     *
-     * @throws Exception
-     */
-    public function unserialize($data)
-    {
-        $data = @unserialize($data);
-        if (!is_array($data)) {
-            throw new Exception('Cache version change');
-        }
-
-        $this->_search = $data;
-        $this->changed = true;
-    }
-
-    /**
-     * Initialize search data for a session.
-     *
-     * @param boolean $no_vf  Don't readd the Virtual Folders.
-     */
-    public function init($no_vf = false)
-    {
-        if (!$no_vf) {
-            $imaptree = $GLOBALS['injector']->getInstance('IMP_Imap_Tree');
-            foreach ($this->_getVFolderList() as $key => $val) {
-                if (!empty($val['v']) &&
-                    !$this->isEditableVFolder($key)) {
-                    $imaptree->insertVFolders(array($key => $val['l']));
-                    unset($val['c']);
-                    $this->_search[$key] = $val;
-                    $this->changed = true;
-                }
-            }
-        }
-
-        $this->createVINBOXFolder();
-        $this->createVTrashFolder();
-    }
-
-    /**
-     * Return the base search fields.
-     *
-     * @return array  The base search fields.
-     */
-    public function searchFields()
-    {
-        return array(
-            'from' => array(
-                'label' => _("From"),
-                'type' => 'header',
-                'not' => true
-            ),
-            'to' => array(
-                'label' => _("To"),
-                'type' => 'header',
-                'not' => true
-            ),
-            'cc' => array(
-                'label' => _("Cc"),
-                'type' => 'header',
-                'not' => true
-            ),
-            'bcc' => array(
-                'label' => _("Bcc"),
-                'type' => 'header',
-                'not' => true
-            ),
-            'subject' => array(
-                'label' => _("Subject"),
-                'type' => 'header',
-                'not' => true
-            ),
-            'customhdr' => array(
-                'label' => _("Custom Header"),
-                'type' => 'customhdr',
-                'not' => true
-            ),
-            'body' => array(
-               'label' => _("Body"),
-               'type' => 'body',
-               'not' => true
-            ),
-            'text' => array(
-               'label' => _("Entire Message"),
-               'type' => 'text',
-               'not' => true
-            ),
-            'date_on' => array(
-                'label' => _("Date Equals (=)"),
-                'type' => 'date',
-                'not' => true
-            ),
-            'date_until' => array(
-                'label' => _("Date Until (<)"),
-                'type' => 'date',
-                'not' => true
-            ),
-            'date_since' => array(
-                'label' => _("Date Since (>=)"),
-                'type' => 'date',
-                'not' => true
-            ),
-            'older' => array(
-                'label' => _("Older Than"),
-                'type' => 'within',
-                'not' => true
-            ),
-            'younger' => array(
-                'label' => _("Younger Than"),
-                'type' => 'within',
-                'not' => true
-            ),
-            // Displayed in KB, but stored internally in bytes
-            'size_smaller' => array(
-                'label' => _("Size (KB) <"),
-                'type' => 'size',
-                'not' => false
-            ),
-            // Displayed in KB, but stored internally in bytes
-            'size_larger' => array(
-                'label' => _("Size (KB) >"),
-                'type' => 'size',
-                'not' => false
-            ),
-        );
-    }
-
-    /**
-     * Return the base flag fields.
-     *
-     * @return array  The base flag fields.
-     */
-    public function flagFields()
-    {
-        $flags = array();
-        $flist = $GLOBALS['injector']->getInstance('IMP_Imap_Flags')->getFlagList(null);
-
-        for ($i = 0, $cnt = count($flist['set']); $i < $cnt; ++$i) {
-            $flags[$flist['set'][$i]['f']] = $flist['set'][$i]['l'];
-            $flags[$flist['unset'][$i]['f']] = sprintf(_("Not %s"), $flist['unset'][$i]['l']);
-        }
-
-        return $flags;
+        $this->setVFolders($this->getVFolders(), false);
     }
 
     /**
@@ -271,12 +91,12 @@ class IMP_Search implements Serializable
         $mbox = '';
         $sorted = new IMP_Indices();
 
-        if (empty($this->_search[$id])) {
+        $query = $this[$id]->query;
+        if (!$query) {
             return $sorted;
         }
 
         /* Prepare the search query. */
-        $query = unserialize($this->_search[$id]['q']);
         if (!empty($ob)) {
             $query->andSearch(array($ob));
         }
@@ -287,7 +107,7 @@ class IMP_Search implements Serializable
             $sortpref['by'] = $GLOBALS['prefs']->getValue('sortdate');
         }
 
-        foreach ($this->_search[$id]['f'] as $val) {
+        foreach ($this[$id]->mboxes as $val) {
             $results = $this->imapSearch($val, $query, array('reverse' => $sortpref['dir'], 'sort' => array($sortpref['by'])));
             $sorted->add($val, $results['sort']);
         }
@@ -308,8 +128,8 @@ class IMP_Search implements Serializable
      *
      * @return IMP_Indices  An indices object.
      */
-    public function runSearchQuery($query, $mailbox, $sortby = null,
-                                   $sortdir = null)
+    public function runQuery($query, $mailbox, $sortby = null,
+                             $sortdir = null)
     {
         try {
             $results = $this->imapSearch($mailbox, $query, array('reverse' => $sortdir, 'sort' => is_null($sortby) ? null : array($sortby)));
@@ -370,472 +190,445 @@ class IMP_Search implements Serializable
     /**
      * Creates the IMAP search query in the IMP session.
      *
-     * @param object $query    The search query object
-     *                         (Horde_Imap_Client_Search_Query).
-     * @param array $folders   The list of folders to search.
      * @param array $criteria  The search criteria array.
+     * @param array $mboxes    The list of mailboxes to search.
      * @param string $label    The label to use for the search results.
-     * @param string $id       The query id (otherwise, one is
-     *                         automatically generated).
+     * @param integer $type    Query type.
+     * @param string $id       Use as the mailbox ID.
      *
-     * @return string  Returns the search query id.
+     * @return IMP_Search_Query  Returns the query object.
      */
-    public function createSearchQuery($query, $folders, $criteria, $label,
-                                      $id = null)
+    public function createQuery($criteria, $mboxes, $label = null,
+                                $type = self::CREATE_QUERY, $id = null)
     {
-        $id = is_null($id)
-            ? strval(new Horde_Support_Randomid())
-            : $this->_strip($id);
-
-        $this->_search[$id] = array(
-            'c' => $criteria,
-            'f' => $folders,
-            'l' => $label,
-            'q' => serialize($query),
-            'v' => false
-        );
+        if (!is_null($id)) {
+            $id = $this->_strip($id);
+        }
+
+        switch ($type) {
+        case self::CREATE_QUERY:
+            $cname = 'IMP_Search_Query';
+            break;
+
+        case self::CREATE_VFOLDER:
+            $cname = 'IMP_Search_Vfolder';
+            break;
+        }
+
+        $ob = new $cname(array_filter(array(
+            'add' => $criteria,
+            'id' => $id,
+            'label' => $label,
+            'mboxes' => $mboxes
+        )));
+
+        switch ($type) {
+        case self::CREATE_QUERY:
+            $this->_search['query'][$ob->id] = $ob;
+            break;
+
+        case self::CREATE_VFOLDER:
+            /* This will overwrite previous value, if it exists. */
+            $this->_search['vfolders'][$ob->id] = $ob;
+            $this->setVFolders($this->_search['vfolders']);
+            break;
+        }
+
         $this->changed = true;
 
-        return $id;
+        return $ob;
     }
 
     /**
-     * Deletes an IMAP search query.
-     *
-     * @param string $id          The search query id.
-     * @param boolean $no_delete  Don't delete the entry in the tree object.
+     * Obtains the list of virtual folders for the current user.
      *
-     * @return string  Returns the search query id.
+     * @return array  The list of virtual folders.  Keys are mailbox IDs,
+     *                values are IMP_Search_Vfolder objects.
      */
-    public function deleteSearchQuery($id, $no_delete = false)
+    public function getVFolders()
     {
-        $id = $this->_strip($id);
-        $is_vfolder = $this->isVFolder($id);
-        unset($this->_search[$id]);
-        $this->changed = true;
-
-        if ($is_vfolder) {
-            $vfolders = $this->_getVFolderList();
-            unset($vfolders[$id]);
-            $this->_saveVFolderList($vfolders);
+        if ($pref_vf = $GLOBALS['prefs']->getValue('vfolder')) {
+            $pref_vf = @unserialize($pref_vf);
+        }
 
-            if (!$no_delete) {
-                $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->delete($id);
+        $has_vinbox = $has_vtrash = false;
+        $vf = array();
+
+        if (is_array($pref_vf)) {
+            foreach ($pref_vf as $val) {
+                if ($val instanceof IMP_Search_Vfolder) {
+                    $vf[$val->id] = $val;
+
+                    if (!$has_vinbox &&
+                        ($val instanceof IMP_Search_Vfolder_Vinbox)) {
+                        $has_vinbox = true;
+                    } elseif (!$has_vtrash &&
+                        ($val instanceof IMP_Search_Vfolder_Vtrash)) {
+                        $has_vtrash = true;
+                    }
+                }
             }
         }
-    }
 
-    /**
-     * Retrieves the previously stored search criteria information.
-     *
-     * @param string $id  The search query id.
-     *
-     * @return array  The array necessary to rebuild the search UI page.
-     */
-    public function getCriteria($id)
-    {
-        $id = $this->_strip($id);
-        if (isset($this->_search[$id]['c'])) {
-            return $this->_search[$id]['c'];
+        if (!$has_vtrash) {
+            $ob = new IMP_Search_Vfolder_Vtrash(array(
+                'disable' => true
+            ));
+            $vf = array($ob->id => $ob) + $vf;
         }
 
-        if ($this->isVFolder($id)) {
-            $vlist = $this->_getVFolderList();
-            return $vlist[$id]['c'];
+        if (!$has_vinbox) {
+            $ob = new IMP_Search_Vfolder_Vinbox(array(
+                'disable' => true
+            ));
+            $vf = array($ob->id => $ob) + $vf;
         }
 
-        return array();
+        return $vf;
     }
 
     /**
-     * Generates the label to use for search results.
-     *
-     * @param string $id  The search query id.
+     * Saves the list of virtual folders for the current user.
      *
-     * @return string  The search results label.
+     * @param array $vfolders  The virtual folder list.
+     * @param boolean $save    Save the virtual folder list to the preference
+     *                         backend?
      */
-    public function getLabel($id)
+    public function setVFolders($vfolders, $save = true)
     {
-        $id = $this->_strip($id);
-        return isset($this->_search[$id]['l'])
-            ? $this->_search[$id]['l']
-            : '';
+        if ($save) {
+            $GLOBALS['prefs']->setValue('vfolder', serialize(array_values($vfolders)));
+        }
+
+        $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->updateVFolders($vfolders);
+        $this->_search['vfolders'] = $vfolders;
+        $this->changed = true;
     }
 
     /**
-     * Obtains the list of virtual folders for the current user.
+     * Is a mailbox a virtual folder?
      *
-     * @return array  The list of virtual folders.
+     * @param string $id         The mailbox ID.
+     * @param boolean $editable  Is this an editable (i.e. not built-in)
+     *                           virtual folder?
+     *
+     * @return boolean  Whether the mailbox ID is a virtual folder.
      */
-    protected function _getVFolderList()
+    public function isVFolder($id, $editable = false)
     {
-        if (!isset($this->_cache['vfolder'])) {
-            if ($vf = $GLOBALS['prefs']->getValue('vfolder')) {
-                $vf = @unserialize($vf);
-            }
-
-            if (empty($vf) || !is_array($vf)) {
-                $vf = array();
-            }
-
-            $this->_cache['vfolder'] = $vf;
-        }
-
-        return $this->_cache['vfolder'];
+        return (isset($this->_search['vfolders'][$this->_strip($id)]) &&
+                (!$editable || $this[$id]->canEdit));
     }
 
     /**
-     * Saves the list of virtual folders for the current user.
+     * Determines whether a mailbox ID is the Virtual Trash Folder.
      *
-     * @param array  The virtual folder list.
+     * @param string $id  The mailbox id.
+     *
+     * @return boolean  True if the ID is the Virtual Trash folder.
      */
-    protected function _saveVFolderList($vfolder)
+    public function isVTrash($id)
     {
-        $GLOBALS['prefs']->setValue('vfolder', serialize($vfolder));
-        $this->_cache['vfolder'] = $vfolder;
+        return (($this->isVFolder($id)) &&
+            ($this[$id] instanceof IMP_Search_Vfolder_Vtrash));
     }
 
     /**
-     * Add a virtual folder for the current user.
+     * Determines whether a mailbox ID is the Virtual INBOX Folder.
      *
-     * @param object $query   The search query object
-     *                        (Horde_Imap_Client_Search_Query).
-     * @param array $folders  The list of folders to search.
-     * @param array $search   The search array used to build the search UI
-     *                        screen.
-     * @param string $label   The label to use for the search results.
-     * @param string $id      The virtual folder id.
+     * @param string $id  The mailbox id.
      *
-     * @return string  The virtual folder ID.
+     * @return boolean  True if the ID is the Virtual INBOX folder.
      */
-    public function addVFolder($query, $folders, $search, $label, $id = null)
+    public function isVinbox($id)
     {
-        $id = $this->createSearchQuery($query, $folders, $search, $label, $id);
-        $this->_search[$id]['v'] = true;
-        $this->changed = true;
-
-        if ($this->_saveVFolder) {
-            $vfolders = $this->_getVFolderList();
-            $vfolders[$id] = $this->_search[$id];
-            $this->_saveVFolderList($vfolders);
-        }
-
-        $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->insertVFolders(array($id => $label));
-
-        return $id;
+        return (($this->isVFolder($id)) &&
+            ($this[$id] instanceof IMP_Search_Vfolder_Vinbox));
     }
 
     /**
-     * Add a virtual trash folder for the current user.
+     * Is a mailbox a search query?
+     *
+     * @param string $id         The mailbox ID.
+     * @param boolean $editable  Is this an editable (i.e. not built-in)
+     *                           search query?
      */
-    public function createVTrashFolder()
+    public function isQuery($id, $editable = false)
     {
-        /* Delete the current Virtual Trash folder, if it exists. */
-        $vtrash_id = $GLOBALS['prefs']->getValue('vtrash_id');
-        if (!empty($vtrash_id)) {
-            $this->deleteSearchQuery($vtrash_id, true);
-        }
-
-        if (!$GLOBALS['prefs']->getValue('use_vtrash')) {
-            return;
-        }
-
-        /* Create Virtual Trash with new folder list. */
-        $flist = array_keys(iterator_to_array($GLOBALS['injector']->getInstance('IMP_Imap_Tree')));
-
-        $query = new Horde_Imap_Client_Search_Query();
-        $query->flag('\\deleted', true);
-        $label = _("Virtual Trash");
-
-        $this->_saveVFolder = false;
-        if (empty($vtrash_id)) {
-            $vtrash_id = $this->addVFolder($query, $flist, array(), $label);
-            $GLOBALS['prefs']->setValue('vtrash_id', $vtrash_id);
-        } else {
-            $this->addVFolder($query, $flist, array(), $label, $vtrash_id);
-        }
-        $this->_saveVFolder = true;
+        return (isset($this->_search['query'][$this->_strip($id)]) &&
+                (!$editable ||
+                 !in_array($this[$id]->id, array(self::BASIC_SEARCH, self::DIMP_FILTERSEARCH, self::DIMP_QUICKSEARCH))));
     }
 
     /**
-     * Determines whether a virtual folder ID is the Virtual Trash Folder.
+     * Get the list of searchable folders for the given search query.
      *
      * @param string $id  The search query id.
      *
-     * @return boolean  True if the ID is the Virtual Trash folder.
+     * @return array  The list of searchable folders.
      */
-    public function isVTrashFolder($id)
+    public function getSearchMailboxes($id)
     {
-        $vtrash_id = $GLOBALS['prefs']->getValue('vtrash_id');
-        return (!empty($vtrash_id) && ($this->_strip($id) == $vtrash_id));
+        return isset($this[$id])
+            ? $this[$id]->mboxes
+            : array();
     }
 
     /**
-     * Add a virtual INBOX folder for the current user.
+     * Returns a link to edit a given search query.
+     *
+     * @param string $id  The search query id.
+     *
+     * @return Horde_Url  The URL to the search page.
      */
-    public function createVINBOXFolder()
+    public function editUrl($id)
     {
-        /* Delete the current Virtual Inbox folder, if it exists. */
-        $vinbox_id = $GLOBALS['prefs']->getValue('vinbox_id');
-        if (!empty($vinbox_id)) {
-            $this->deleteSearchQuery($vinbox_id, true);
-        }
-
-        if (!$GLOBALS['prefs']->getValue('use_vinbox')) {
-            return;
-        }
-
-        /* Create Virtual INBOX with nav_poll list. */
-        $flist = $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->getPollList();
-
-        $query = new Horde_Imap_Client_Search_Query();
-        $query->flag('\\seen', false);
-        $query->flag('\\deleted', false);
-        $label = _("Virtual INBOX");
-
-        $this->_saveVFolder = false;
-        if (empty($vinbox_id)) {
-            $vinbox_id = $this->addVFolder($query, $flist, array(), $label);
-            $GLOBALS['prefs']->setValue('vinbox_id', $vinbox_id);
-        } else {
-            $this->addVFolder($query, $flist, array(), $label, $vinbox_id);
-        }
-        $this->_saveVFolder = true;
+        return Horde::url('search.php')->add(array('edit_query' => $this->createSearchId($id)));
     }
 
     /**
-     * Determines whether a virtual folder ID is the Virtual INBOX Folder.
+     * Is the given mailbox a search mailbox?
      *
-     * @param string $id  The search query id.
+     * @param string $id  The mailbox name.
      *
-     * @return boolean  True if the ID is the Virtual INBOX folder.
+     * @return boolean  Whether the given mailbox name is a search mailbox.
      */
-    public function isVINBOXFolder($id)
+    public function isSearchMbox($id)
     {
-        $vinbox_id = $GLOBALS['prefs']->getValue('vinbox_id');
-        return (!empty($vinbox_id) && ($this->_strip($id) == $vinbox_id));
+        return (strpos($id, self::MBOX_PREFIX) === 0);
     }
 
     /**
-     * Is a mailbox an editable Virtual Folder?
+     * Strip the identifying label from a mailbox ID.
      *
-     * @param string $id  The search query id.
+     * @param string $id  The mailbox query ID.
      *
-     * @return boolean  True if the mailbox is both a virtual folder and can
-     *                  be edited.
+     * @return string  The virtual folder ID, with any IMP specific
+     *                 identifying information stripped off.
      */
-    public function isEditableVFolder($id)
+    protected function _strip($id)
     {
-        $id = $this->_strip($id);
-        return ($this->isVFolder($id) &&
-                !$this->isVTrashFolder($id) &&
-                !$this->isVINBOXFolder($id));
+        return $this->isSearchMbox($id)
+            ? substr($id, strlen(self::MBOX_PREFIX))
+            : $id;
     }
 
     /**
-     * Return a list of queryies.
+     * Create the canonical search ID for a given search query.
      *
-     * @param integer $mask   A bitmask of the query types to return.
-     *                        IMP_Search::LIST_SEARCH,
-     *                        IMP_Search::LIST_VFOLDER, and/or
-     *                        IMP_Search::NO_BASIC_SEARCH.
-     * @param boolean $label  If true, returns the label. Otherwise, returns
-     *                        a textual representation.
+     * @param string $id  The mailbox query ID.
      *
-     * @return array  An array with the folder IDs as the key and the labels
-     *                as the value.
+     * @return string  The canonical search query ID.
      */
-    public function listQueries($mask = null, $label = true)
+    public function createSearchId($id)
     {
-        $folders = array();
-
-        if (empty($this->_search)) {
-            return $folders;
-        }
+        return self::MBOX_PREFIX . $this->_strip($id);
+    }
 
-        if (is_null($mask)) {
-            $mask = self::LIST_SEARCH | self::LIST_VFOLDER;
-        }
+    /* ArrayAccess methods. */
 
-        foreach ($this->_search as $key => $val) {
-            if ((($mask & self::LIST_VFOLDER) && !empty($val['v'])) ||
-                (($mask & self::LIST_SEARCH) && empty($val['v'])) &&
-                (!($mask & self::NO_BASIC_SEARCH) ||
-                 ($key != self::BASIC_SEARCH))) {
-                $folders[$key] = $label
-                    ? $this->getLabel($key)
-                    : $this->searchQueryText($key);
-            }
-        }
+    public function offsetExists($offset)
+    {
+        $id = $this->_strip($offset);
+        return (isset($this->_search['query'][$id]) ||
+                isset($this->_search['vfolders'][$id]));
+    }
 
-        if ($label) {
-            natcasesort($folders);
-            return $folders;
+    public function offsetGet($offset)
+    {
+        $id = $this->_strip($offset);
+        if (isset($this->_search['query'][$id])) {
+            return $this->_search['query'][$id];
+        } elseif (isset($this->_search['vfolders'][$id])) {
+            return $this->_search['vfolders'][$id];
         }
 
-        return array_reverse($folders, true);
+        return null;
     }
 
     /**
-     * Get the list of searchable folders for the given search query.
+     * Alter the current IMAP search query.
      *
-     * @param string $id  The search query id.
+     * @param string $id               The search query id.
+     * @param IMP_Search_Query $value  The query object.
      *
-     * @return array  The list of searchable folders.
+     * @throws InvalidArgumentException
      */
-    public function getSearchFolders($id)
+    public function offsetSet($offset, $value)
     {
-        $id = $this->_strip($id);
-        return isset($this->_search[$id]['f'])
-            ? $this->_search[$id]['f']
-            : array();
+        if (!($value instanceof IMP_Search_Query)) {
+            throw new InvalidArgumentException('$value must be a query object.');
+        }
+
+        $id = $this->_strip($offset);
+        if (isset($this->_search['query'][$id])) {
+            $this->_search['query'][$id] = $value;
+            return;
+        } elseif (isset($this->_search['vfolders'][$id])) {
+            $this->_search['vfolders'][$id] = $value;
+            return;
+        }
+
+        throw new InvalidArgumentException('Creating search queries by array index is not supported. Use createQuery() instead.');
     }
 
     /**
-     * Return search query text representation for a given search ID.
+     * Deletes an IMAP search query.
      *
      * @param string $id  The search query id.
-     *
-     * @return array  The textual description of the search.
      */
-    public function searchQueryText($id)
+    public function offsetUnset($offset)
     {
-        $id = $this->_strip($id);
+        $id = $this->_strip($offset);
 
-        if (empty($this->_search[$id])) {
-            return '';
-        } elseif ($this->isVINBOXFolder($id) || $this->isVTrashFolder($id)) {
-            return $this->_search[$id]['l'];
-        }
-
-        $flagfields = $this->flagFields();
-        $searchfields = $this->searchFields();
-        $text = '';
-        $criteria = $this->getCriteria($id);
+        foreach (array_keys($this->_search) as $val) {
+            if (isset($this->_search[$val][$id])) {
+                unset($this->_search[$val][$id]);
+                $this->changed = true;
 
-        $text = _("Search") . ' ';
-        $text_array = array();
-        foreach ($criteria as $rule) {
-            $field = $rule->t;
-
-            switch ($field) {
-            case 'flag':
-                if (isset($flagfields[$rule->v])) {
-                    $text_array[] = sprintf(_("flagged \"%s\""), $flagfields[$rule->v]);
+                if ($val == 'vfolders') {
+                    $this->setVFolders($this->_search['vfolders']);
                 }
                 break;
+            }
+        }
+    }
 
-            case 'or':
-                $text .= implode(' ' . _("and") . ' ', $text_array) . ' ' . _("OR") . ' ';
-                $text_array = array();
-                break;
-
-            default:
-                switch ($searchfields[$field]['type']) {
-                case 'customhdr':
-                    $text_array[] = sprintf("%s for '%s'", $rule->v->h, ((!empty($rule->n)) ? _("not") . ' ' : '') . $rule->v->s);
-                    break;
-
-                case 'date':
-                    $date_ob = new Horde_Date($rule->v);
-                    $text_array[] = sprintf("%s '%s'", $searchfields[$field]['label'], $date_ob->strftime("%x"));
-                    break;
+    /* Iterator methods. */
 
-                case 'within':
-                    $text_array[] = sprintf("%s %u %s", $searchfields[$field]['label'], $rule->v->v, $rule->v->l == 'y' ? _("years") : ($rule->v->l == 'm' ? _("months") : _("days")));
-                    break;
+    public function current()
+    {
+        return (($key = key($this->_search)) !== null)
+            ? current($this->_search[$key])
+            : null;
+    }
 
-                case 'size':
-                    $text_array[] = $searchfields[$field]['label'] . ' ' . ($rule->v / 1024);
-                    break;
+    public function key()
+    {
+        return (($key = key($this->_search)) !== null)
+            ? key($this->_search[$key])
+            : null;
+    }
 
-                default:
-                    $text_array[] = sprintf("%s for '%s'", $searchfields[$field]['label'], ((!empty($rule->n)) ? _("not") . ' ' : '') . $rule->v);
-                    break;
+    public function next()
+    {
+        $curr = null;
+
+        while (($skey = key($this->_search)) !== null) {
+            /* When switching between search types, need to catch the first
+             * element of the new array. */
+            $curr = is_null($curr)
+                ? next($this->_search[$skey])
+                : current($this->_search[$skey]);
+
+            while ($curr !== false) {
+                if ($this->_currValid()) {
+                    return;
                 }
+                $curr = next($this->_search[$skey]);
             }
-        }
 
-        return $text . implode(' ' . _("and") . ' ', $text_array) . ' ' . _("in") . ' ' . implode(', ', $this->getSearchFolders($id));
+            next($this->_search);
+        }
     }
 
-    /**
-     * Returns a link to edit a given search query.
-     *
-     * @param string $id  The search query id.
-     *
-     * @return Horde_Url  The URL to the search page.
-     */
-    public function editUrl($id)
+    public function rewind()
     {
-        return Horde::url('search.php')->add(array('edit_query' => $this->createSearchId($id)));
+        foreach (array_keys($this->_search) as $key) {
+            reset($this->_search[$key]);
+        }
+        reset($this->_search);
+
+        if ($this->valid() && !$this->_currValid()) {
+            $this->next();
+        }
     }
 
-    /**
-     * Returns a link to delete a given search query.
-     *
-     * @param string $id  The search query id.
-     *
-     * @return Horde_Url  The URL to allow deletion of the search query.
-     */
-    public function deleteUrl($id)
+    public function valid()
     {
-        return Horde::url('folders.php')->add(array(
-            'actionID' => 'delete_search_query',
-            'folders_token' => Horde::getRequestToken('imp.folders'),
-            'queryid' => $this->createSearchId($id)
-        ));
+        return (key($this->_search) !== null);
     }
 
+    /* Helper functions for Iterator methods. */
+
     /**
-     * Is the given mailbox a search mailbox?
-     *
-     * @param string $id  The mailbox name.
+     * Set the current iterator filter and reset the internal pointer.
      *
-     * @return boolean  Whether the given mailbox name is a search mailbox.
+     * @param integer $mask  A mask with the following possible elements:
+     * <pre>
+     * IMP_Search::LIST_SEARCH
+     * IMP_Search::LIST_VFOLDER
+     * IMP_Search::NO_BASIC_SEARCH.
+     * </pre>
      */
-    public function isSearchMbox($id)
+    public function setIteratorFilter($mask = 0)
     {
-        return (strpos($id, self::MBOX_PREFIX) === 0);
+        $this->_filter = $mask;
+        reset($this);
     }
 
     /**
-     * Is the given mailbox a virtual folder?
-     *
-     * @param string $id  The search query id.
+     * Returns true if the current object is valid given the current filter.
      *
-     * @return boolean  Whether the given mailbox name is a virtual folder.
+     * @return boolean  True if the object should be displayed.
      */
-    public function isVFolder($id)
+    protected function _currValid()
     {
-        return !empty($this->_search[$this->_strip($id)]['v']);
+        if (!($ob = $this->current())) {
+            return false;
+        }
+
+        if ($ob->enabled ||
+            ($this->_filter & self::LIST_DISABLED)) {
+            if (!$this->_filter) {
+                return true;
+            }
+
+            if (($this->_filter & self::LIST_VFOLDER) &&
+                $this->isVfolder($ob)) {
+                return true;
+            }
+
+            if (($this->_filter & self::LIST_SEARCH) &&
+                !$this->isVfolder($ob)) {
+                return true;
+            }
+
+            if ($this->isQuery($ob, true)) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
+    /* Serializable methods. */
+
     /**
-     * Strip the identifying label from a mailbox ID.
-     *
-     * @param string $id  The mailbox query ID.
+     * Serialize.
      *
-     * @return string  The virtual folder ID, with any IMP specific
-     *                 identifying information stripped off.
+     * @return string  Serialized representation of this object.
      */
-    protected function _strip($id)
+    public function serialize()
     {
-        return $this->isSearchMbox($id)
-            ? substr($id, strlen(self::MBOX_PREFIX))
-            : $id;
+        return serialize($this->_search);
     }
 
     /**
-     * Create the canonical search ID for a given search query.
+     * Unserialize.
      *
-     * @param string $id  The mailbox query ID.
+     * @param string $data  Serialized data.
      *
-     * @return string  The canonical search query ID.
+     * @throws Exception
      */
-    public function createSearchId($id)
+    public function unserialize($data)
     {
-        return self::MBOX_PREFIX . $this->_strip($id);
+        $data = @unserialize($data);
+        if (!is_array($data)) {
+            throw new Exception('Cache version change');
+        }
+
+        $this->_search = $data;
     }
 
 }
diff --git a/imp/lib/Search/Element.php b/imp/lib/Search/Element.php
new file mode 100644 (file)
index 0000000..ce8d040
--- /dev/null
@@ -0,0 +1,99 @@
+<?php
+/**
+ * This class provides the framework for a search query element.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+abstract class IMP_Search_Element implements Serializable
+{
+    /* Serialized version. */
+    const VERSION = 1;
+
+    /**
+     * Allow NOT search on this element?
+     *
+     * @var boolean
+     */
+    public $not = true;
+
+    /**
+     * Data for this element.
+     *
+     * @var object
+     */
+    protected $_data;
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     */
+    abstract public function createQuery($queryob);
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    abstract public function queryText();
+
+    /**
+     * Returns the criteria data for the element.
+     *
+     * @return object  The criteria (see each class for the available
+     *                 properties).
+     */
+    public function getCriteria()
+    {
+        return $this->_data;
+    }
+
+    /* Serializable methods. */
+
+    /**
+     * Serialization.
+     *
+     * @return string  Serialized data.
+     */
+    public function serialize()
+    {
+        return empty($this->_data)
+            ? null
+            : json_encode(array(
+                  self::VERSION,
+                  $this->_data
+              ));
+    }
+
+    /**
+     * Unserialization.
+     *
+     * @param string $data  Serialized data.
+     *
+     * @throws Exception
+     */
+    public function unserialize($data)
+    {
+        if (empty($data)) {
+            return;
+        }
+
+        $data = json_decode($data);
+        if (!is_array($data) ||
+            !isset($data[0]) ||
+            ($data[0] != self::VERSION)) {
+            throw new Exception('Cache version change');
+        }
+
+        $this->_data = $data[1];
+    }
+
+}
diff --git a/imp/lib/Search/Element/Bulk.php b/imp/lib/Search/Element/Bulk.php
new file mode 100644 (file)
index 0000000..d913c3b
--- /dev/null
@@ -0,0 +1,56 @@
+<?php
+/**
+ * This class handles the bulk message search query.
+ *
+ * Precedence is a non-standard, discouraged header pursuant to RFC 2076
+ * [3.9]. However, it is widely used and may be useful in sorting out
+ * unwanted e-mail.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Bulk extends IMP_Search_Element
+{
+    /**
+     * Constructor.
+     *
+     * @param boolean $not  If true, do a 'NOT' search of $text.
+     */
+    public function __construct($not = false)
+    {
+        /* Data element: (integer) Do a NOT search? */
+        $this->_data = intval($not);
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $queryob->headerText('precedence', 'bulk', $this->_data);
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        return ($this->_data ? _("not") . ' ' : '') . _("Bulk Messages");
+    }
+
+}
diff --git a/imp/lib/Search/Element/Date.php b/imp/lib/Search/Element/Date.php
new file mode 100644 (file)
index 0000000..a9e74bf
--- /dev/null
@@ -0,0 +1,82 @@
+<?php
+/**
+ * This class handles date-related search queries.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Date extends IMP_Search_Element
+{
+    /* Date types. */
+    const DATE_ON = 1;
+    const DATE_BEFORE = 2;
+    const DATE_SINCE = 3;
+
+    /**
+     * Constructor.
+     *
+     * @param DateTime $date  Date object.
+     * @param integer $type   Either:
+     * <pre>
+     * IMP_Search_Element_Date::DATE_ON
+     * IMP_Search_Element_Date::DATE_BEFORE
+     * IMP_Search_Element_Date::DATE_SINCE
+     * </pre>
+     */
+    public function __construct(DateTime $date, $type)
+    {
+        /* Data element:
+         * d = (integer) UNIX timestamp.
+         * t = (integer) Type: one of the self::DATE_* constants. */
+        $this->_data = new stdClass;
+        $this->_data->d = $date->format('U');
+        $this->_data->t = $type;
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $date = new DateTime($this->_data->d);
+        $queryob->dateSearch($date, ($this->_data->t == self::DATE_ON) ? Horde_Imap_Client_Search_Query::DATE_ON : (($this->_data->t == self::DATE_BEFORE) ? Horde_Imap_Client_Search_Query::DATE_BEFORE : Horde_Imap_Client_Search_Query::DATE_SINCE));
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        switch ($this->_data->t) {
+        case self::DATE_ON:
+            $label = _("Date Equals (=)");
+            break;
+
+        case self::DATE_BEFORE:
+            $label = _("Date Until (<)");
+            break;
+
+        case self::DATE_SINCE:
+            $label = _("Date Since (>=)");
+            break;
+        }
+
+        return sprintf("%s '%s'", $label, strftime('%x', $this->_data->d));
+    }
+
+}
diff --git a/imp/lib/Search/Element/Flag.php b/imp/lib/Search/Element/Flag.php
new file mode 100644 (file)
index 0000000..f2454ef
--- /dev/null
@@ -0,0 +1,66 @@
+<?php
+/**
+ * This class handles flag/keyword search queries.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Flag extends IMP_Search_Element
+{
+    /**
+     * Allow NOT search on this element?
+     *
+     * @var boolean
+     */
+    public $not = false;
+
+    /**
+     * Constructor.
+     *
+     * @param string $name  The flag or keyword name.
+     * @param boolean $set  If true, search for messages that have the flag
+     *                      set.  If false, search for messages that do not
+     *                      have the flag set.
+     */
+    public function __construct($name, $set = true)
+    {
+        /* Data element:
+         * f = (string) Flag/keyword name.
+         * s = (integer) Search for set flag? */
+        $this->_data = new stdClass;
+        $this->_data->f = $name;
+        $this->_data->s = intval($set);
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $queryob->flag($this->_data->f, $this->_data->s);
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        return sprintf(_("flagged \"%s\""), $GLOBALS['injector']->getInstance('IMP_Imap_Flags')->getLabel($this->_data->f, $this->_data->s));
+    }
+
+}
diff --git a/imp/lib/Search/Element/Header.php b/imp/lib/Search/Element/Header.php
new file mode 100644 (file)
index 0000000..33d9b8a
--- /dev/null
@@ -0,0 +1,60 @@
+<?php
+/**
+ * This class handles header-related search queries.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Header extends IMP_Search_Element
+{
+    /**
+     * Constructor.
+     *
+     * @param string $text    The search text.
+     * @param string $header  The header field.
+     * @param boolean $not    If true, do a 'NOT' search of $text.
+     */
+    public function __construct($text, $header, $not = false)
+    {
+        /* Data element:
+         * h = (string) Header name (lower case).
+         * n = (integer) Do a NOT search?
+         * t = (string) The search text. */
+        $this->_data = new stdClass;
+        $this->_data->h = trim(Horde_String::lower($header));
+        $this->_data->n = intval($not);
+        $this->_data->t = $text;
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $queryob->headerText($this->_data->h, $this->_data->t, $this->_data->n);
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        return sprintf("%s (Header) for '%s'", Horde_String::ucfirst($this->_data->h), ($this->_data->n ? _("not") . ' ' : '') . $this->_data->t);
+    }
+
+}
diff --git a/imp/lib/Search/Element/Or.php b/imp/lib/Search/Element/Or.php
new file mode 100644 (file)
index 0000000..bd17ad2
--- /dev/null
@@ -0,0 +1,43 @@
+<?php
+/**
+ * This class handles an OR clause in a search query.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Or extends IMP_Search_Element
+{
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     *
+     */
+    public function createQuery($queryob)
+    {
+        $ob = new Horde_Imap_Client_Search_Query();
+        $ob->orSearch(array($queryob));
+
+        return $ob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        return _("OR");
+    }
+
+}
diff --git a/imp/lib/Search/Element/Recipient.php b/imp/lib/Search/Element/Recipient.php
new file mode 100644 (file)
index 0000000..7f1132c
--- /dev/null
@@ -0,0 +1,71 @@
+<?php
+/**
+ * This class handles the recipient (To/Cc/Bcc) search query.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Recipient extends IMP_Search_Element
+{
+    /**
+     * Constructor.
+     *
+     * @param string $text  The search text.
+     * @param boolean $not  If true, do a 'NOT' search of $text.
+     */
+    public function __construct($text, $not = false)
+    {
+        /* Data element:
+         * n = (integer) Do a NOT search?
+         * t = (string) The search text. */
+        $this->_data = new stdClass;
+        $this->_data->n = intval($not);
+        $this->_data->t = $text;
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $and_ob = new Horde_Imap_Client_Search_Query();
+
+        $ob = new Horde_Imap_Client_Search_Query();
+        $ob->headerText('to', $this->_data->t, $this->_data->n);
+        $and_ob->orSearch(array($ob));
+
+        $ob = new Horde_Imap_Client_Search_Query();
+        $ob->headerText('cc', $this->_data->t, $this->_data->n);
+        $and_ob->orSearch(array($ob));
+
+        $ob = new Horde_Imap_Client_Search_Query();
+        $ob->headerText('bcc', $this->_data->t, $this->_data->n);
+        $and_ob->orSearch(array($ob));
+
+        $queryob->andSearch(array($and_ob));
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        return sprintf("Recipients (To/Cc/Bcc) for '%s'", ($this->_data->n ? _("not") . ' ' : '') . $this->_data->t);
+    }
+
+}
diff --git a/imp/lib/Search/Element/Size.php b/imp/lib/Search/Element/Size.php
new file mode 100644 (file)
index 0000000..41f67f5
--- /dev/null
@@ -0,0 +1,68 @@
+<?php
+/**
+ * This class handles size-related search queries.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Size extends IMP_Search_Element
+{
+    /**
+     * Allow NOT search on this element?
+     *
+     * @var boolean
+     */
+    public $not = false;
+
+    /**
+     * Constructor.
+     *
+     * @param integer $size    The size (in bytes).
+     * @param boolean $larger  Search for messages larger than $size?
+     */
+    public function __construct($size, $larger = false)
+    {
+        /* Data element:
+         * l = (integer) Larger if non-zero, smaller if zero.
+         * s = (integer) Size (in bytes). */
+        $this->_data = new stdClass;
+        $this->_data->s = intval($size);
+        $this->_data->l = intval($larger);
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $queryob->size($this->_data->s, $this->_data->l);
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        $label = $this->_data->l
+            ? _("Size (KB) >")
+            : _("Size (KB) <");
+
+        return $label . ' ' . ($rule->v / 1024);
+    }
+
+}
diff --git a/imp/lib/Search/Element/Text.php b/imp/lib/Search/Element/Text.php
new file mode 100644 (file)
index 0000000..58b9e46
--- /dev/null
@@ -0,0 +1,65 @@
+<?php
+/**
+ * This class handles text-related search queries.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Text extends IMP_Search_Element
+{
+    /**
+     * Constructor.
+     *
+     * @param string $text      The search text.
+     * @param string $bodyonly  If true, only search in the body of the
+     *                          message. If false, also search in the headers.
+     * @param boolean $not      If true, do a 'NOT' search of $text.
+     */
+    public function __construct($text, $bodyonly = true, $not = false)
+    {
+        /* Data element:
+         * b = (integer) Search in body only?
+         * n = (integer) Do a NOT search?
+         * t = (string) The search text. */
+        $this->_data = new stdClass;
+        $this->_data->b = intval($bodyonly);
+        $this->_data->n = intval($not);
+        $this->_data->t = $text;
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        $queryob->text($this->_data->t, $this->_data->b, $this->_data->n);
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        $label = $this->_data->b
+            ? _("Message Body")
+            : _("Entire Message (including Headers)");
+
+        return sprintf("%s for '%s'", $label, ((!empty($this->_data->n)) ? _("not") . ' ' : '') . $this->_data->t);
+    }
+
+}
diff --git a/imp/lib/Search/Element/Within.php b/imp/lib/Search/Element/Within.php
new file mode 100644 (file)
index 0000000..771efaa
--- /dev/null
@@ -0,0 +1,101 @@
+<?php
+/**
+ * This class handles within (date) search queries.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Element_Within extends IMP_Search_Element
+{
+    /* Interval types. */
+    const INTERVAL_DAYS = 1;
+    const INTERVAL_MONTHS = 2;
+    const INTERVAL_YEARS = 3;
+
+    /**
+     * Constructor.
+     *
+     * @param integer $interval  Interval value.
+     * @param integer $type      Interval type. Either:
+     * <pre>
+     * IMP_Search_Element_Within::INTERVAL_DAYS
+     * IMP_Search_Element_Within::INTERVAL_MONTHS
+     * IMP_Search_Element_Within::INTERVAL_YEARS
+     * </pre>
+     * @param boolean $older     Do an older search?
+     */
+    public function __construct($interval, $type, $older = true)
+    {
+        /* Data element:
+         * o = (integer) Do an older search?
+         * t = (integer) Interval type.
+         * v = (integer) Interval value. */
+        $this->_data = new stdClass;
+        $this->_data->o = intval($older);
+        $this->_data->t = $type;
+        $this->_data->v = $interval;
+    }
+
+    /**
+     * Adds the current query item to the query object.
+     *
+     * @param Horde_Imap_Client_Search_Query  The query object.
+     *
+     * @return Horde_Imap_Client_Search_Query  The query object.
+     */
+    public function createQuery($queryob)
+    {
+        /* Limited to day granularity because that is the technical
+         * limit for IMAP servers without 'WITHIN' extension. */
+        $secs = $this->_data->v * 60 * 60 * 24;
+        switch ($this->_data->t) {
+        case self::INTERVAL_YEARS:
+            $secs *= 365;
+            break;
+
+        case self::INTERVAL_MONTHS:
+            $secs *= 30;
+            break;
+        }
+
+        $queryob->intervalSearch($secs, $this->_data->o ? Horde_Imap_Client_Search_Query::INTERVAL_OLDER : Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER);
+
+        return $queryob;
+    }
+
+    /**
+     * Return search query text representation.
+     *
+     * @return array  The textual description of this search element.
+     */
+    public function queryText()
+    {
+        $label = $this->_data->o
+            ? _("Older Than")
+            : _("Younger Than");
+
+        switch ($this->_data->t) {
+        case self::INTERVAL_YEARS:
+            $term = _("years");
+            break;
+
+        case self::INTERVAL_MONTHS:
+            $term = _("months");
+            break;
+
+        case self::INTERVAL_DAYS:
+            $term = _("days");
+            break;
+        }
+
+        return sprintf("%s %u %s", $label, $this->_data->v, $term);
+    }
+
+}
diff --git a/imp/lib/Search/Query.php b/imp/lib/Search/Query.php
new file mode 100644 (file)
index 0000000..d36c0ad
--- /dev/null
@@ -0,0 +1,249 @@
+<?php
+/**
+ * This class provides a data structure for a search query.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Query implements Serializable
+{
+    /* Serialized version. */
+    const VERSION = 1;
+
+    /**
+     * Is this query enabled?
+     *
+     * @var boolean
+     */
+    public $enabled = true;
+
+    /**
+     * Can this query be edited?
+     *
+     * @var boolean
+     */
+    protected $_canEdit = true;
+
+    /**
+     * The search criteria (IMP_Search_Element objects).
+     *
+     * @var array
+     */
+    protected $_criteria = array();
+
+    /**
+     * The search ID.
+     *
+     * @var string
+     */
+    protected $_id;
+
+    /**
+     * The virtual folder label.
+     *
+     * @var string
+     */
+    protected $_label;
+
+    /**
+     * The mailbox list.
+     *
+     * @var array
+     */
+    protected $_mboxes = array();
+
+    /**
+     * List of serialize entries not to save.
+     *
+     * @var array
+     */
+    protected $_nosave = array();
+
+    /**
+     * Constructor.
+     *
+     * @var array $opts  Options:
+     * <pre>
+     * 'add' - (array) A list of criteria to add (Horde_Search_Element
+     *         objects).
+     *         DEFAULT: No criteria explicitly added.
+     * 'disable' - (boolean) Disable this query?
+     *             DEFAULT: false
+     * 'id' - (string) Use this ID.
+     *        DEFAULT: ID automatically generated.
+     * 'label' - (string) The label for this query.
+     *           DEFAULT: Search Results
+     * 'mboxes' - (array) The list of mailboxes to search.
+     *            DEFAULT: None
+     * </pre>
+     */
+    public function __construct(array $opts = array())
+    {
+        $this->enabled = empty($opts['disable']);
+        if (isset($opts['add'])) {
+            foreach ($opts['add'] as $val) {
+                $this->add($val);
+            }
+        }
+
+        $this->_id = isset($opts['id'])
+            ? $opts['id']
+            : strval(new Horde_Support_Randomid());
+
+        $this->_label = isset($opts['label'])
+            ? $opts['label']
+            : _("Search Results");
+
+        if (isset($opts['mboxes'])) {
+            $this->_mboxes = $opts['mboxes'];
+        }
+    }
+
+    /**
+     * Get object properties.
+     *
+     * @param string $name  Available properties:
+     * <pre>
+     * 'canEdit' - (boolean)
+     * 'id' - (string)
+     * 'label' - (string)
+     * 'mboxes' - (array)
+     * 'mid' - (string)
+     * 'query' - (Horde_Imap_Client_Search_Query)
+     * 'querytext' - (string)
+     * </pre>
+     *
+     * @return mixed  Property value.
+     */
+    public function __get($name)
+    {
+        switch ($name) {
+        case 'canEdit':
+            return $this->_canEdit;
+
+        case 'criteria':
+            $out = array();
+            foreach ($this->_criteria as $elt) {
+                $out[] = array(
+                    'criteria' => $elt->getCriteria(),
+                    'element' => get_class($elt)
+                );
+            }
+            return $out;
+
+        case 'id':
+            return $this->_id;
+
+        case 'label':
+            return $this->_label;
+
+        case 'mboxes':
+            return $this->_mboxes;
+
+        case 'mid':
+            return IMP_Search::MBOX_PREFIX . $this->_id;
+
+        case 'query':
+            $query = new Horde_Imap_Client_Search_Query();
+            foreach ($this->_criteria as $elt) {
+                $query = $elt->createQuery($query);
+            }
+            return $query;
+
+        case 'querytext':
+            $text = array(_("Search"));
+
+            foreach ($this->_criteria as $elt) {
+                $text[] = $elt->queryText();
+                if (!($elt instanceof IMP_Search_Element_Or)) {
+                    $text[] = _("and");
+                }
+            }
+            array_pop($text);
+
+            return implode(' ', $text) . ' ' . _("in") . ' [' . implode(', ', $this->mboxes) . ']';
+        }
+    }
+
+    /**
+     * String representation of this object: the mailbox ID.
+     *
+     * @return string  Mailbox ID.
+     */
+    public function __toString()
+    {
+        return $this->mid;
+    }
+
+    /**
+     * Add a search query element.
+     *
+     * @param IMP_Search_Element $elt  The search element to add.
+     */
+    public function add(IMP_Search_Element $elt)
+    {
+        $this->_criteria[] = $elt;
+    }
+
+    /* Serializable methods. */
+
+    /**
+     * Serialization.
+     *
+     * @return string  Serialized data.
+     */
+    public function serialize()
+    {
+        $data = array_filter(array(
+            'c' => $this->_criteria,
+            'e' => intval($this->enabled),
+            'i' => $this->_id,
+            'l' => $this->_label,
+            'm' => $this->_mboxes,
+            'v' => self::VERSION
+        ));
+
+        foreach ($this->_nosave as $val) {
+            unset($data[$val]);
+        }
+
+        return serialize($data);
+    }
+
+    /**
+     * Unserialization.
+     *
+     * @param string $data  Serialized data.
+     *
+     * @throws Exception
+     */
+    public function unserialize($data)
+    {
+        $data = unserialize($data);
+        if (!is_array($data) ||
+            !isset($data['v']) ||
+            ($data['v'] != self::VERSION)) {
+            throw new Exception('Cache version change');
+        }
+
+        if (isset($data['c'])) {
+            $this->_criteria = $data['c'];
+        }
+        $this->enabled = !empty($data['e']);
+        if (isset($data['i'])) {
+            $this->_id = $data['i'];
+        }
+        if (isset($data['l'])) {
+            $this->_label = $data['l'];
+        }
+        $this->_mboxes = $data['m'];
+    }
+
+}
diff --git a/imp/lib/Search/Vfolder.php b/imp/lib/Search/Vfolder.php
new file mode 100644 (file)
index 0000000..88b08de
--- /dev/null
@@ -0,0 +1,24 @@
+<?php
+/**
+ * This class provides a data structure for storing a virtual folder.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Vfolder extends IMP_Search_Query
+{
+    /**
+     * Display this virtual folder in the preferences screen?
+     *
+     * @var boolean
+     */
+    public $prefDisplay = true;
+
+}
diff --git a/imp/lib/Search/Vfolder/Vinbox.php b/imp/lib/Search/Vfolder/Vinbox.php
new file mode 100644 (file)
index 0000000..c42c6f0
--- /dev/null
@@ -0,0 +1,92 @@
+<?php
+/**
+ * This class provides a data structure for storing the virtual inbox.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Vfolder_Vinbox extends IMP_Search_Vfolder
+{
+    /**
+     * Can this query be edited?
+     *
+     * @var boolean
+     */
+    protected $_canEdit = false;
+
+    /**
+     * List of serialize entries not to save.
+     *
+     * @var array
+     */
+    protected $_nosave = array('i', 'l', 'm');
+
+    /**
+     * Constructor.
+     *
+     * The 'add', 'id', 'label', and 'mboxes' parameters are not honored.
+     *
+     * @see parent::__construct()
+     */
+    public function __construct(array $opts = array())
+    {
+        $this->enabled = empty($opts['disable']);
+
+        $this->add(new IMP_Search_Element_Flag(
+            '\\seen',
+            false
+        ));
+        $this->add(new IMP_Search_Element_Flag(
+            '\\deleted',
+            false
+        ));
+
+        $this->_init();
+    }
+
+    /**
+     * Initialization tasks.
+     */
+    protected function _init()
+    {
+        $this->_id = 'vinbox';
+        $this->_label = _("Virtual Inbox");
+    }
+
+    /**
+     * Get object properties.
+     * Only create mailbox list on demand.
+     *
+     * @see parent::__get()
+     */
+    public function __get($name)
+    {
+        switch ($name) {
+        case 'mboxes':
+            return $GLOBALS['injector']->getInstance('IMP_Imap_Tree')->getPollList(true, true);
+        }
+
+        return parent::__get($name);
+    }
+
+    /**
+     * Unserialization.
+     *
+     * @param string $data  Serialized data.
+     *
+     * @throws Exception
+     */
+    public function unserialize($data)
+    {
+        parent::unserialize($data);
+        $this->_init();
+    }
+
+}
diff --git a/imp/lib/Search/Vfolder/Vtrash.php b/imp/lib/Search/Vfolder/Vtrash.php
new file mode 100644 (file)
index 0000000..e9a343b
--- /dev/null
@@ -0,0 +1,95 @@
+<?php
+/**
+ * This class provides a data structure for storing the virtual trash.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Vfolder_Vtrash extends IMP_Search_Vfolder
+{
+    /**
+     * Can this query be edited?
+     *
+     * @var boolean
+     */
+    public $canEdit = false;
+
+    /**
+     * Display this virtual folder in the preferences screen?
+     *
+     * @var boolean
+     */
+    public $prefDisplay = false;
+
+    /**
+     * List of serialize entries not to save.
+     *
+     * @var array
+     */
+    protected $_nosave = array('i', 'l', 'm');
+
+    /**
+     * Constructor.
+     *
+     * The 'add', 'id', 'label', and 'mboxes' parameters are not honored.
+     *
+     * @see parent::__construct()
+     */
+    public function __construct(array $opts = array())
+    {
+        $this->enabled = empty($opts['disable']);
+
+        $this->add(new IMP_Search_Element_Flag(
+            '\\deleted',
+            true
+        ));
+
+        $this->_init();
+    }
+
+    /**
+     * Initialization tasks.
+     */
+    protected function _init()
+    {
+        $this->_id = 'vtrash';
+        $this->_label = _("Virtual Trash");
+    }
+
+    /**
+     * Get object properties.
+     * Only create mailbox list on demand.
+     *
+     * @see parent::__get()
+     */
+    public function __get($name)
+    {
+        switch ($name) {
+        case 'mboxes':
+            return array_keys(iterator_to_array($GLOBALS['injector']->getInstance('IMP_Imap_Tree')));
+        }
+
+        return parent::__get($name);
+    }
+
+    /**
+     * Unserialization.
+     *
+     * @param string $data  Serialized data.
+     *
+     * @throws Exception
+     */
+    public function unserialize($data)
+    {
+        parent::unserialize($data);
+        $this->_init();
+    }
+
+}
index 59afc56..8aa95ca 100644 (file)
@@ -80,6 +80,7 @@ class IMP_Tree_Flist extends Horde_Tree_Select
 
         $this->_buildIndents($this->_root_nodes);
 
+        $filter = $injector->createInstance('Horde_Text_Filter');
         $t = $injector->createInstance('Horde_Template');
         $t->setOption('gettext', true);
 
@@ -100,16 +101,18 @@ class IMP_Tree_Flist extends Horde_Tree_Select
         /* Virtual folders. */
         if ($this->getOption('inc_vfolder')) {
             $imp_search = $injector->getInstance('IMP_Search');
-            $vfolders = $imp_search->listQueries(IMP_Search::LIST_VFOLDER);
-            if (!empty($vfolders)) {
-                $vfolder_list = array();
-                foreach ($vfolders as $id => $val) {
-                    $vfolder_list[] = array(
-                        'l' => $injector->getInstance('Horde_Text_Filter')->filter($val, 'space2html', array('encode' => true)),
-                        'sel' => (IMP::$mailbox == $id),
-                        'v' => IMP::formMbox($imp_search->createSearchId($id), true)
-                    );
-                }
+            $vfolder_list = array();
+
+            $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER);
+            foreach ($imp_search as $val) {
+                $vfolder_list[] = array(
+                    'l' => $filter->filter($val->label, 'space2html', array('encode' => true)),
+                    'sel' => (IMP::$mailbox == strval($val)),
+                    'v' => IMP::formMbox(strval($val), true)
+                );
+            }
+
+            if (!empty($vfolder_list)) {
                 $t->set('vfolder', $vfolder_list);
             }
         }
@@ -124,7 +127,7 @@ class IMP_Tree_Flist extends Horde_Tree_Select
                     $tasklist_list = array();
                     foreach ($tasklists as $id => $tasklist) {
                         $tasklist_list[] = array(
-                            'l' => $injector->getInstance('Horde_Text_Filter')->filter($tasklist->get('name'), 'space2html', array('encode' => true)),
+                            'l' => $filter->filter($tasklist->get('name'), 'space2html', array('encode' => true)),
                             'v' => IMP::formMbox(IMP::TASKLIST_EDIT . $id, true)
                         );
                     }
@@ -143,7 +146,7 @@ class IMP_Tree_Flist extends Horde_Tree_Select
                     $notepad_list[] = array();
                     foreach ($notepads as $id => $notepad) {
                         $notepad_list[] = array(
-                            'l' => $injector->getInstance('Horde_Text_Filter')->filter($notepad->get('name'), 'space2html', array('encode' => true)),
+                            'l' => $filter->filter($notepad->get('name'), 'space2html', array('encode' => true)),
                             'v' => IMP::formMbox(IMP::NOTEPAD_EDIT . $id, true)
                         );
                     }
diff --git a/imp/lib/Ui/Search.php b/imp/lib/Ui/Search.php
deleted file mode 100644 (file)
index 586972b..0000000
+++ /dev/null
@@ -1,166 +0,0 @@
-<?php
-/**
- * The IMP_Ui_Search:: class is designed to provide a place to store common
- * code shared among IMP's various UI views for the search page.
- *
- * Copyright 2009-2010 The Horde Project (http://www.horde.org/)
- *
- * See the enclosed file COPYING for license information (GPL). If you
- * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
- *
- * @author   Michael Slusarz <slusarz@horde.org>
- * @category Horde
- * @license  http://www.fsf.org/copyleft/gpl.html GPL
- * @package  IMP
- */
-class IMP_Ui_Search
-{
-    /**
-     * Creates a search query.
-     *
-     * @param array $search  The list of search criteria.
-     *
-     * @return object  A search object (Horde_Imap_Client_Search_Query).
-     */
-    public function createQuery($search)
-    {
-        $query = new Horde_Imap_Client_Search_Query();
-
-        $search_array = array();
-        $search_fields = $GLOBALS['injector']->getInstance('IMP_Search')->searchFields();
-        $flag_fields = $GLOBALS['injector']->getInstance('IMP_Search')->flagFields();
-        $imp_flags = $GLOBALS['injector']->getInstance('IMP_Imap_Flags');
-        $or_search = false;
-
-        foreach ($search as $rule) {
-            $ob = new Horde_Imap_Client_Search_Query();
-
-            $type = isset($search_fields[$rule->t]['type'])
-                ? $search_fields[$rule->t]['type']
-                : $rule->t;
-
-            switch ($type) {
-            case 'or':
-                $query->orSearch($search_array);
-                $search_array = array();
-                $or_search = true;
-                break;
-
-            case 'flag':
-                if (isset($flag_fields[$rule->v])) {
-                    $val = $imp_flags->parseFormId($rule->t);
-                    $ob->flag($val['flag'], $val['set']);
-                    $search_array[] = $ob;
-                }
-                break;
-
-            case 'header':
-                if (!empty($rule->v)) {
-                    $ob->headerText($rule->t, $rule->v, !empty($rule->n));
-                    $search_array[] = $ob;
-                }
-                break;
-
-            case 'customhdr':
-                if (!empty($rule->v)) {
-                    $ob->headerText($rule->v->h, $rule->v->s, !empty($rule->n));
-                    $search_array[] = $ob;
-                }
-                break;
-
-            case 'body':
-            case 'text':
-                if (!empty($rule->v)) {
-                    $ob->text($rule->v, $search_fields[$rule->t]['type'] == 'body', !empty($rule->n));
-                    $search_array[] = $ob;
-                }
-                break;
-
-            case 'date':
-                if (!empty($rule->v)) {
-                    $date = new Horde_Date($rule->v);
-                    $ob->dateSearch($date, ($rule->t == 'date_on') ? Horde_Imap_Client_Search_Query::DATE_ON : (($rule->t == 'date_until') ? Horde_Imap_Client_Search_Query::DATE_BEFORE : Horde_Imap_Client_Search_Query::DATE_SINCE));
-                    $search_array[] = $ob;
-                }
-                break;
-
-            case 'within':
-                /* Limited to day granularity because that is the technical
-                 * limit for IMAP servers without 'WITHIN' extension. */
-                if (!empty($rule->v)) {
-                    $secs = $rule->v->v * 60 * 60 * 24;
-
-                    switch ($rule->v->l) {
-                    case 'y':
-                        $secs *= 365;
-                        break;
-
-                    case 'm':
-                        $secs *= 30;
-                        break;
-                    }
-
-                    $ob->intervalSearch($secs, $rule->t == 'older' ? Horde_Imap_Client_Search_Query::INTERVAL_OLDER : Horde_Imap_Client_Search_Query::INTERVAL_YOUNGER);
-                    $search_array[] = $ob;
-                }
-                break;
-
-            case 'size':
-                if (!empty($rule->v)) {
-                    $ob->size(intval($rule->v), $rule->t == 'size_larger');
-                    $search_array[] = $ob;
-                }
-                break;
-            }
-        }
-
-        if ($or_search) {
-            $query->orSearch($search_array);
-        } else {
-            $query->andSearch($search_array);
-        }
-
-        return $query;
-    }
-
-    /**
-     * Create a search query from input gathered from the basic search script
-     * (imp/search-basic.php).
-     *
-     * @param string $mbox      The mailbox to search.
-     * @param string $criteria  The criteria to search.
-     * @param string $text      The criteria text.
-     * @param boolean $not      Should the criteria search be a not search?
-     * @param string $flag      A flag to search for.
-     *
-     * @return string  The search query ID.
-     */
-    public function processBasicSearch($mbox, $criteria, $text, $not, $flag)
-    {
-        $c_list = array();
-
-        if ($criteria) {
-            $search_fields = $GLOBALS['injector']->getInstance('IMP_Search')->searchFields();
-            $tmp = new stdClass;
-            $tmp->t = $criteria;
-            $tmp->v = ($search_fields[$criteria]['type'] == 'size')
-                ? floatval($text) * 1024
-                : $text;
-            if ($search_fields[$criteria]['not']) {
-                $tmp->n = (bool)$not;
-            }
-            $c_list[] = $tmp;
-        }
-
-        if ($flag) {
-            $tmp = new stdClass;
-            $tmp->t = 'flag';
-            $tmp->v = $flag;
-            $c_list[] = $tmp;
-        }
-
-        /* Set the search in the IMP session. */
-        return $GLOBALS['injector']->getInstance('IMP_Search')->createSearchQuery($this->createQuery($c_list), array($mbox), $c_list, _("Search Results"), IMP_Search::MBOX_PREFIX . IMP_Search::BASIC_SEARCH);
-    }
-
-}
index 9bb269a..d12249c 100644 (file)
@@ -31,14 +31,13 @@ class IMP_Views_ListMessages
         /* Check for quicksearch request. */
         if (strlen($args['qsearchmbox'])) {
             /* Create the search query. */
-            $query = new Horde_Imap_Client_Search_Query();
+            $c_list = array();
 
             if (strlen($args['qsearchflag'])) {
-                $query->flag($args['qsearchflag'], empty($args['qsearchflagnot']));
-                $tmp = new stdClass;
-                $tmp->t = 'flag';
-                $tmp->v = $args['qsearchflag'];
-                $criteria = array($tmp);
+                $c_list[] = new IMP_Search_Element_Flag(
+                    $args['qsearchflag'],
+                    empty($args['qsearchflagnot'])
+                );
 
                 $is_search = true;
             } elseif (strlen($args['qsearch'])) {
@@ -46,66 +45,26 @@ class IMP_Views_ListMessages
                 $is_search = true;
 
                 switch ($field) {
+                case 'all':
                 case 'body':
-                    $query->text($args['qsearch'], true);
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'body';
-                    $tmp->v = $args['qsearch'];
-                    $criteria = array($tmp);
+                    $c_list[] = new IMP_Search_Element_Text(
+                        $args['qsearch'],
+                        ($field == 'body')
+                    );
                     break;
 
                 case 'from':
                 case 'subject':
-                    $query->headerText($field, $args['qsearch']);
-
-                    $tmp = new stdClass;
-                    $tmp->t = $field;
-                    $tmp->v = $args['qsearch'];
-                    $criteria = array($tmp);
-                    break;
-
-                case 'to':
-                    $query2 = new Horde_Imap_Client_Search_Query();
-                    $query2->headerText('cc', $args['qsearch']);
-
-                    $query3 = new Horde_Imap_Client_Search_Query();
-                    $query3->headerText('bcc', $args['qsearch']);
-
-                    $query->headerText('to', $args['qsearch']);
-                    $query->orSearch(array($query2, $query3));
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'to';
-                    $tmp->v = $args['qsearch'];
-                    $criteria = array($tmp);
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'or';
-                    $criteria[] = $tmp;
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'cc';
-                    $tmp->v = $args['qsearch'];
-                    $criteria[] = $tmp;
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'or';
-                    $criteria[] = $tmp;
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'bcc';
-                    $tmp->v = $args['qsearch'];
-                    $criteria[] = $tmp;
+                    $c_list[] = new IMP_Search_Element_Header(
+                        $args['qsearch'],
+                        $field
+                    );
                     break;
 
-                case 'all':
-                    $query->text($args['qsearch'], false);
-
-                    $tmp = new stdClass;
-                    $tmp->t = 'text';
-                    $tmp->v = $args['qsearch'];
-                    $criteria = array($tmp);
+                case 'recip':
+                    $c_list[] = new IMP_Search_Element_Recipient(
+                        $args['qsearch']
+                    );
                     break;
 
                 default:
@@ -114,10 +73,16 @@ class IMP_Views_ListMessages
                 }
             }
 
-            /* Set the search in the IMP session. */
+            /* Store the search in the session. */
             if ($is_search) {
                 $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
-                $imp_search->createSearchQuery($query, array($args['qsearchmbox']), $criteria, _("Search Results"), $mbox);
+                $imp_search->createQuery(
+                    $c_list,
+                    array($args['qsearchmbox']),
+                    null,
+                    false,
+                    $mbox
+                );
             }
         } else {
             $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
@@ -183,10 +148,10 @@ class IMP_Views_ListMessages
         /* The search query may have changed. */
         if ($is_search &&
             ($args['initial'] || strlen($args['qsearchmbox']))) {
-            $md->slabel = $imp_search->searchQueryText($mbox);
+            $md->slabel = $imp_search[$mbox]->querytext;
             if ($imp_search->isVFolder($mbox)) {
                 $md->vfolder = 1;
-                if (!$imp_search->isEditableVFolder($mbox)) {
+                if (!$imp_search->isVFolder($mbox, true)) {
                     $md->noedit = 1;
                 }
             }
index 4f23024..0aecf7a 100644 (file)
@@ -112,12 +112,12 @@ case 's':
 case 'rs':
     if (!empty($vars->search) &&
         ($_SESSION['imp']['protocol'] == 'imap')) {
-        $query = new Horde_Imap_Client_Search_Query();
-        $query->text($vars->search, false);
-
         /* Create the search query and reset the global mailbox variable. */
-        $sq = $imp_search->createSearchQuery($query, array(IMP::$mailbox), array(), _("Search Results"));
-        IMP::setCurrentMailboxInfo($imp_search->createSearchId($sq));
+        $q_ob = $imp_search->createQuery(
+            array(new IMP_Search_Element_Text($vars->search, false)),
+            array(IMP::$mailbox)
+        );
+        IMP::setCurrentMailboxInfo(strval($q_ob));
 
         /* Need to re-calculate these values. */
         $readonly = $imp_imap->isReadOnly(IMP::$mailbox);
@@ -215,7 +215,7 @@ $search_mbox = $imp_search->isSearchMbox(IMP::$mailbox);
 /* Determine if we are going to show the Purge Deleted link. */
 if (!$readonly &&
     !$prefs->getValue('use_trash') &&
-    !$imp_search->isVINBOXFolder(IMP::$mailbox)) {
+    !$imp_search->isVinbox(IMP::$mailbox)) {
     $menu[] = array(_("Purge Deleted"), $mailbox->copy()->add('a', 'e'));
 }
 
@@ -247,7 +247,7 @@ if (!$search_mbox && IMP::threadSortAvailable(IMP::$mailbox)) {
 /* Add search link. */
 if ($_SESSION['imp']['protocol'] == 'imap') {
     if ($search_mbox) {
-        $orig_mbox = reset($imp_search->getSearchFolders(IMP::$mailbox));
+        $orig_mbox = reset($imp_search->getSearchMailboxes(IMP::$mailbox));
         $menu[] = array(sprintf(_("New Search in %s"), IMP::getLabel($orig_mbox)), IMP::generateIMPUrl('mailbox-mimp.php', $orig_mbox)->add('a', 's'));
     } else {
         $menu[] = array(_("Search"), $mailbox_url->copy()->add('a', 's'));
index d11dc10..c689c71 100644 (file)
@@ -50,7 +50,6 @@ try {
 $imp_search = $injector->getInstance('IMP_Search');
 $search_mbox = $imp_search->isSearchMbox(IMP::$mailbox);
 $vars = Horde_Variables::getDefaultVariables();
-$vfolder = $imp_search->isVFolder(IMP::$mailbox);
 
 /* There is a chance that this page is loaded directly via message.php. If so,
  * don't re-include config files, and the following variables will already be
@@ -229,7 +228,7 @@ $sortpref = IMP::getSort(IMP::$mailbox);
 
 /* Determine if we are going to show the Hide/Purge Deleted Message links. */
 if (!$prefs->getValue('use_trash') &&
-    !$imp_search->isVINBOXFolder(IMP::$mailbox)) {
+    !$imp_search->isVinbox(IMP::$mailbox)) {
     $showdelete = array('hide' => ($sortpref['by'] != Horde_Imap_Client::SORT_THREAD), 'purge' => true);
 } else {
     $showdelete = array('hide' => false, 'purge' => false);
@@ -309,9 +308,7 @@ if (!$preview_tooltip) {
 }
 
 $unread = $imp_mailbox->unseenMessages(Horde_Imap_Client::SORT_RESULTS_COUNT);
-$vtrash = $imp_search->isVTrashFolder(IMP::$mailbox)
-    ? $imp_search->createSearchId($search_mbox)
-    : null;
+$vtrash = $imp_search->isVTrash(IMP::$mailbox);
 
 Horde::addInlineScript(array(
     'ImpMailbox.unread = ' . intval($unread)
@@ -335,15 +332,13 @@ if ($unread) {
     $pagetitle = $title .= ' (' . $unread . ')';
 }
 
-if ($vfolder ||
-    ($search_mbox && (IMP::$mailbox != IMP_Search::BASIC_SEARCH))) {
-    $query_text = wordwrap($imp_search->searchQueryText(IMP::$mailbox));
-    if ($vfolder) {
-        $pagetitle .= ' [' . Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . _("Virtual Folder") . '</a>]';
-        $title .= ' [' . _("Virtual Folder") . ']';
-    } else {
-        $pagetitle = Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . $pagetitle . '</a>';
-    }
+if ($imp_search->isVFolder(IMP::$mailbox, true)) {
+    $query_text = wordwrap($imp_search[IMP::$mailbox]->querytext);
+    $pagetitle .= ' [' . Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . _("Virtual Folder") . '</a>]';
+    $title .= ' [' . _("Virtual Folder") . ']';
+} elseif ($imp_search->isQuery(IMP::$mailbox, true)) {
+    $query_text = wordwrap($imp_search[IMP::$mailbox]->querytext);
+    $pagetitle = Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . $pagetitle . '</a>';
 } else {
     $pagetitle = $title = htmlspecialchars($title);
 }
@@ -400,17 +395,17 @@ if ($_SESSION['imp']['protocol'] != 'pop') {
             $hdr_template->set('empty_img', Horde::img('empty_spam.png', _("Empty folder")));
         }
     } else {
-        if ($imp_search->isEditableVFolder(IMP::$mailbox)) {
+        if ($imp_search->isVFolder(IMP::$mailbox, true)) {
             $edit_search = _("Edit Virtual Folder");
-            $hdr_template->set('delete_vfolder_url', $imp_search->deleteUrl(IMP::$mailbox));
-            $hdr_template->set('delete_vfolder_img', Horde::img('delete.png', _("Delete Virtual Folder")));
-        } elseif ($search_mbox && !isset($query_text)) {
-            /* Mini search results. */
-            $search_mailbox = reset($imp_search->getSearchFolders(IMP::$mailbox));
-            $hdr_template->set('search_url', Horde::url('search-basic.php')->add('search_mailbox', $search_mailbox));
-            $hdr_template->set('searchclose', IMP::generateIMPUrl('mailbox.php', $search_mailbox));
-        } elseif (!$vfolder) {
-            $edit_search = _("Edit Search Query");
+        } elseif ($imp_search->isQuery(IMP::$mailbox)) {
+            if ($imp_search->isQuery(IMP::$mailbox, true)) {
+                $edit_search = _("Edit Search Query");
+            } else {
+                /* Basic search results. */
+                $search_mailbox = reset($imp_search->getSearchMailboxes(IMP::$mailbox));
+                $hdr_template->set('search_url', Horde::url('search-basic.php')->add('search_mailbox', $search_mailbox));
+                $hdr_template->set('searchclose', IMP::generateIMPUrl('mailbox.php', $search_mailbox));
+            }
         }
 
         if (isset($edit_search)) {
@@ -420,6 +415,7 @@ if ($_SESSION['imp']['protocol'] != 'pop') {
         }
     }
 }
+
 /* Generate mailbox summary string. */
 if (empty($pageOb['end'])) {
     $hdr_template->set('msgcount', _("No Messages"));
@@ -495,7 +491,7 @@ if ($pageOb['msgcount']) {
     /* Prepare the actions template. */
     $a_template = $injector->createInstance('Horde_Template');
     if (!$readonly) {
-        $del_class = ($use_trash && ((IMP::$mailbox == (IMP::folderPref($prefs->getValue('trash_folder'), true))) || !is_null($vtrash)))
+        $del_class = ($use_trash && ($vtrash || (IMP::$mailbox == (IMP::folderPref($prefs->getValue('trash_folder'), true)))))
             ? 'permdeleteAction'
             : 'deleteAction';
         $a_template->set('delete', Horde::widget('#', _("Delete"), 'widget ' . $del_class, '', '', _("_Delete")));
index 57bfd80..f81cd80 100644 (file)
@@ -51,7 +51,7 @@ $imp_search = $injector->getInstance('IMP_Search');
 
 /* Obtain some information describing the mailbox state. */
 $total_num = count($imp_mailbox);
-$unseen_num = ($imp_search->isVINBOXFolder($mailbox))
+$unseen_num = ($imp_search->isVinbox($mailbox))
     ? $total_num
     : $imp_mailbox->unseenMessages(Horde_Imap_Client::SORT_RESULTS_COUNT);
 
@@ -59,7 +59,7 @@ $query = new Horde_Imap_Client_Search_Query();
 if ($new_mail) {
     $query->flag('\\seen', false);
 }
-$ids = $imp_search->runSearchQuery($query, $mailbox, Horde_Imap_Client::SORT_ARRIVAL, 1);
+$ids = $imp_search->runQuery($query, $mailbox, Horde_Imap_Client::SORT_ARRIVAL, 1);
 
 if (count($ids)) {
     $imp_ui = new IMP_Ui_Mailbox(IMP::$mailbox);
index dde491e..00f4629 100644 (file)
@@ -33,29 +33,65 @@ $imp_search = $injector->getInstance('IMP_Search');
 $vars = Horde_Variables::getDefaultVariables();
 
 /* If search_basic_mbox is set, we are processing the search query. */
-if ($vars->search_basic_mailbox) {
-    $imp_ui_search = new IMP_Ui_Search();
-    $id = $imp_ui_search->processBasicSearch($vars->search_mailbox, $vars->search_criteria, $vars->search_criteria_text, $vars->search_criteria_not, $vars->search_flags);
+if ($vars->search_basic_mbox) {
+    $c_list = array();
 
-    /* Redirect to the mailbox screen. */
-    Horde::url('mailbox.php', true)->add('mailbox', $imp_search->createSearchId($id))->redirect();
-}
+    if ($vars->search_criteria_text) {
+        switch ($vars->search_criteria) {
+        case 'from':
+        case 'subject':
+            $c_list[] = new IMP_Search_Element_Header(
+                $vars->search_criteria_text,
+                $vars->search_criteria,
+                $vars->search_criteria_not
+            );
+            break;
 
-$f_fields = $s_fields = array();
+        case 'recip':
+            $c_list[] = new IMP_Search_Element_Recipient(
+                $vars->search_criteria_text,
+                $vars->search_criteria_not
+            );
+            break;
+
+        case 'body':
+        case 'text':
+            $c_list[] = new IMP_Search_Element_Text(
+                $vars->search_criteria_text,
+                ($vars->search_criteria == 'body'),
+                $vars->search_criteria_not
+            );
+        break;
+        }
+    }
 
-foreach ($imp_search->searchFields() as $key => $val) {
-    if (!in_array($val['type'], array('customhdr', 'date', 'within'))) {
-        $s_fields[] = array(
-            'val' => $key,
-            'label' => $val['label']
+    if ($vars->search_criteria_flag) {
+        $formdata = $injector->getInstance('IMP_Imap_Flags')->parseFormId($vars->search_criteria_flag);
+        $c_list[] = new IMP_Search_Element_Flag(
+            $formdata['flag'],
+            ($formdata['set'] && !$vars->search_criteria_flag_not)
         );
     }
+
+    /* Store the search in the session. */
+    $q_ob = $imp_search->createQuery(
+        $c_list,
+        array($vars->search_basic_mbox),
+        null,
+        IMP_Search::CREATE_QUERY,
+        IMP_Search::BASIC_SEARCH
+    );
+
+    /* Redirect to the mailbox screen. */
+    Horde::url('mailbox.php', true)->add('mailbox', strval($q_ob))->redirect();
 }
 
-foreach ($imp_search->flagFields() as $key => $val) {
-    $f_fields[] = array(
-        'val' => $key,
-        'label' => $val
+$flist = $injector->getInstance('IMP_Imap_Flags')->getFlagList($vars->search_mailbox);
+$flag_set = array();
+foreach ($flist['set'] as $val) {
+    $flag_set[] = array(
+        'val' => $val['f'],
+        'label' => $val['l']
     );
 }
 
@@ -66,8 +102,7 @@ $t->setOption('gettext', true);
 $t->set('action', Horde::url('search-basic.php'));
 $t->set('mbox', htmlspecialchars($vars->search_mailbox));
 $t->set('search_title', sprintf(_("Search %s"), htmlspecialchars(IMP::displayFolder($vars->search_mailbox))));
-$t->set('s_fields', $s_fields);
-$t->set('f_fields', $f_fields);
+$t->set('flist', $flag_set);
 
 $title = _("Search");
 $menu = IMP::menu();
index 0fa2abd..a4f099d 100644 (file)
 require_once dirname(__FILE__) . '/lib/Application.php';
 Horde_Registry::appInit('imp');
 
-/* Load basic search if javascript is not enabled or searching is not
- * allowed (basic page will do the required redirection in the latter case). */
+/* Define the criteria list. */
+$criteria = array(
+    'from' => array(
+        'label' => _("From"),
+        'type' => 'header'
+    ),
+    'recip' => array(
+        'label' => _("Recipients (To/Cc/Bcc)"),
+        'type' => 'header'
+    ),
+    'to' => array(
+        'label' => _("To"),
+        'type' => 'header'
+    ),
+    'cc' => array(
+        'label' => _("Cc"),
+        'type' => 'header'
+    ),
+    'bcc' => array(
+        'label' => _("Bcc"),
+        'type' => 'header'
+    ),
+    'subject' => array(
+        'label' => _("Subject"),
+        'type' => 'header'
+    ),
+    'customhdr' => array(
+        'label' => _("Custom Header"),
+        'type' => 'customhdr'
+    ),
+    'body' => array(
+        'label' => _("Body"),
+        'type' => 'text'
+    ),
+    'text' => array(
+        'label' => _("Entire Message"),
+        'type' => 'text'
+    ),
+    'date_on' => array(
+        'label' => _("Date Equals (=)"),
+        'type' => 'date'
+    ),
+    'date_until' => array(
+        'label' => _("Date Until (<)"),
+        'type' => 'date'
+    ),
+    'date_since' => array(
+        'label' => _("Date Since (>=)"),
+        'type' => 'date'
+    ),
+    'older' => array(
+        'label' => _("Older Than"),
+        'type' => 'within'
+    ),
+    'younger' => array(
+        'label' => _("Younger Than"),
+        'type' => 'within'
+    ),
+    // Displayed in KB, but stored internally in bytes
+    'size_smaller' => array(
+        'label' => _("Size (KB) <"),
+        'type' => 'size'
+    ),
+    // Displayed in KB, but stored internally in bytes
+    'size_larger' => array(
+        'label' => _("Size (KB) >"),
+        'type' => 'size'
+    ),
+);
+
+/* Define some constants. */
+$constants = array(
+    'date' => array(
+        'date_on' => IMP_Search_Element_Date::DATE_ON,
+        'date_until' => IMP_Search_Element_Date::DATE_BEFORE,
+        'date_since' => IMP_Search_Element_Date::DATE_SINCE
+    ),
+    'within' => array(
+        'd' => IMP_Search_Element_Within::INTERVAL_DAYS,
+        'm' => IMP_Search_Element_Within::INTERVAL_MONTHS,
+        'y' => IMP_Search_Element_Within::INTERVAL_YEARS
+    )
+);
+
+/* Load basic search if javascript is not enabled or searching is not allowed
+ * (basic page will do the required redirection in the latter case). */
 if (!$browser->hasFeature('javascript') ||
     ($_SESSION['imp']['protocol'] == 'pop')) {
     require IMP_BASE . '/search-basic.php';
     exit;
 }
 
+$imp_flags = $injector->getInstance('IMP_Imap_Flags');
 $imp_search = $injector->getInstance('IMP_Search');
 $vars = Horde_Variables::getDefaultVariables();
 
-$charset = $registry->getCharset();
 $dimp_view = ($_SESSION['imp']['view'] == 'dimp');
-$js_load = array();
-$search_fields = $imp_search->searchFields();
+$js_vars = array();
 $search_mailbox = isset($vars->search_mailbox)
     ? $vars->search_mailbox
     : 'INBOX';
 
+$flist = $imp_flags->getFlagList($search_mailbox);
+
 /* Generate the search query if 'criteria_form' is present in the form
  * data. */
 if ($vars->criteria_form) {
     $criteria = Horde_Serialize::unserialize($vars->criteria_form, Horde_Serialize::JSON);
+    $c_list = array();
+
+    foreach ($criteria as $val) {
+        switch ($val->t) {
+        case 'from':
+        case 'to':
+        case 'cc':
+        case 'bcc':
+        case 'subject':
+            $c_list[] = new IMP_Search_Element_Header(
+                $val->v,
+                $val->t,
+                $val->n
+            );
+            break;
+
+        case 'recip':
+            $c_list[] = new IMP_Search_Element_Recipient(
+                $val->v,
+                $val->n
+            );
+            break;
+
+        case 'customhdr':
+            $c_list[] = new IMP_Search_Element_Header(
+                $val->v->s,
+                $val->v->h,
+                $val->n
+            );
+            break;
+
+        case 'body':
+        case 'text':
+            $c_list[] = new IMP_Search_Element_Text(
+                $val->v,
+                ($val->t == 'body'),
+                $val->n
+            );
+            break;
+
+        case 'date_on':
+        case 'date_until':
+        case 'date_since':
+            $c_list[] = new IMP_Search_Element_Date(
+                new DateTime($val->v),
+                $constants['date'][$val->t]
+            );
+            break;
+
+        case 'older':
+        case 'younger':
+            $c_list[] = new IMP_Search_Element_Within(
+                $val->v,
+                $constants['within'][$val->l]
+                ($val->t == 'older')
+            );
+            break;
+
+        case 'size_smaller':
+        case 'size_larger':
+            $c_list[] = new IMP_Search_Element_Size(
+                $val->v,
+                ($val->t == 'size_larger')
+            );
+            break;
+
+        case 'or':
+            $c_list[] = new IMP_Search_Element_Or();
+            break;
 
-    /* Create the search query. */
-    $imp_ui_search = new IMP_Ui_Search();
-    $query = $imp_ui_search->createQuery($criteria);
+        case 'flag':
+            /* Flag search. */
+            $formdata = $imp_flags->parseFormId($val->v);
+            $c_list[] = new IMP_Search_Element_Flag(
+                $formdata['flag'],
+                ($formdata['set'] && !$val->n)
+            );
+            break;
+        }
+    }
 
     /* Save the search if requested. */
     if ($vars->search_save) {
         switch ($vars->search_type) {
         case 'vfolder':
-            $id = $imp_search->addVFolder($query, $vars->folder_list, $criteria, $vars->search_label, $vars->edit_query_vfolder);
+            $q_ob = $imp_search->createQuery(
+                $c_list,
+                $vars->folder_list,
+                $vars->search_label,
+                IMP_Search::CREATE_VFOLDER,
+                IMP::formMbox($vars->edit_query_vfolder, false)
+            );
+
+            if ($vars->edit_query_vfolder) {
+                $notification->push(sprintf(_("Virtual Folder \"%s\" edited successfully."), $vars->search_label), 'horde.success');
+                if ($dimp_view) {
+                    IMP_Dimp::returnToDimp(strval($q_ob));
+                }
+                Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
+                exit;
+            }
+
             $notification->push(sprintf(_("Virtual Folder \"%s\" created succesfully."), $vars->search_label), 'horde.success');
             break;
         }
     } else {
         /* Set the search in the session. */
-        $id = $imp_search->createSearchQuery($query, $vars->folder_list, $criteria, _("Search Results"));
+        $q_ob = $imp_search->createQuery(
+            $c_list,
+            $vars->folder_list
+        );
     }
 
     /* Redirect to the mailbox page. */
-    $id = $imp_search->createSearchId($id);
     if ($dimp_view) {
-        /* Output javascript code to close the IFRAME and load the search
-         * mailbox in DIMP. */
-        print '<html><head>' .
-            Horde::wrapInlineScript(array('window.parent.DimpBase.go(' . Horde_Serialize::serialize('folder:' . $id, Horde_Serialize::JSON, $charset) . ')')) .
-            '</head></html>';
-        exit;
+        IMP_Dimp::returnToDimp(strval($q_ob));
     }
 
-    Horde::url('mailbox.php', true)->add('mailbox', $id)->redirect();
-}
-
-/* Generate master folder list. */
-$tree = $injector->getInstance('IMP_Imap_Tree')->createTree('imp_search', array(
-    'checkbox' => true,
-));
-
-/* Process list of recent searches. */
-$recent_searches = $imp_search->listQueries(IMP_Search::LIST_SEARCH | IMP_Search::NO_BASIC_SEARCH, false);
-if (!empty($recent_searches)) {
-    $rs = array();
-    foreach ($recent_searches as $key => $val) {
-        $rs[$key] = array(
-            'c' => $imp_search->getCriteria($key),
-            'l' => Horde_String::truncate($val),
-            'v' => $key
-        );
-    }
-    $js_load[] = 'ImpSearch.updateRecentSearches(' . Horde_Serialize::serialize($rs, Horde_Serialize::JSON, $charset) . ')';
+    Horde::url('mailbox.php', true)->add('mailbox', strval($q_ob))->redirect();
+    exit;
 }
 
 /* Preselect mailboxes. */
-$js_load[] = 'ImpSearch.updateSelectedFolders(' . Horde_Serialize::serialize(array($search_mailbox), Horde_Serialize::JSON, $charset) . ')';
+$js_vars['ImpSearch.selected'] = array($search_mailbox);
 
 /* Prepare the search template. */
 $t = $injector->createInstance('Horde_Template');
@@ -114,81 +270,103 @@ $t->setOption('gettext', true);
 $t->set('action', Horde::url('search.php'));
 $t->set('virtualfolder', $_SESSION['imp']['protocol'] != 'pop');
 
-/* Determine if we are editing a current search folder. */
+/* Determine if we are editing a search query. */
 if ($vars->edit_query && $imp_search->isSearchMbox($vars->edit_query)) {
-    if ($imp_search->isVFolder($vars->edit_query)) {
-        if (!$imp_search->isEditableVFolder($vars->edit_query)) {
+    $q_ob = $imp_search[$vars->edit_query];
+    if ($imp_search->isVFolder($q_ob)) {
+        if (!$imp_search->isVFolder($q_ob, true)) {
             $notification->push(_("Special Virtual Folders cannot be edited."), 'horde.error');
-            Horde::url('mailbox.php', true)->redirect();
+            Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
         }
-        $t->set('edit_query_vfolder', htmlspecialchars($vars->edit_query));
+        $t->set('edit_query_vfolder', IMP::formMbox($q_ob, true));
+        $t->set('search_label', htmlspecialchars($q_ob->label));
+
+        $js_vars['ImpSearch.prefsurl'] = strval(Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->setRaw(true));
+    }
+    $js_vars['ImpSearch.i_criteria'] = $q_ob->criteria;
+} else {
+    /* Process list of recent searches. */
+    $rs = array();
+    $imp_search->setIteratorFilter(IMP_Search::LIST_SEARCH);
+    foreach ($imp_search as $val) {
+        $rs[$val->id] = array(
+            'c' => $val->criteria,
+            'l' => Horde_String::truncate($val->querytext),
+            'v' => $val->id
+        );
     }
-    $js_load[] = 'ImpSearch.updateSearchCriteria(' . Horde_Serialize::serialize($imp_search->getCriteria($vars->edit_query), Horde_Serialize::JSON, $charset) . ')';
-    $js_load[] = 'ImpSearch.updateSavedSearches(' . Horde_Serialize::serialize($imp_search->getLabel($vars->edit_query), Horde_Serialize::JSON, $charset) . ')';
-}
 
-$f_fields = $s_fields = $types = array();
+    if (!empty($rs)) {
+        $js_vars['ImpSearch.recent'] = $rs;
+    }
+}
 
-/* Process the list of fields. */
-foreach ($search_fields as $key => $val) {
-    $s_fields[] = array(
+$c_list = $types = array();
+foreach ($criteria as $key => $val) {
+    $c_list[] = array(
         'val' => $key,
-        'label' => $val['label']
+        'label' => htmlspecialchars($val['label'])
     );
     $types[$key] = $val['type'];
 }
-$t->set('s_fields', $s_fields);
+$t->set('clist', $c_list);
 
-foreach ($imp_search->flagFields() as $key => $val) {
-    $f_fields[] = array(
-        'val' => $key,
-        'label' => $val
+/* Create the flag_list. */
+$flag_set = array();
+foreach ($flist['set'] as $val) {
+    $flag_set[] = array(
+        'val' => rawurlencode($val['f']),
+        'label' => htmlspecialchars($val['l'])
     );
-    $types[$key] = 'flag';
+    $types[rawurlencode($val['f'])] = 'flag';
 }
-$t->set('f_fields', $f_fields);
-$t->set('tree', $tree->getTree());
+$t->set('flist', $flag_set);
 
-Horde_Core_Ui_JsCalendar::init();
-
-/* Gettext strings for this page. */
-$gettext_strings = array(
-    'and' => _("and"),
-    'customhdr' => _("Custom Header:"),
-    'dateselection' => _("Date Selection"),
-    'flag' => _("Flag:"),
-    'loading' => _("Loading..."),
-    'need_criteria' => _("Please select at least one search criteria."),
-    'need_folder' => _("Please select at least one folder to search."),
-    'need_label' => _("Saved searches require a label."),
-    'not_match' => _("Do NOT Match"),
-    'or' => _("OR"),
-    'search_term' => _("Search Term:")
-);
-
-/* Javascript data for this page. */
-$js_data = array(
-    'months' => Horde_Core_Ui_JsCalendar::months(),
-    'searchmbox' => $search_mailbox,
-    'types' => $types
-);
-
-Horde::addInlineJsVars(array(
-    'ImpSearch.data' => $js_data,
-    'ImpSearch.text' => $gettext_strings,
+/* Generate master folder list. */
+$tree = $injector->getInstance('IMP_Imap_Tree')->createTree('imp_search', array(
+    'checkbox' => true,
 ));
-Horde::addInlineScript($js_load, 'dom');
+$t->set('tree', $tree->getTree());
 
-$title = _("Search");
+Horde_Core_Ui_JsCalendar::init();
 Horde::addScriptFile('horde.js', 'horde');
 Horde::addScriptFile('stripe.js', 'horde');
 Horde::addScriptFile('search.js', 'imp');
 
+Horde::addInlineJsVars(array_merge($js_vars, array(
+    /* Javascript data for this page. */
+    'ImpSearch.data' => array(
+        'constants' => $constants,
+        'dimp' => $dimp_view,
+        'months' => Horde_Core_Ui_JsCalendar::months(),
+        'searchmbox' => $search_mailbox,
+        'types' => $types
+    ),
+    /* Gettext strings for this page. */
+    'ImpSearch.text' => array(
+        'and' => _("and"),
+        'customhdr' => _("Custom Header:"),
+        'dateselection' => _("Date Selection"),
+        'flag' => _("Flag:"),
+        'loading' => _("Loading..."),
+        'need_criteria' => _("Please select at least one search criteria."),
+        'need_folder' => _("Please select at least one folder to search."),
+        'need_label' => _("Saved searches require a label."),
+        'not_match' => _("Do NOT Match"),
+        'or' => _("OR"),
+        'search_term' => _("Search Term:")
+    )
+)), false, 'dom');
+
 if ($dimp_view) {
-    $t->set('return_mailbox_val', sprintf(_("Return to %s"), htmlspecialchars($search_mailbox)));
+    if (!$vars->edit_query) {
+        $t->set('return_mailbox_val', sprintf(_("Return to %s"), htmlspecialchars(IMP::displayFolder($search_mailbox))));
+    }
 } else {
     $menu = IMP::menu();
 }
+
+$title = _("Search");
 require IMP_TEMPLATES . '/common-header.inc';
 if (!$dimp_view) {
     echo $menu;
index 0669133..ae5fb58 100644 (file)
@@ -73,7 +73,7 @@ function _simpleButton($id, $text, $image, $nodisplay = false)
     <?php echo _simpleButton('appportal', _("_Portal"), 'hordeIcon') ?>
 <?php endif; ?>
 <?php if (Horde_Menu::showService('prefs')): ?>
-    <?php echo _simpleButton('appoptions', _("_Options"), 'prefsIcon') ?>
+    <?php echo _simpleButton('appprefs', _("Preferences"), 'prefsIcon') ?>
 <?php endif; ?>
 <?php if (Horde_Menu::showService('logout')): ?>
     <?php echo _simpleButton('applogout', _("_Log Out"), 'logoutIcon') ?>
@@ -384,6 +384,10 @@ function _simpleButton($id, $text, $image, $nodisplay = false)
  <div><?php echo _("No actions available") ?></div>
 </div>
 
+<div class="context" id="ctx_vcontainer" style="display:none">
+ <a id="ctx_vcontainer_edit"><span class="contextImg"></span><?php echo _("Edit Virtual Folders") ?></a>
+</div>
+
 <div class="context" id="ctx_vfolder" style="display:none">
  <a id="ctx_vfolder_edit"><span class="contextImg"></span><?php echo _("Edit Virtual Folder") ?></a>
  <a id="ctx_vfolder_delete"><span class="contextImg"></span><?php echo _("Delete Virtual Folder") ?></a>
@@ -505,7 +509,7 @@ function _simpleButton($id, $text, $image, $nodisplay = false)
  <a id="ctx_qsearchby_all"><?php echo _("Entire Message") ?></a>
  <a id="ctx_qsearchby_body"><?php echo _("Body") ?></a>
  <a id="ctx_qsearchby_from"><?php echo _("From") ?></a>
- <a id="ctx_qsearchby_to"><?php echo _("To") ?></a>
+ <a id="ctx_qsearchby_recip"><?php echo _("Recipients (To/Cc/Bcc)") ?></a>
  <a id="ctx_qsearchby_subject"><?php echo _("Subject") ?></a>
 </div>
 <?php endif; ?>
index 2b75095..035ace4 100644 (file)
@@ -28,9 +28,6 @@
 <if:empty>
   <a href="<tag:empty />" id="empty_mailbox" title="<gettext>Empty Mailbox</gettext>"><tag:empty_img /></a>
 </if:empty>
-<if:delete_vfolder_url>
-  <a href="<tag:delete_vfolder_url />" id="delete_vfolder" title="<gettext>Delete Virtual Folder</gettext>"><tag:delete_vfolder_img /></a>
-</if:delete_vfolder_url>
  </div>
  <div class="clear"></div>
 </div>
index f9a701f..d37dc13 100644 (file)
    <select name="search_criteria">
     <option value=""><gettext>None</gettext></option>
     <option value="" disabled="disabled">- - - - - - - - - -</option>
-<loop:s_fields>
-    <option value="<tag:s_fields.val />"><tag:s_fields.label /></option>
-</loop:s_fields>
+    <option value="from"><gettext>From</gettext></option>
+    <option value="recip"><gettext>Recipients (To/Cc/Bcc)</gettext></option>
+    <option value="subject"><gettext>Subject</gettext></option>
+    <option value="body"><gettext>Message Body</gettext></option>
+    <option value="text"><gettext>Entire Message</gettext></option>
    </select>
-   <span<if:hide_criteria> style="display:none"</if:hide_criteria>>
-    <input type="text" name="search_criteria_text" size="30" />
-    <input type="checkbox" class="checkbox" name="search_criteria_not" />
-    <label for="search_criteria_not"><gettext>Do NOT Match</gettext></label>
-   </span>
+   <input type="text" name="search_criteria_text" size="30" />
+   <input type="checkbox" class="checkbox" name="search_criteria_not" />
+   <label for="search_criteria_not"><gettext>Do NOT Match</gettext></label>
   </td>
  </tr>
  <tr>
   <td class="searchUILabel"><gettext>Search Flags:</gettext></td>
   <td>
-   <select name="search_flags">
+   <select name="search_criteria_flag">
     <option value=""><gettext>None</gettext></option>
     <option value="" disabled="disabled">- - - - - - - - - -</option>
-<loop:f_fields>
-    <option value="<tag:f_fields.val />"><tag:f_fields.label /></option>
-</loop:f_fields>
+<loop:flist>
+    <option value="<tag:flist.val />"><tag:flist.label /></option>
+</loop:flist>
    </select>
+   <input type="checkbox" class="checkbox" name="search_criteria_flag_not" />
+   <label for="search_criteria_flag_not"><gettext>Do NOT Match</gettext></label>
   </td>
  </tr>
 <if:advsearch>
index d96539f..c41bb66 100644 (file)
     <option value="" disabled="disabled">- - - - - - - - -</option>
     <option value="or" style="display:none"><gettext>Add OR clause</gettext></option>
     <option value="" disabled="disabled" style="display:none">- - - - - - - - -</option>
-<loop:s_fields>
-    <option value="<tag:s_fields.val />"><tag:s_fields.label /></option>
-</loop:s_fields>
+<loop:clist>
+    <option value="<tag:clist.val />"><tag:clist.label /></option>
+</loop:clist>
     <option value="" disabled="disabled">- - - - - - - - -</option>
-<loop:f_fields>
-    <option value="<tag:f_fields.val />"><tag:f_fields.label /></option>
-</loop:f_fields>
+<loop:flist>
+    <option value="<tag:flist.val />"><tag:flist.label /></option>
+</loop:flist>
    </select>
   </div>
  </div>
@@ -48,8 +48,8 @@
   <span class="searchuiImg arrowCollapsed"></span>
   <gettext>Search Folders</gettext>
   <span class="searchuiFoldersActions" style="display:none">
-   <a id="link_sel_all" href="#"><gettext>Select all</gettext></a> |
-   <a id="link_sel_none" href="#"><gettext>Select none</gettext></a>
+   <a id="link_sel_all" href="#"><gettext>Select all</gettext></a> |
+   <a id="link_sel_none" href="#"><gettext>Select none</gettext></a> ]
   </span>
  </div>
 
@@ -66,7 +66,9 @@
 
  <div style="display:none">
 <if:edit_query_vfolder>
-  <input type="hidden" name="edit_query_vfolder" value="" />
+  <input type="hidden" name="search_save" id="search_save" value="1" />
+  <input type="hidden" name="search_type" value="vfolder" />
+  <input type="hidden" name="edit_query_vfolder" value="<tag:edit_query_vfolder />" />
 <else:edit_query_vfolder>
   <div class="item">
    <input type="checkbox" class="checkbox" id="search_save" name="search_save" /> <label for="search_save"><gettext>Save search?</gettext></label>
   </div>
 </else:edit_query_vfolder></if:edit_query_vfolder>
   <div class="item">
-   <label for="search_label"><gettext>Label:</gettext></label> <input type="text" name="search_label" id="search_label" />
+   <label for="search_label"><gettext>Label:</gettext></label> <input type="text" name="search_label" id="search_label"<if:search_label> value="<tag:search_label />"</if:search_label> />
   </div>
  </div>
 </if:virtualfolder>
 
  <div class="searchuiButtons">
-  <input type="button" id="search_submit" class="button" value="<if:edit_query_vfolder><gettext>Save</gettext><else:edit_query_vfolder><gettext>Submit</gettext></else:edit_query_vfolder></if:edit_query_vfolder>" />
+<if:edit_query_vfolder>
+  <input type="button" id="search_submit" class="button" value="<gettext>Save</gettext>" />
+  <input type="button" id="search_edit_query_cancel" class="button" value="<gettext>Cancel</gettext>" />
+<else:edit_query_vfolder>
+  <input type="button" id="search_submit" class="button" value="<gettext>Submit</gettext>" />
   <input type="button" id="search_reset" class="button" value="<gettext>Reset</gettext>" />
+</else:edit_query_vfolder></if:edit_query_vfolder>
 <if:return_mailbox_val>
   <input type="button" id="search_dimp_return" class="button" value="<tag:return_mailbox_val />" />
 </if:return_mailbox_val>
diff --git a/imp/templates/prefs/searches.html b/imp/templates/prefs/searches.html
new file mode 100644 (file)
index 0000000..98d8501
--- /dev/null
@@ -0,0 +1,43 @@
+<input type="hidden" name="searches_action" id="searches_action" />
+<input type="hidden" name="searches_data" id="searches_data" />
+<table class="searchesmanagement">
+ <thead>
+  <tr>
+   <td><gettext>Virtual Folder</gettext></td>
+   <td><gettext>Enabled?</gettext></td>
+   <td><gettext>Actions</gettext></td>
+  </tr>
+ </thead>
+ <tbody>
+<loop:vfolders>
+  <tr>
+   <td>
+<if:vfolders.enabled>
+<if:vfolders.m_url>
+    <tag:vfolders.m_url /><tag:vfolders.label /></a>
+<else:vfolders.m_url>
+    <span class="vfolderenabled"><tag:vfolders.label /></span>
+</else:vfolders.m_url></if:vfolders.m_url>
+<else:vfolders.enabled>
+    <tag:vfolders.label />
+</else:vfolders.enabled></if:vfolders.enabled>
+   </td>
+   <td class="enabled">
+    <input class="checkbox" type="checkbox" name="enable_<tag:vfolders.key />"<if:vfolders.enabled> checked="checked"</if:vfolders.enabled> />
+   </td>
+   <td>
+<if:vfolders.edit>
+    <a class="vfolderedit" href="<tag:vfolders.edit />"><span class="editImg"></span></a>
+    <a class="vfolderdelete" href="#"><span class="deleteImg"></span></a>
+<else:vfolders.edit>
+     <gettext>No Actions Available</gettext>
+</else:vfolders.edit></if:vfolders.edit>
+   </td>
+  </tr>
+  <tr>
+   <td colspan="3" class="fixed vfolderdescription">
+    <tag:vfolders.description />
+   </td>
+</loop:vfolders>
+ </tbody>
+</table>
index 5994d69..8e54cff 100644 (file)
@@ -5,7 +5,9 @@
  <div>
   <select id="trash" name="trash">
    <option value="<tag:nofolder />"><gettext>None</gettext></option>
+<if:vtrash>
    <option value="<tag:vtrash />"<if:vtrash_select> selected="selected"</if:vtrash_select>><gettext>Use Virtual Trash</gettext></option>
+</if:vtrash>
    <tag:special_use />
    <tag:flist />
   </select>
index aa01cd1..b94051e 100644 (file)
@@ -973,7 +973,7 @@ span.dimpactionEditsearch {
 #ctx_folder_create span.contextImg, #ctx_container_create span.contextImg, #ctx_folderopts_new span.contextImg {
     background-image: url("../graphics/folders/create.png");
 }
-#ctx_folder_rename span.contextImg, #ctx_container_rename span.contextImg, #ctx_vfolder_edit span.contextImg {
+#ctx_folder_rename span.contextImg, #ctx_container_rename span.contextImg, #ctx_vcontainer_edit span.contextImg, #ctx_vfolder_edit span.contextImg {
     background-image: url("../graphics/folders/edit.png");
 }
 #ctx_folder_delete span.contextImg, #ctx_vfolder_delete span.contextImg {
index d099233..51bb6b0 100644 (file)
@@ -371,34 +371,44 @@ div.msgflags.flagDeleted, span.contextImg.flagDeleted {
     background-image: url("graphics/mail_deleted.png");
 }
 
-/* Accounts/flag management (prefs) styles. */
-table.accountsmanagement, table.flagmanagement {
+/* Prefs management styles. */
+table.accountsmanagement, table.flagmanagement, table.searchesmanagement {
     padding-bottom: 10px;
 }
-table.accountsmanagement td, table.flagmanagement td {
+table.accountsmanagement td, table.flagmanagement td, table.searchesmanagement td {
     padding-right: 12px;
 }
-table.accountsmanagement thead td, table.flagmanagement thead td {
+table.accountsmanagement thead td, table.flagmanagement thead td, table.searchesmanagement thead td {
     font-weight: bold;
     text-decoration: underline;
 }
 
-table.flagmanagement div.flagUser {
-    border: 1px solid gray;
-    margin-right: 2px;
-}
-
 table.accountsmanagement td.required, .accountsNotSecure {
     color: red;
 }
+table.accountsmanagement td.noneconfigured {
+    font-style: italic;
+}
 .accountsSecure {
     color: green;
 }
+
+table.flagmanagement div.flagUser {
+    border: 1px solid gray;
+    margin-right: 2px;
+}
 table.flagmanagement tbody td.flagicon {
     text-align: center;
 }
-table.accountsmanagement td.noneconfigured {
-    font-style: italic;
+
+table.searchesmanagement td.vfolderdescription {
+    background-color: #ffa;
+    width: 500px;
+}
+table.searchesmanagement .vfolderenabled {
+    color: blue;
+    cursor: pointer;
+    text-decoration: underline;
 }
 
 /* Prefs styles. */
@@ -597,7 +607,7 @@ div.mimeStatusMessage, div.mimePartInfo {
 }
 
 /* Other images. */
-.downloadAtc, .downloadZipAtc, .saveImgAtc, .printAtc, .closeImg, .deleteImg, .folderImg, .foldersImg, .searchuiImg, .reloadImg {
+.downloadAtc, .downloadZipAtc, .saveImgAtc, .printAtc, .closeImg, .deleteImg, .folderImg, .foldersImg, .searchuiImg, .reloadImg, .editImg {
     display: -moz-inline-stack;
     display: inline-block;
     height: 16px;
@@ -631,6 +641,9 @@ div.mimeStatusMessage, div.mimePartInfo {
 .reloadImg {
     background-image: url("graphics/reload.png");
 }
+.editImg {
+    background-image: url("graphics/edit.png");
+}
 
 /* iTip styles. */
 #itipconflicts {
index 502f2da..4f4f0e5 100644 (file)
@@ -124,7 +124,7 @@ span.dimpactionEditsearch {
 #ctx_folder_create span.contextImg, #ctx_container_create span.contextImg, #ctx_folderopts_new span.contextImg {
     background-image: url("../graphics/folders/create.png");
 }
-#ctx_folder_rename span.contextImg, #ctx_container_rename span.contextImg, #ctx_vfolder_edit span.contextImg {
+#ctx_folder_rename span.contextImg, #ctx_container_rename span.contextImg, #ctx_vcontainer_edit span.contextImg, #ctx_vfolder_edit span.contextImg {
     background-image: url("../graphics/folders/edit.png");
 }
 #ctx_folder_delete span.contextImg, #ctx_vfolder_delete span.contextImg {