Request #6875: Improved advanced search query interface
authorMichael M Slusarz <slusarz@curecanti.org>
Tue, 25 Aug 2009 20:52:44 +0000 (14:52 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Tue, 15 Sep 2009 05:52:37 +0000 (23:52 -0600)
17 files changed:
imp/docs/CHANGES
imp/folders.php
imp/js/search.js
imp/lib/Search.php
imp/lib/UI/Search.php
imp/locale/en_US/help.xml
imp/mailbox.php
imp/search-basic.php
imp/search.php
imp/templates/javascript_defs.php
imp/templates/search/search.html
imp/themes/graphics/calendar.png [new file with mode: 0644]
imp/themes/screen.css
imp/themes/silver/graphics/calendar.png [new file with mode: 0644]
imp/themes/silver/screen.css
imp/themes/tango-blue/graphics/calendar.png [new file with mode: 0644]
imp/themes/tango-blue/screen.css

index 5dc235f..84bfcad 100644 (file)
@@ -2,6 +2,7 @@
 v5.0-git
 --------
 
+[mms] Improved advanced search query interface (Request #6875).
 [mms] More intelligent ordering of autocomplete search results.
 [mms] Add a mini search query interface (Request #6875).
 [mms] Make DIMP quicksearch field selection persist across sessions.
index f43f40e..f266bca 100644 (file)
@@ -423,9 +423,9 @@ $rowct = 0;
 $morembox = $rows = array();
 foreach ($raw_rows as $val) {
     $val['nocheckbox'] = !empty($val['vfolder']);
-    if (!empty($val['vfolder']) && ($val['value'] != IMP_Imap_Tree::VFOLDER_KEY)) {
-        $val['delvfolder'] = Horde::link($imp_search->deleteURL($val['value']), _("Delete Virtual Folder")) . _("Delete") . '</a>';
-        $val['editvfolder'] = Horde::link($imp_search->editURL($val['value']), _("Edit Virtual Folder")) . _("Edit") . '</a>';
+    if (!empty($val['vfolder']) && $val['editvfolder']) {
+        $val['delvfolder'] = Horde::link($imp_search->deleteUrl($val['value']), _("Delete Virtual Folder")) . _("Delete") . '</a>';
+        $val['editvfolder'] = Horde::link($imp_search->editUrl($val['value']), _("Edit Virtual Folder")) . _("Edit") . '</a>';
     }
 
     $val['cname'] = (++$rowct % 2) ? 'item0' : 'item1';
index 0ec9103..1c91527 100644 (file)
@@ -1,5 +1,5 @@
 /**
- * Provides the javascript for the search.php script (standard view).
+ * Provides the javascript for the search.php script (advanced view).
  *
  * 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 ImpSearch = {
     // The following variables are defined in search.php:
-    //   inverse_sub, not_search, search_date
+    //   loading, months, need_criteria, need_folder, types
+    criteria: {},
+    saved_searches: {},
+    show_unsub: false,
 
-    _toggleAll: function(checked)
+    _getAll: function()
     {
-        $('search').getInputs(null, 'search_folders[]').each(function(e) {
+        return $('search_form').getInputs(null, 'search_folders_form[]');
+    },
+
+    selectFolders: function(checked)
+    {
+        this._getAll().each(function(e) {
             e.checked = checked;
         });
     },
 
-    _dateCheck: function(field)
+    updateFolderList: function(folders)
     {
-        var m = $('search_' + field + '_month'),
-            d = $('search_' + field + '_day'),
-            y = $('search_' + field + '_year');
+        var fragment = document.createDocumentFragment(),
+            node = $($('folder_row').cloneNode(true)).writeAttribute('id', false).show();
 
-        if (m.selectedIndex == 0) {
-            m.selectedIndex = this.search_date.m;
-        }
+        folders.each(function(f) {
+            var n = $(node.cloneNode(true));
+            n.down().writeAttribute({ disabled: !Boolean(f.v), value: (f.v ? f.v.escapeHTML() : null) }).insert({ after: f.l });
+            fragment.appendChild(n);
+        });
 
-        if (d.selectedIndex == 0) {
-            d.selectedIndex = this.search_date.d;
-        }
+        $('search_folders_hdr').next('DIV').update('').appendChild(fragment);
+    },
 
-        if (y.value == "") {
-            y.value = this.search_date.y;
-        }
+    updateSavedSearches: function(searches)
+    {
+        var fragment = document.createDocumentFragment(),
+            node = new Element('OPTION');
+
+        $('recent_searches_div').show();
+
+        $H(searches).each(function(s) {
+            fragment.appendChild($(node.cloneNode(false)).writeAttribute({ value: s.value.v.escapeHTML() }).update(s.value.l.escapeHTML()));
+            this.saved_searches[s.key] = s.value.c;
+        }, this);
+
+        $('recent_searches').appendChild(fragment);
     },
 
-    _formCheck: function()
+    updateSearchCriteria: function(criteria)
     {
-        if (this.not_search &&
-            (!$('preselected_folders') || !$F('preselected_folders'))) {
-            if (!Form.getInputs('search', null, 'search_folders[]').detect(function(e) { return e.checked; })) {
-                alert(IMP.text.search_select);
-                return;
+        this.resetCriteria();
+
+        criteria.each(function(c) {
+            switch (this.types[c.t]) {
+            case 'header':
+            case 'body':
+            case 'text':
+                this.insertText(c.t, c.v, c.n);
+                break;
+
+            case 'size':
+                this.insertSize(c.t, c.v);
+                break;
+
+            case 'date':
+                this.insertDate(c.t, c.v);
+                break;
+
+            case 'flag':
+                this.insertFlag(c.t);
+                break;
+            }
+        }, this);
+    },
+
+    updateSelectedFolders: function(folders)
+    {
+        var tmp = $('search_folders_hdr').next();
+        this.selectFolders(false);
+        folders.each(function(f) {
+            var i = tmp.down('INPUT[value=' + f + ']');
+            if (i) {
+                i.checked = true;
+            }
+        });
+    },
+
+    resetCriteria: function()
+    {
+        $('search_criteria').siblings().invoke('remove');
+    },
+
+    changeHandler: function(e)
+    {
+        var elt = e.element(), val = $F(elt);
+
+        switch (elt.readAttribute('id')) {
+        case 'recent_searches':
+            this.updateSearchCriteria(this.saved_searches[$F(elt)]);
+            elt.setValue('');
+            break;
+
+        case 'search_criteria':
+            switch (this.types[val]) {
+            case 'header':
+            case 'body':
+            case 'text':
+                this.insertText(val);
+                break;
+
+            case 'size':
+                this.insertSize(val);
+                break;
+
+            case 'date':
+                this.insertDate(val);
+                break;
+
+            case 'flag':
+                this.insertFlag(val);
+                break;
             }
+            break;
         }
 
-        $('actionID').setValue('do_search');
+        e.stop();
     },
 
-    _reset: function()
+    getLabel: function(id)
     {
-        $('actionID').setValue('reset_search');
-        $('search').submit();
+        return $('search_criteria').down('[value=' + id + ']').getText() + ': ';
     },
 
-    _saveCache: function()
+    insertCriteria: function(elt)
     {
-        $('edit_query').setValue($F('save_cache'));
-        $('search').submit();
+        var div = new Element('DIV').insert(elt.writeAttribute('id', null).show()).insert($($('delete_criteria').cloneNode(true)).writeAttribute('id', null).show());
+        $('search_criteria').insert({ before: div }).setValue('');
+        return div.identify();
     },
 
-    _deleteField: function(i)
+    insertText: function(id, text, not)
     {
-        $('delete_field_id').setValue(i);
-        $('actionID').setValue('delete_field');
-        $('search').submit();
+        var tmp = $($('text_criteria').cloneNode(true));
+        tmp.down('EM').insert(this.getLabel(id));
+        tmp.down('INPUT').setValue(text);
+        if (not) {
+            tmp.down('INPUT', 1).writeAttribute('checked', true);
+        }
+        this.criteria[this.insertCriteria(tmp)] = { t: id };
     },
 
-    _showSubscribed: function(i)
+    insertSize: function(id, size)
     {
-        $('show_subscribed_only').setValue(i);
-        $('search').submit();
+        var tmp = $($('text_criteria').cloneNode(true));
+        tmp.down('EM').insert(this.getLabel(id));
+        // Convert from bytes to KB
+        tmp.down('INPUT').writeAttribute('size', 10).setValue(Object.isNumber(size) ? Math.round(size / 1024) : '');
+        tmp.down('SPAN').hide();
+        this.criteria[this.insertCriteria(tmp)] = { t: id };
     },
 
-    changeHandler: function(e)
+    insertDate: function(id, data)
     {
-        var id = e.element().readAttribute('id');
+        var d = (data ? new Date(data.y, data.m, data.d) : new Date()),
+            tmp = $($('date_criteria').cloneNode(true));
+        tmp.down('EM').insert(this.getLabel(id));
+        this.replaceDate(this.insertCriteria(tmp), id, { y: d.getFullYear(), m: d.getMonth(), d: d.getDate() });
+    },
 
-        switch (id) {
-        case 'save_cache':
-            this._saveCache();
-            break;
+    replaceDate: function(id, type, data)
+    {
+        $(id).down('SPAN', 1).update(this.months[data.m] + ' ' + data.d + ', ' + data.y);
+        // Need to store date information at all times in criteria, since we
+        // have no other way to track this information (there is not form
+        // field for this type).
+        this.criteria[id] = { t: type, v: data };
+    },
+
+    insertFlag: function(id)
+    {
+        var tmp = $($('flag_criteria').cloneNode(true)).insert({ bottom: this.getLabel(id) } );
+        this.criteria[this.insertCriteria(tmp)] = { t: id };
+    },
+
+    _submit: function()
+    {
+        var data = [], tmp;
+
+        if (!this._getAll().findAll(function(i) { return i.checked; }).size()) {
+            alert(this.need_folder);
+        } else {
+            tmp = $('search_criteria').siblings().pluck('id');
+            if (tmp.size()) {
+                tmp.each(function(c) {
+                    var tmp2;
+
+                    switch (this.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]'));
+                        data.push(this.criteria[c]);
+                        break;
+
+                    case 'size':
+                        tmp2 = Number($F($(c).down('INPUT')));
+                        if (!isNaN(tmp2)) {
+                            // Convert KB to bytes
+                            this.criteria[c].v = tmp2 * 1024;
+                            data.push(this.criteria[c]);
+                        }
+                        break;
 
-        default:
-            if (id.startsWith('field_')) {
-                $('search').submit();
-            } else if (id.startsWith('search_date_')) {
-                this._dateCheck('on');
+                    case 'date':
+                        data.push(this.criteria[c]);
+                        break;
+
+                    case 'flag':
+                        data.push({ t: 'flag', v: this.criteria[c].t });
+                        break;
+                    }
+                }, this);
+                $('criteria_form').setValue(data.toJSON());
+                $('search_form').submit();
+            } else {
+                alert(this.need_criteria);
             }
-            break;
         }
     },
 
@@ -98,40 +242,71 @@ var ImpSearch = {
             return;
         }
 
-        var elt = e.element();
+        var id, tmp,
+            elt = e.element();
 
         while (Object.isElement(elt)) {
-            if (elt.hasClassName('searchSubmit')) {
-                this._formCheck();
-            } else if (elt.hasClassName('searchReset')) {
-                this._reset();
-            } else if (elt.hasClassName('searchDelete')) {
-                this._deleteField(elt.readAttribute('fid'));
-            } else {
-                switch (elt.readAttribute('id')) {
-                case 'link_sel_all':
-                    this._toggleAll(true);
-                    break;
-
-                case 'link_sel_none':
-                    this._toggleAll(false);
-                    break;
-
-                case 'link_sub':
-                    this._showSubscribed(this.inverse_sub);
-                    break;
-
-                case 'search_match_and':
-                case 'search_match_or':
-                    if ($('field_1')) {
-                        $('search').submit();
+            id = elt.readAttribute('id');
+
+            switch (id) {
+            case 'search_submit':
+                this._submit();
+                e.stop();
+                return;
+
+            case 'search_reset':
+                this.resetCriteria();
+                this.selectFolders(false);
+                return;
+
+            case 'link_sel_all':
+            case 'link_sel_none':
+                this.selectFolders(id == 'link_sel_all');
+                e.stop();
+                return;
+
+            case 'link_sub':
+                tmp = this._getAll();
+                this.show_unsub = !this.show_unsub;
+                $('search_folders_hdr').next('DIV').update(this.loading);
+                new Ajax.Request($('search_form').readAttribute('action'), {
+                    parameters: { show_unsub: Number(this.show_unsub) },
+                    onComplete: this._showFoldersCallback.bind(this, tmp)
+                });
+                elt.childElements().invoke('toggle');
+                e.stop();
+                return;
+
+            default:
+                if (elt.hasClassName('arrowExpanded') ||
+                    elt.hasClassName('arrowCollapsed')) {
+                    elt.up().down().toggle().next().toggle().up().next().toggle();
+                    if (elt.descendantOf('search_folders_hdr')) {
+                        elt.next('SPAN.item').toggle();
                     }
-                    break;
+                } else if (elt.hasClassName('searchuiDelete')) {
+                    tmp = elt.up('DIV');
+                    delete this.criteria[tmp.identify()];
+                    tmp.remove();
+                    e.stop();
+                    return;
+                } else if (elt.hasClassName('searchuiCalendar')) {
+                    tmp = this.criteria[elt.up('DIV').identify()];
+                    Horde_Calendar.open(elt.identify(), { y: tmp.v.y, m: tmp.v.m + 1, d: tmp.v.d }, this.replaceDate.bind(this, elt.up('DIV').identify(), tmp.t));
+                    e.stop();
+                    return;
                 }
+                break;
             }
 
             elt = elt.up();
         }
+    },
+
+    _showFoldersCallback: function(flist, r)
+    {
+        this.updateFolderList(r.responseJSON);
+        this.updateSelectedFolders(flist);
     }
 
 };
index ef4405d..da1db54 100644 (file)
@@ -8,16 +8,12 @@
  *
  * $_SESSION['imp']['search'] = array(
  *     'id_1' => array(
- *         'query' => Horde_Imap_Client_Search_Query object (serialized),
- *         'folders' => array (List of folders to search),
- *         'uiinfo' => array (Info used by search.php to render page.
- *                            For virtual folders, this data is stored
- *                            in the preferences),
- *         'label' => string (Description of search),
- *         'vfolder' => boolean (True if this is a Virtual Folder)
- *     ),
- *     'id_2' => array(
- *         ....
+ *         'c' => (array) List of search criteria. For virtual folders, this
+ *                data is stored in the preferences,
+ *         'f' => (array) List of folders to search,
+ *         'l' => (string) Description of search,
+ *         'q' => (Horde_Imap_Client_Search_Query) [serialized],
+ *         'v' => (boolean) True if this is a Virtual Folder
  *     ),
  *     ....
  * );
@@ -35,9 +31,13 @@ class IMP_Search
     /* The mailbox search prefix. */
     const MBOX_PREFIX = 'impsearch\0';
 
+    /* The basic search mailbox name. */
+    const BASIC_SEARCH = 'impbsearch';
+
     /* Bitmask constants for listQueries(). */
     const LIST_SEARCH = 1;
     const LIST_VFOLDER = 2;
+    const NO_BASIC_SEARCH = 4;
 
     /**
      * The ID of the current search query in use.
@@ -76,7 +76,7 @@ class IMP_Search
     }
 
     /**
-     * Initialize the class.
+     * Initialize search data for a session.
      *
      * @param boolean $no_vf  Don't readd the Virtual Folders.
      */
@@ -85,10 +85,10 @@ class IMP_Search
         if (!$no_vf) {
             $imaptree = IMP_Imap_Tree::singleton();
             foreach ($this->_getVFolderList() as $key => $val) {
-                if (!empty($val['vfolder']) &&
+                if (!empty($val['v']) &&
                     !$this->isEditableVFolder($key)) {
-                    $imaptree->insertVFolders(array($key => $val['label']));
-                    unset($val['uiinfo']);
+                    $imaptree->insertVFolders(array($key => $val['l']));
+                    unset($val['c']);
                     $_SESSION['imp']['search'][$key] = $val;
                 }
             }
@@ -98,6 +98,99 @@ class IMP_Search
     }
 
     /**
+     * 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
+            ),
+            'body' => array(
+               'label' => _("Body"),
+               'type' => 'body',
+                'not' => true
+            ),
+            'text' => array(
+               'label' => _("Entire Message"),
+               'type' => 'text',
+                'not' => true
+            ),
+            'date_on' => array(
+                'label' => _("Date ="),
+                'type' => 'date',
+                'not' => true
+            ),
+            'date_until' => array(
+                'label' => _("Date <"),
+                'type' => 'date',
+                'not' => true
+            ),
+            'date_since' => array(
+                'label' => _("Date >="),
+                'type' => 'date',
+                '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()
+    {
+        $imp_flags = IMP_Imap_Flags::singleton();
+        $flist = $imp_flags->getFlagList(null);
+
+        $flags = array();
+
+        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;
+    }
+
+    /**
      * Run a search.
      *
      * @param object $ob  An optional search query to add (via 'AND') to the
@@ -120,7 +213,7 @@ class IMP_Search
         $search = &$_SESSION['imp']['search'][$id];
 
         /* Prepare the search query. */
-        $query = unserialize($search['query']);
+        $query = unserialize($search['q']);
         if (!empty($ob)) {
             $query->andSearch(array($ob));
         }
@@ -131,7 +224,7 @@ class IMP_Search
             $sortpref['by'] = Horde_Imap_Client::SORT_DATE;
         }
 
-        foreach ($search['folders'] as $val) {
+        foreach ($search['f'] as $val) {
             $results = $GLOBALS['imp_imap']->ob()->search($val, $query, array('reverse' => $sortpref['dir'], 'sort' => array($sortpref['by'])));
             foreach ($results['sort'] as $val2) {
                 $sorted[] = $val2 . IMP::IDX_SEP . $val;
@@ -146,10 +239,11 @@ class IMP_Search
      * queries with custom sorts to be used without affecting cached
      * mailboxes.
      *
-     * @param object $query    The search query.
-     * @param string $mailbox  The mailbox to search.
-     * @param integer $sortby  The sort criteria.
-     * @param integer $sortdir The sort directory.
+     * @param object $query     The search query object
+     *                          (Horde_Imap_Client_Search_Query).
+     * @param string $mailbox   The mailbox to search.
+     * @param integer $sortby   The sort criteria.
+     * @param integer $sortdir  The sort directory.
      *
      * @return array  The sorted list.
      */
@@ -167,28 +261,29 @@ class IMP_Search
     /**
      * 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 $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 query id to use (or else one is
-     *                        automatically generated).
+     * @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 string $label    The label to use for the search results.
+     * @param string $id       The query id to use (or else one is
+     *                         automatically generated).
      *
      * @return string  Returns the search query id.
      */
-    public function createSearchQuery($query, $folders, $search, $label,
+    public function createSearchQuery($query, $folders, $criteria, $label,
                                       $id = null)
     {
         $id = is_null($id) ? uniqid(mt_rand()) : $this->_strip($id);
+
         $_SESSION['imp']['search'][$id] = array(
-            'query' => serialize($query),
-            'folders' => $folders,
-            'uiinfo' => $search,
-            'label' => $label,
-            'vfolder' => false
+            'c' => $criteria,
+            'f' => $folders,
+            'l' => $label,
+            'q' => serialize($query),
+            'v' => false
         );
+
         return $id;
     }
 
@@ -204,13 +299,14 @@ class IMP_Search
     public function deleteSearchQuery($id = null, $no_delete = false)
     {
         $id = $this->_strip($id);
-        $is_vfolder = !empty($_SESSION['imp']['search'][$id]['vfolder']);
+        $is_vfolder = $this->isVFolder($id);
         unset($_SESSION['imp']['search'][$id]);
 
         if ($is_vfolder) {
             $vfolders = $this->_getVFolderList();
             unset($vfolders[$id]);
             $this->_saveVFolderList($vfolders);
+
             if (!$no_delete) {
                 $imaptree = IMP_Imap_Tree::singleton();
                 $imaptree->delete($id);
@@ -219,23 +315,23 @@ class IMP_Search
     }
 
     /**
-     * Retrieves the previously stored search UI information.
+     * Retrieves the previously stored search criteria information.
      *
      * @param string $id  The search query id to use (by default, will use
      *                    the current ID set in the object).
      *
      * @return array  The array necessary to rebuild the search UI page.
      */
-    public function retrieveUIQuery($id = null)
+    public function getCriteria($id = null)
     {
         $id = $this->_strip($id);
-        if (isset($_SESSION['imp']['search'][$id]['uiinfo'])) {
-            return $_SESSION['imp']['search'][$id]['uiinfo'];
+        if (isset($_SESSION['imp']['search'][$id]['c'])) {
+            return $_SESSION['imp']['search'][$id]['c'];
         }
 
         if ($this->isVFolder($id)) {
             $vlist = $this->_getVFolderList();
-            return $vlist[$id]['uiinfo'];
+            return $vlist[$id]['c'];
         }
 
         return array();
@@ -252,8 +348,8 @@ class IMP_Search
     public function getLabel($id = null)
     {
         $id = $this->_strip($id);
-        return isset($_SESSION['imp']['search'][$id]['label'])
-            ? $_SESSION['imp']['search'][$id]['label']
+        return isset($_SESSION['imp']['search'][$id]['l'])
+            ? $_SESSION['imp']['search'][$id]['l']
             : '';
     }
 
@@ -264,22 +360,18 @@ class IMP_Search
      */
     protected function _getVFolderList()
     {
-        if (!is_null(self::$_vfolder)) {
-            return self::$_vfolder;
-        }
-
-        $vfolder = $GLOBALS['prefs']->getValue('vfolder');
-        if (!empty($vfolder)) {
-            $vfolder = @unserialize($vfolder);
-        }
+        if (is_null(self::$_vfolder)) {
+            self::$_vfolder = $GLOBALS['prefs']->getValue('vfolder');
+            if (!empty(self::$_vfolder)) {
+                self::$_vfolder = @unserialize(self::$_vfolder);
+            }
 
-        if (empty($vfolder) || !is_array($vfolder)) {
-            $vfolder = array();
+            if (empty(self::$_vfolder) || !is_array(self::$_vfolder)) {
+                self::$_vfolder = array();
+            }
         }
 
-        self::$_vfolder = $vfolder;
-
-        return $vfolder;
+        return self::$_vfolder;
     }
 
     /**
@@ -290,26 +382,27 @@ class IMP_Search
     protected function _saveVFolderList($vfolder)
     {
         $GLOBALS['prefs']->setValue('vfolder', serialize($vfolder));
-        self::$_vfolder = null;
+        self::$_vfolder = $vfolder;
     }
 
     /**
      * Add a virtual folder for the current user.
      *
-     * @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 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.
      *
      * @return string  The virtual folder ID.
      */
     public function addVFolder($query, $folders, $search, $label, $id = null)
     {
         $id = $this->createSearchQuery($query, $folders, $search, $label, $id);
-        $_SESSION['imp']['search'][$id]['vfolder'] = true;
+        $_SESSION['imp']['search'][$id]['v'] = true;
+
         if ($this->_saveVFolder) {
             $vfolders = $this->_getVFolderList();
             $vfolders[$id] = $_SESSION['imp']['search'][$id];
@@ -439,15 +532,18 @@ class IMP_Search
     public function isEditableVFolder($id = null)
     {
         $id = $this->_strip($id);
-        return ($this->isVFolder($id) && !$this->isVTrashFolder($id) && !$this->isVINBOXFolder($id));
+        return ($this->isVFolder($id) &&
+                !$this->isVTrashFolder($id) &&
+                !$this->isVINBOXFolder($id));
     }
 
     /**
      * Return a list of queryies.
      *
      * @param integer $mask   A bitmask of the query types to return.
-     *                        IMP_Search::LIST_SEARCH and/or
-     *                        IMP_Search::LIST_VFOLDER.
+     *                        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.
      *
@@ -467,9 +563,11 @@ class IMP_Search
         }
 
         foreach ($_SESSION['imp']['search'] as $key => $val) {
-            if ((($mask & self::LIST_VFOLDER) && !empty($val['vfolder'])) ||
-                (($mask & self::LIST_SEARCH) && empty($val['vfolder']))) {
-                $vfolders[$key] = $label
+            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);
             }
@@ -494,8 +592,8 @@ class IMP_Search
     public function getSearchFolders($id = null)
     {
         $id = $this->_strip($id);
-        return isset($_SESSION['imp']['search'][$id]['folders'])
-            ? $_SESSION['imp']['search'][$id]['folders']
+        return isset($_SESSION['imp']['search'][$id]['f'])
+            ? $_SESSION['imp']['search'][$id]['f']
             : array();
     }
 
@@ -514,43 +612,39 @@ class IMP_Search
         if (empty($_SESSION['imp']['search'][$id])) {
             return '';
         } elseif ($this->isVINBOXFolder($id) || $this->isVTrashFolder($id)) {
-            return $_SESSION['imp']['search'][$id]['label'];
+            return $_SESSION['imp']['search'][$id]['l'];
         }
 
-        $imp_ui_search = new IMP_UI_Search();
-
-        $flagfields = $imp_ui_search->flagFields();
-        $searchfields = $imp_ui_search->searchFields();
+        $flagfields = $this->flagFields();
+        $searchfields = $this->searchFields();
         $text = '';
-        $uiinfo = $this->retrieveUIQuery($id);
-
-        if (empty($uiinfo['field'])) {
-            return '';
-        }
+        $criteria = $this->getCriteria($id);
 
         $text = _("Search") . ' ';
         $text_array = array();
-        foreach ($uiinfo['field'] as $key2 => $val2) {
-            if (isset($flagfields[$val2])) {
-                $text_array[] = $flagfields[$val2]['label'];
+        foreach ($criteria as $rule) {
+            $field = $rule->t;
+
+            if (isset($flagfields[$field])) {
+                $text_array[] = sprintf(_("flagged \"%s\""), $flagfields[$field]);
             } else {
-                switch ($searchfields[$val2]['type']) {
-                case IMP_UI_Search::DATE:
-                    $text_array[] = sprintf("%s '%s'", $searchfields[$val2]['label'], strftime("%x", mktime(0, 0, 0, $uiinfo['date'][$key2]['month'], $uiinfo['date'][$key2]['day'], $uiinfo['date'][$key2]['year'])));
+                switch ($searchfields[$field]['type']) {
+                case 'date':
+                    $text_array[] = sprintf("%s '%s'", $searchfields[$field]['label'], strftime("%x", mktime(0, 0, 0, $rule->v->m + 1, $rule->v->d, $rule->v->y)));
                     break;
 
-                case IMP_UI_Search::SIZE:
-                    $text_array[] = $searchfields[$val2]['label'] . ' ' . ($uiinfo['text'][$key2] / 1024);
+                case 'size':
+                    $text_array[] = $searchfields[$field]['label'] . ' ' . ($rule->v / 1024);
                     break;
 
                 default:
-                    $text_array[] = sprintf("%s for '%s'", $searchfields[$val2]['label'], ((!empty($uiinfo['text_not'][$key2])) ? _("not") . ' ' : '') . $uiinfo['text'][$key2]);
+                    $text_array[] = sprintf("%s for '%s'", $searchfields[$field]['label'], ((!empty($rule->n)) ? _("not") . ' ' : '') . $rule->v);
                     break;
                 }
             }
         }
 
-        $text .= implode(' ' . (($uiinfo['match'] == 'and') ? _("and") : _("or")) . ' ', $text_array) . ' ' . _("in") . ' ' . implode(', ', $uiinfo['folders']);
+        return $text . implode(' ' . _("and") . ' ', $text_array) . ' ' . _("in") . ' ' . implode(', ', $this->getSearchFolders($id));
     }
 
     /**
@@ -561,10 +655,9 @@ class IMP_Search
      *
      * @return string  The URL to the search page.
      */
-    public function editURL($id = null)
+    public function editUrl($id = null)
     {
-        $id = $this->_strip($id);
-        return Horde_Util::addParameter(Horde::applicationUrl('search.php'), array('edit_query' => $id));
+        return Horde_Util::addParameter(Horde::applicationUrl('search.php'), array('edit_query' => $this->createSearchID($this->_strip($id))));
     }
 
     /**
@@ -575,14 +668,13 @@ class IMP_Search
      *
      * @return string  The URL to allow deletion of the search query.
      */
-    public function deleteURL($id = null)
+    public function deleteUrl($id = null)
     {
-        $id = $this->_strip($id);
-        return Horde_Util::addParameter(Horde::applicationUrl('folders.php'),
-                                  array('actionID' => 'delete_search_query',
-                                        'folders_token' => Horde::getRequestToken('imp.folders'),
-                                        'queryid' => $id,
-                                  ));
+        return Horde_Util::addParameter(Horde::applicationUrl('folders.php'), array(
+            'actionID' => 'delete_search_query',
+            'folders_token' => Horde::getRequestToken('imp.folders'),
+            'queryid' => $this->createSearchID($this->_strip($id))
+        ));
     }
 
     /**
@@ -608,7 +700,7 @@ class IMP_Search
     public function isVFolder($id = null)
     {
         $id = $this->_strip($id);
-        return !empty($_SESSION['imp']['search'][$id]['vfolder']);
+        return !empty($_SESSION['imp']['search'][$id]['v']);
     }
 
     /**
@@ -629,8 +721,8 @@ class IMP_Search
      *
      * @param string $id  The mailbox query ID.
      *
-     * @return string  The virtual folder ID, with any IMP specific identifying
-     *                 information stripped off.
+     * @return string  The virtual folder ID, with any IMP specific
+     *                 identifying information stripped off.
      */
     protected function _strip($id)
     {
index 2b097bf..9cc82e2 100644 (file)
  */
 class IMP_UI_Search
 {
-    /* Defines used to determine what kind of field query we are dealing
-     * with. */
-    const HEADER = 1;
-    const BODY = 2;
-    const DATE = 3;
-    const TEXT = 4;
-    const SIZE = 5;
-
-    /**
-     * Return the base search fields.
-     *
-     * @return array  The base search fields.
-     */
-    public function searchFields()
-    {
-        return array(
-            'from' => array(
-                'label' => _("From"),
-                'type' => self::HEADER,
-                'not' => true
-            ),
-            'to' => array(
-                'label' => _("To"),
-                'type' => self::HEADER,
-                'not' => true
-            ),
-            'cc' => array(
-                'label' => _("Cc"),
-                'type' => self::HEADER,
-                'not' => true
-            ),
-            'bcc' => array(
-                'label' => _("Bcc"),
-                'type' => self::HEADER,
-                'not' => true
-            ),
-            'subject' => array(
-                'label' => _("Subject"),
-                'type' => self::HEADER,
-                'not' => true
-            ),
-            'body' => array(
-               'label' => _("Body"),
-               'type' => self::BODY,
-                'not' => true
-            ),
-            'text' => array(
-               'label' => _("Entire Message"),
-               'type' => self::TEXT,
-                'not' => true
-            ),
-            'date_on' => array(
-                'label' => _("Date ="),
-                'type' => self::DATE,
-                'not' => true
-            ),
-            'date_until' => array(
-                'label' => _("Date <"),
-                'type' => self::DATE,
-                'not' => true
-            ),
-            'date_since' => array(
-                'label' => _("Date >="),
-                'type' => self::DATE,
-                'not' => true
-            ),
-            // Displayed in KB, but stored internally in bytes
-            'size_smaller' => array(
-                'label' => _("Size (KB) <"),
-                'type' => self::SIZE,
-                'not' => false
-            ),
-            // Displayed in KB, but stored internally in bytes
-            'size_larger' => array(
-                'label' => _("Size (KB) >"),
-                'type' => self::SIZE,
-                'not' => false
-            ),
-        );
-    }
-
-    /**
-     * Return the base flag fields.
-     *
-     * @return array  The base flag fields.
-     */
-    public function flagFields()
-    {
-        $imp_flags = IMP_Imap_Flags::singleton();
-        $flist = $imp_flags->getFlagList(null);
-
-        $flags = array();
-
-        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;
-    }
-
     /**
      * Creates a search query.
      *
-     * @param array $uiinfo  A UI info array (see imp/search.php).
+     * @param array $search  The list of search criteria.
      *
      * @return object  A search object (Horde_Imap_Client_Search_Query).
      */
@@ -126,47 +25,46 @@ class IMP_UI_Search
         $query = new Horde_Imap_Client_Search_Query();
 
         $search_array = array();
-        $search_fields = $this->searchFields();
-        $flag_fields = $this->flagFields();
+        $search_fields = $GLOBALS['imp_search']->searchFields();
+        $flag_fields = $GLOBALS['imp_search']->flagFields();
         $imp_flags = IMP_Imap_Flags::singleton();
 
-        foreach ($search['field'] as $key => $val) {
+        foreach ($search as $rule) {
             $ob = new Horde_Imap_Client_Search_Query();
 
-            if (isset($flag_fields[$val])) {
-                $val = $imp_flags->parseFormId($val);
+            if (isset($flag_fields[$rule->t])) {
+                $val = $imp_flags->parseFormId($rule->t);
                 $ob->flag($val['flag'], $val['set']);
                 $search_array[] = $ob;
             } else {
-                switch ($search_fields[$val]['type']) {
-                case self::HEADER:
-                    if (!empty($search['text'][$key])) {
-                        $ob->headerText($val, $search['text'][$key], $search['text_not'][$key]);
+                /* Ignore unknown types. */
+                switch ($search_fields[$rule->t]['type']) {
+                case 'header':
+                    if (!empty($rule->v)) {
+                        $ob->headerText($rule->t, $rule->v, !empty($rule->n));
                         $search_array[] = $ob;
                     }
                     break;
 
-                case self::BODY:
-                case self::TEXT:
-                    if (!empty($search['text'][$key])) {
-                        $ob->text($search['text'][$key], $search_fields[$val]['type'] == self::BODY, $search['text_not'][$key]);
+                case 'body':
+                case 'text':
+                    if (!empty($rule->v)) {
+                        $ob->text($rule->c, $search_fields[$rule->t]['type'] == 'body', !empty($rule->n));
                         $search_array[] = $ob;
                     }
                     break;
 
-                case self::DATE:
-                    if (!empty($search['date'][$key]['day']) &&
-                        !empty($search['date'][$key]['month']) &&
-                        !empty($search['date'][$key]['year'])) {
-                        $date = new Horde_Date($search['date']);
-                        $ob->dateSearch($date, ($val == 'date_on') ? Horde_Imap_Client_Search_Query::DATE_ON : (($val == 'date_until') ? Horde_Imap_Client_Search_Query::DATE_BEFORE : Horde_Imap_Client_Search_Query::DATE_SINCE));
+                case 'date':
+                    if (!empty($rule->v)) {
+                        $date = new Horde_Date(array('year' => $rule->v->y, 'month' => $rule->v->m + 1, 'mday' => $rule->v->d));
+                        $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 self::SIZE:
-                    if (!empty($search['text'][$key])) {
-                        $ob->size(intval($search['text'][$key]), $val == 'size_larger');
+                case 'size':
+                    if (!empty($rule->v)) {
+                        $ob->size(intval($rule->v), $rule->t == 'size_larger');
                         $search_array[] = $ob;
                     }
                     break;
@@ -174,41 +72,43 @@ class IMP_UI_Search
             }
         }
 
-        /* Search match. */
-        if ($search['match'] == 'and') {
-            $query->andSearch($search_array);
-        } elseif ($search['match'] == 'or') {
-            $query->orSearch($search_array);
-        }
+        $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)
     {
-        $search_query = array(
-            'field' => array(),
-            'match' => 'and',
-            'text' => array(),
-            'text_not' => array()
-        );
+        $c_list = array();
 
         if ($criteria) {
-            $search_query['field'][] = $criteria;
-            $search_query['text'][] = $text;
-            $search_query['text_not'][] = $not;
+            $tmp = new stdClass;
+            $tmp->t = $criteria;
+            $tmp->v = $text;
+            $tmp->n = $not;
+            $c_list[] = $tmp;
         }
 
         if ($flag) {
-            $search_query['field'][] = $flag;
-            $search_query['text'][] = $search_query['text_not'][] = null;
+            $tmp = new stdClass;
+            $tmp->t = $flag;
+            $c_list[] = $tmp;
         }
 
         /* Set the search in the IMP session. */
-        return $GLOBALS['imp_search']->createSearchQuery($this->createQuery($search_query), array($mbox), array(), _("Search Results"));
+        return $GLOBALS['imp_search']->createSearchQuery($this->createQuery($c_list), array($mbox), $c_list, _("Search Results"), IMP_Search::MBOX_PREFIX . IMP_Search::BASIC_SEARCH);
     }
 
 }
index 9dd1af1..a0fc211 100644 (file)
     </para>
 </entry>
 
-<entry id="search">
-    <title>Message Search</title>
-    <heading>Search Criteria</heading>
-    <para>
-    You may search for messages from your folders using different search criteria.
-    </para>
-    <para>
-        Step 1: Select whether you want to match <b>all</b> queries (an AND search) or match <b>any</b> query (an OR search).
-    </para>
-    <para>
-        Step 2: Choose the fields(s) to add to your search criteria and, if necessary, identify the search parameters for these fields.
-    </para>
-    <para>
-        Step 3: Choose the folder(s) from which to search for the messages.
-    </para>
-    <para>
-        Step 4: Click &quot;Search&quot;.
-    </para>
-    <para>
-    If any messages matched your search criteria, then the subjects of these messages will be shown in the &quot;Search Results&quot; view.
-    </para>
-</entry>
-
 <entry id="folder-options">
     <title>Folders: Folder Actions</title>
     <para>
index 4d479c0..8699965 100644 (file)
@@ -358,18 +358,14 @@ if ($unread) {
     $pagetitle = $title .= ' (' . $unread . ')';
 }
 
-if ($vfolder || $search_mbox) {
-    $query_text = $imp_search->searchQueryText($imp_search->searchMboxID());
-    if ($query_text) {
-        $query_text = htmlspecialchars(wordwrap($query_text));
-        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 ($vfolder ||
+    ($search_mbox && ($imp_search->searchMboxID() != IMP_Search::BASIC_SEARCH))) {
+    $query_text = wordwrap($imp_search->searchQueryText($imp_search->searchMboxID()));
+    if ($vfolder) {
+        $pagetitle .= ' [' . Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . _("Virtual Folder") . '</a>]';
+        $title .= ' [' . _("Virtual Folder") . ']';
     } else {
-        $pagetitle = htmlspecialchars($title);
+        $pagetitle = Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . $pagetitle . '</a>';
     }
 } else {
     $pagetitle = $title = htmlspecialchars($title);
@@ -431,8 +427,8 @@ if ($_SESSION['imp']['protocol'] != 'pop') {
     } else {
         if ($imp_search->isEditableVFolder()) {
             $edit_search = sprintf(_("Edit Virtual Folder Definition for %s"), htmlspecialchars($rawtitle));
-            $hdr_template->set('delete_vfolder', Horde::link($imp_search->deleteURL(), sprintf(_("Delete Virtual Folder Definition for %s"), htmlspecialchars($rawtitle)), null, null, "if (confirm('" . addslashes(_("Are you sure you want to delete this Virtual Folder Definition?")) . "')) { return true; } else { return false; }") . Horde::img('delete.png', sprintf(_("Delete Virtual Folder Definition for %s"), $rawtitle), '', $graphicsdir) . '</a>');
-        } elseif (!$query_text) {
+            $hdr_template->set('delete_vfolder', Horde::link($imp_search->deleteUrl(), sprintf(_("Delete Virtual Folder Definition for %s"), htmlspecialchars($rawtitle)), null, null, "if (confirm('" . addslashes(_("Are you sure you want to delete this Virtual Folder Definition?")) . "')) { return true; } else { return false; }") . Horde::img('delete.png', sprintf(_("Delete Virtual Folder Definition for %s"), $rawtitle), '', $graphicsdir) . '</a>');
+        } elseif ($search_mbox && !isset($query_text)) {
             /* Mini search results. */
             $search_mailbox = reset($imp_search->getSearchFolders());
             $hdr_template->set('search', Horde::link(Horde_Util::addParameter(Horde::applicationUrl('search-basic.php'), array('search_mailbox' => $search_mailbox)), sprintf(_("Search %s"), IMP::getLabel($search_mailbox))) . Horde::img('search.png', _("Search")) . '</a>');
@@ -442,7 +438,7 @@ if ($_SESSION['imp']['protocol'] != 'pop') {
         }
 
         if (isset($edit_search)) {
-            $hdr_template->set('search', Horde::link($imp_search->editURL(), $edit_search) . Horde::img('edit.png', $edit_search, '', $graphicsdir) . '</a>');
+            $hdr_template->set('search', Horde::link($imp_search->editUrl(), $edit_search) . Horde::img('edit.png', $edit_search, '', $graphicsdir) . '</a>');
         }
     }
 }
index 5d20d64..ae05418 100644 (file)
@@ -24,11 +24,10 @@ if ($_SESSION['imp']['protocol'] == 'pop') {
     exit;
 }
 
-$imp_ui_search = new IMP_UI_Search();
-
 /* If search_basic_mbox is set, we are processing the search query. */
 $search_mailbox = Horde_Util::getFormData('search_basic_mbox');
 if ($search_mailbox) {
+    $imp_ui_search = new IMP_UI_Search();
     $id = $imp_ui_search->processBasicSearch($search_mailbox, Horde_Util::getFormData('search_criteria'), Horde_Util::getFormData('search_criteria_text'), Horde_Util::getFormData('search_criteria_not'), Horde_Util::getFormData('search_flags'));
 
     /* Redirect to the mailbox screen. */
@@ -39,8 +38,8 @@ if ($search_mailbox) {
 $f_fields = $s_fields = array();
 $search_mailbox = Horde_Util::getFormData('search_mailbox');
 
-foreach ($imp_ui_search->searchFields() as $key => $val) {
-    if ($val['type'] != IMP_UI_Search::DATE) {
+foreach ($imp_search->searchFields() as $key => $val) {
+    if ($val['type'] != 'date') {
         $s_fields[] = array(
             'val' => $key,
             'label' => $val['label']
@@ -48,7 +47,7 @@ foreach ($imp_ui_search->searchFields() as $key => $val) {
     }
 }
 
-foreach ($imp_ui_search->flagFields() as $key => $val) {
+foreach ($imp_search->flagFields() as $key => $val) {
     $f_fields[] = array(
         'val' => $key,
         'label' => $val
index 0710afe..043eca6 100644 (file)
@@ -1,12 +1,22 @@
 <?php
 /**
- * IMP search script.
+ * IMP advanced search script. This search script only works with javascript
+ * enabled browsers. All other browsers are limited to the basic search
+ * script only.
  *
  * URL Parameters:
  * ---------------
- * 'search_mailbox'  --  If exists, don't show the folder selection list; use
- *                       the passed in mailbox value instead.
- * 'edit_query'      --  If exists, the search query to edit.
+ * 'criteria_form' - (string) JSON representation of the search query.
+ * 'edit_query' - (string) The search query to edit.
+ * 'search_mailbox' - (string) Use this mailbox as the default value.
+ *                    DEFAULT: INBOX
+ *
+ * TODO:
+ * 'edit_query_vfolder'
+ * 'search_folders_form[]'
+ * 'show_unsub'
+ * 'vfolder_label'
+ * 'vfolder_save'
  *
  * Copyright 1999-2009 The Horde Project (http://www.horde.org/)
  *
 require_once dirname(__FILE__) . '/lib/Application.php';
 new IMP_Application(array('init' => true));
 
-/* Load mailbox page if searching is not allowed. */
-if ($_SESSION['imp']['protocol'] == 'pop') {
-    $notification->push(_("Searching is not available with a POP3 server."), 'horde.error');
-    $from_message_page = true;
-    $actionID = $start = null;
-    require_once IMP_BASE . '/mailbox.php';
-    exit;
-}
-
-/* Load basic search if javascript is not enabled. */
-if (!$browser->hasFeature('javascript')) {
+/* 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_once IMP_BASE . '/search-basic.php';
     exit;
 }
 
-$imp_ui_search = new IMP_UI_Search();
-
-$actionID = Horde_Util::getFormData('actionID');
-$edit_query = Horde_Util::getFormData('edit_query');
-$edit_query_vfolder = Horde_Util::getFormData('edit_query_vfolder');
-$search_mailbox = Horde_Util::getFormData('search_mailbox');
-
-$imp_search_fields = $imp_ui_search->searchFields();
-
 $charset = Horde_Nls::getCharset();
+$criteria = Horde_Util::getFormData('criteria_form');
+$edit_query = Horde_Util::getFormData('edit_query');
+$imp_search_fields = $imp_search->searchFields();
 
-/* Get URL parameter data. */
-$retrieve_search = false;
-$search = array();
-if (!is_null($edit_query) && $imp_search->isSearchMbox($edit_query)) {
-    if ($imp_search->isVFolder($edit_query)) {
-        if (!$imp_search->isEditableVFolder($edit_query)) {
-            $notification->push(_("Special Virtual Folders cannot be edited."), 'horde.error');
-            header('Location: ' . Horde::applicationUrl('mailbox.php', true));
-            exit;
-        }
-        $edit_query_vfolder = $edit_query;
-    }
-    $search = $imp_search->retrieveUIQuery($edit_query);
-    $retrieve_search = true;
-}
-
-if (empty($search)) {
-    $search['field'] = Horde_Util::getFormData('field', array('from', 'to', 'subject', 'body'));
-    if (!empty($search['field']) && !end($search['field'])) {
-        array_pop($search['field']);
-    }
-    $search['field_end'] = count($search['field']);
-    $search['match'] = Horde_Util::getFormData('search_match');
-    $search['text'] = Horde_Util::getFormData('search_text');
-    $search['text_not'] = Horde_Util::getFormData('search_text_not');
-    $search['date'] = Horde_Util::getFormData('search_date');
-    $search['folders'] = Horde_Util::getFormData('search_folders', array());
-    $search['save_vfolder'] = Horde_Util::getFormData('save_vfolder');
-    $search['vfolder_label'] = Horde_Util::getFormData('vfolder_label');
-    $search['mbox'] = Horde_Util::getFormData('mbox', $search_mailbox);
-}
-
-/* Run through the action handlers. */
-switch ($actionID) {
-case 'do_search':
-    /* Need to convert size from KB to bytes. */
-    for ($i = 0; $i <= $search['field_end']; $i++) {
-        if (isset($search['field'][$i]) &&
-            isset($imp_search_fields[$search['field'][$i]]) &&
-            ($imp_search_fields[$search['field'][$i]]['type'] == IMP_UI_Search::SIZE)) {
-            $search['text'][$i] *= 1024;
-        }
-    }
+/* Generate the search query if 'criteria_form' is present in the form
+ * data. */
+if (!empty($criteria)) {
+    $criteria = Horde_Serialize::unserialize($criteria, Horde_Serialize::JSON);
+    $folders = Horde_Util::getFormData('search_folders_form');
 
     /* Create the search query. */
-    $query = $imp_ui_search->createQuery($search);
+    $imp_ui_search = new IMP_UI_Search();
+    $query = $imp_ui_search->createQuery($criteria);
 
     /* Save the search as a virtual folder if requested. */
-    if (!empty($search['save_vfolder'])) {
-        if (empty($search['vfolder_label'])) {
-            $notification->push(_("Virtual Folders require a label."), 'horde.error');
-            break;
-        }
-
-        $id = $imp_search->addVFolder($query, $search['folders'], $search, $search['vfolder_label'], (empty($edit_query_vfolder) ? null : $edit_query_vfolder));
-        $notification->push(sprintf(_("Virtual Folder \"%s\" created succesfully."), $search['vfolder_label']), 'horde.success');
+    if (Horde_Util::getFormData('vfolder_save')) {
+        $edit_query_vfolder = Horde_Util::getFormData('edit_query_vfolder');
+        $vfolder_label = Horde_Util::getFormData('vfolder_label');
+        $id = $imp_search->addVFolder($query, $folders, $criteria, $vfolder_label, empty($edit_query_vfolder) ? null : $edit_query_vfolder);
+        $notification->push(sprintf(_("Virtual Folder \"%s\" created succesfully."), $vfolder_label), 'horde.success');
     } else {
-        /* Set the search in the IMP session. */
-        $id = $imp_search->createSearchQuery($query, $search['folders'], $search, _("Search Results"));
+        /* Set the search in the session. */
+        $id = $imp_search->createSearchQuery($query, $folders, $criteria, _("Search Results"));
     }
 
-    /* Redirect to the Mailbox Screen. */
-    header('Location: ' . Horde_Util::addParameter(Horde::applicationUrl('mailbox.php', true), 'mailbox', $GLOBALS['imp_search']->createSearchID($id), false));
+    /* Redirect to the mailbox page. */
+    header('Location: ' . Horde_Util::addParameter(Horde::applicationUrl('mailbox.php', true), array('mailbox' => $GLOBALS['imp_search']->createSearchID($id)), null, false));
     exit;
+}
 
-case 'reset_search':
-    if ($def_search = $prefs->getValue('default_search')) {
-        $search['field'] = array($def_search);
-        $search['field_end'] = 1;
-    } else {
-        $search['field'] = array();
-        $search['field_end'] = 0;
-    }
-    $search['match'] = null;
-    $search['date'] = $search['text'] = $search['text_not'] = $search['flag'] = array();
-    $search['folders'] = array();
-    break;
+/* Generate master folder list. */
+$show_unsub = ($subscribe = $prefs->getValue('subscribe'))
+    ? Horde_Util::getFormData('show_unsub', false)
+    : false;
+
+$imp_folder = IMP_Folder::singleton();
+$folders = array();
+foreach ($imp_folder->flist(array(), $subscribe && !$show_unsub) as $val) {
+    $folders[] = array_filter(array(
+        'l' => Horde_Text_Filter::filter($val['label'], 'space2html', array('charset' => $charset, 'encode' => true)),
+        'v' => $val['val']
+    ));
+}
 
-case 'delete_field':
-    $key = Horde_Util::getFormData('delete_field_id');
+if (Horde_Util::getFormData('show_unsub') !== null) {
+    Horde::sendHTTPResponse($folders, 'json');
+}
 
-    /* Unset all entries in array input and readjust ids. */
-    $vars = array('field', 'text', 'text_not', 'date');
-    foreach ($vars as $val) {
-        unset($search[$val][$key]);
-        if (!empty($search[$val])) {
-            $search[$val] = array_values($search[$val]);
-        }
+$on_domload = array(
+    'ImpSearch.updateFolderList(' . Horde_Serialize::serialize($folders, Horde_Serialize::JSON, $charset) . ')'
+);
+
+/* Process list of saved searches. */
+$saved_searches = $imp_search->listQueries(IMP_Search::LIST_SEARCH | IMP_Search::NO_BASIC_SEARCH, false);
+if (!empty($saved_searches)) {
+    $ss = array();
+    foreach ($saved_searches as $key => $val) {
+        $ss[$key] = array(
+            'c' => $imp_search->getCriteria($key),
+            'l' => Horde_String::truncate($val),
+            'v' => $key
+        );
     }
-    $search['field_end'] = count($search['field']);
-    break;
+    $on_domload[] = 'ImpSearch.updateSavedSearches(' . Horde_Serialize::serialize($ss, Horde_Serialize::JSON, $charset) . ')';
 }
 
-$shown = null;
-if (!$conf['user']['allow_folders']) {
-    $search['mbox'] = 'INBOX';
-    $search['folders'][] = 'INBOX';
-    $subscribe = false;
-} elseif ($subscribe = $prefs->getValue('subscribe')) {
-    $shown = Horde_Util::getFormData('show_subscribed_only', $subscribe);
-}
+/* Preselect mailboxes. */
+$on_domload[] = 'ImpSearch.updateSelectedFolders(' . Horde_Serialize::serialize(array(Horde_Util::getFormData('search_mailbox', 'INBOX')), Horde_Serialize::JSON, $charset) . ')';
 
 /* Prepare the search template. */
 $t = new Horde_Template();
 $t->setOption('gettext', true);
-
 $t->set('action', Horde::applicationUrl('search.php'));
 $t->set('subscribe', $subscribe);
-$t->set('shown', htmlspecialchars($shown));
-$t->set('edit_query_vfolder', htmlspecialchars($edit_query_vfolder));
-if (!$edit_query_vfolder) {
-    if (empty($search['mbox'])) {
-        $t->set('search_title', _("Search"));
-    } else {
-        $t->set('search_title',
-                sprintf(
-                    _("Search %s"),
-                    Horde::link(
-                        Horde::url(Horde_Util::addParameter('mailbox.php',
-                                                      'mailbox',
-                                                      $search['mbox'])))
-                    . htmlspecialchars(IMP::displayFolder($search['mbox']))
-                    . '</a>'));
-    }
-}
-$t->set('search_help', Horde_Help::link('imp', 'search'));
-$t->set('match_or', $search['match'] == 'or');
-$t->set('label_or', Horde::label('search_match_or', _("Match Any Query")));
-$t->set('match_and', ($search['match'] == null) || ($search['match'] == 'and'));
-$t->set('label_and', Horde::label('search_match_and', _("Match All Queries")));
 
-$saved_searches = $imp_search->listQueries(IMP_Search::LIST_SEARCH, false);
-if (!empty($saved_searches)) {
-    $ss = array();
-    foreach ($saved_searches as $key => $val) {
-        $ss[] = array('val' => htmlspecialchars($key), 'text' => htmlspecialchars(Horde_String::truncate($val)));
+/* Determine if we are editing a current search folder. */
+if (!is_null($edit_query) && $imp_search->isSearchMbox($edit_query)) {
+    if ($imp_search->isVFolder($edit_query)) {
+        if (!$imp_search->isEditableVFolder($edit_query)) {
+            $notification->push(_("Special Virtual Folders cannot be edited."), 'horde.error');
+            header('Location: ' . Horde::applicationUrl('mailbox.php', true));
+            exit;
+        }
+        $t->set('edit_query_vfolder', htmlspecialchars($edit_query));
     }
-    $t->set('saved_searches', $ss);
+    $on_domload[] = 'ImpSearch.updateSearchCriteria(' . Horde_Serialize::serialize($imp_search->getCriteria($edit_query), Horde_Serialize::JSON, $charset) . ')';
 }
 
-$fields = $f_fields = $s_fields = array();
-$js_first = 0;
+$f_fields = $s_fields = $types = array();
 
 /* Process the list of fields. */
 foreach ($imp_search_fields as $key => $val) {
-    $s_fields[$key] = array(
+    $s_fields[] = array(
         'val' => $key,
-        'label' => $val['label'],
-        'sel' => null
+        'label' => $val['label']
     );
+    $types[$key] = $val['type'];
 }
-foreach ($imp_ui_search->flagFields() as $key => $val) {
-    $f_fields[$key] = array(
+$t->set('s_fields', $s_fields);
+
+foreach ($imp_search->flagFields() as $key => $val) {
+    $f_fields[] = array(
         'val' => $key,
-        'label' => $val,
-        'sel' => null
+        'label' => $val
     );
+    $types[$key] = 'flag';
 }
+$t->set('f_fields', $f_fields);
 
-for ($i = 0; $i <= $search['field_end']; $i++) {
-    $curr = (isset($search['field'][$i])) ? $search['field'][$i] : null;
-    $fields[$i] = array(
-        'i' => $i,
-        'last' => ($i == $search['field_end']),
-        'curr' => $curr,
-        'f_fields' => $f_fields,
-        'first' => (($i == 0) && ($i != $search['field_end'])),
-        'notfirst' => ($i > 0),
-        's_fields' => $s_fields,
-        'search_text' => false,
-        'search_date' => false,
-        'js_calendar' => null
-    );
-    if ($curr !== null) {
-        if (isset($f_fields[$curr])) {
-            $fields[$i]['f_fields'][$curr]['sel'] = true;
-        } else {
-            $fields[$i]['s_fields'][$curr]['sel'] = true;
-            if (in_array($imp_search_fields[$curr]['type'], array(IMP_UI_Search::HEADER, IMP_UI_Search::BODY, IMP_UI_Search::TEXT, IMP_UI_Search::SIZE))) {
-                $fields[$i]['search_text'] = true;
-                $fields[$i]['search_text_val'] = (!empty($search['text'][$i])) ? @htmlspecialchars($search['text'][$i], ENT_COMPAT, $charset) : null;
-                if ($retrieve_search &&
-                    ($imp_search_fields[$curr]['type'] == IMP_UI_Search::SIZE)) {
-                    $fields[$i]['search_text_val'] /= 1024;
-                }
-                if ($imp_search_fields[$curr]['not']) {
-                    $fields[$i]['show_not'] = true;
-                    $fields[$i]['search_text_not'] = (!empty($search['text_not'][$i]));
-                }
-            } elseif ($imp_search_fields[$curr]['type'] == IMP_UI_Search::DATE) {
-                if (!isset($curr_date)) {
-                    $curr_date = getdate();
-                }
-                $fields[$i]['search_date'] = true;
-
-                $fields[$i]['month'] = array();
-                $month_default = isset($search['date'][$i]['month']) ? $search['date'][$i]['month'] : $curr_date['mon'];
-                for ($month = 1; $month <= 12; $month++) {
-                    $fields[$i]['month'][] = array(
-                        'val' => $month,
-                        'sel' => ($month == $month_default),
-                        'label' => strftime('%B', mktime(0, 0, 0, $month, 1))
-                    );
-                }
-
-                $fields[$i]['day'] = array();
-                $day_default = isset($search['date'][$i]['day']) ? $search['date'][$i]['day'] : $curr_date['mday'];
-                for ($day = 1; $day <= 31; $day++) {
-                    $fields[$i]['day'][] = array(
-                        'val' => $day,
-                        'sel' => ($day == $day_default)
-                    );
-                }
-
-                $fields[$i]['year'] = array();
-                $year_default = isset($search['date'][$i]['year']) ? $search['date'][$i]['year'] : $curr_date['year'];
-                if (!isset($curr_year)) {
-                    $curr_year = date('Y');
-                    $yearlist = array();
-                    $years = -20;
-                    $startyear = (($year_default < $curr_year) && ($years > 0)) ? $year_default : $curr_year;
-                    $startyear = min($startyear, $startyear + $years);
-                    for ($j = 0; $j <= abs($years); $j++) {
-                        $yearlist[] = $startyear++;
-                    }
-                    if ($years < 0) {
-                        $yearlist = array_reverse($yearlist);
-                    }
-                }
-                foreach ($yearlist as $year) {
-                    $fields[$i]['year'][] = array(
-                        'val' => $year,
-                        'sel' => ($year == $year_default)
-                    );
-                }
-
-                if ($browser->hasFeature('javascript')) {
-                    Horde::addScriptFile('open_calendar.js', 'horde');
-                    $fields[$i]['js_calendar_first'] = !$js_first++;
-                    $fields[$i]['js_calendar'] = Horde::link('#', _("Select a date"), '', '', 'openCalendar(\'dateimg' . $i . '\', \'search_date_' . $i . '\'); return false;');
-                    $fields[$i]['js_calendar_img'] = Horde::img('calendar.png', _("Calendar"), 'align="top" id="dateimg' . $i . '"', $GLOBALS['registry']->getImageDir('horde'));
-                }
-            }
-        }
-    }
-}
-$t->set('fields', array_values($fields));
-$t->set('delete_img', $registry->getImageDir('horde') . '/delete.png');
-$t->set('remove', _("Remove Field From Search"));
-
-if ($subscribe) {
-    $t->set('inverse_subscribe', !$shown);
-}
-
-$t->set('mbox', htmlspecialchars($search['mbox']));
 $t->set('virtualfolder', $_SESSION['imp']['protocol'] != 'pop');
-if ($t->get('virtualfolder')) {
-    $t->set('save_vfolder', !empty($search['save_vfolder']));
-    $t->set('vfolder_label', !empty($search['vfolder_label']) ? htmlspecialchars($search['vfolder_label'], ENT_COMPAT, $charset) : null);
-}
 
-if (empty($search['mbox'])) {
-    $count = -1;
-    $mboxes = array();
-    $newcol = $numcolumns = 1;
-
-    $imp_folder = IMP_Folder::singleton();
-    $mailboxes = $imp_folder->flist(array(), !is_null($shown) ? $shown : null);
-    $total = ceil(count($mailboxes) / 3);
-
-    if (empty($search['folders']) && ($actionID != 'update_search')) {
-        /* Default to Inbox search. */
-        $search['folders'][] = 'INBOX';
-    }
-
-    foreach ($mailboxes as $key => $mbox) {
-        $mboxes[$key] = array(
-            'count' => ++$count,
-            'val' => (!empty($mbox['val']) ? htmlspecialchars($mbox['val']) : null),
-            'sel' => false,
-            'label' => str_replace(' ', '&nbsp;', $mbox['label']),
-            'newcol' => false
-        );
+Horde_UI_JsCalendar::init();
 
-        if (!empty($mbox['val']) &&
-            in_array($mbox['val'], $search['folders'])) {
-            $mboxes[$key]['sel'] = true;
-        }
-
-        if ((++$newcol > $total) && ($numcolumns != 3)) {
-            $newcol = 1;
-            ++$numcolumns;
-            $mboxes[$key]['newcol'] = true;
-        }
-    }
-    $t->set('mboxes', array_values($mboxes));
-}
+Horde::addInlineScript(array(
+    'ImpSearch.loading = ' . Horde_Serialize::serialize(_("Loading..."), Horde_Serialize::JSON, $charset),
+    'ImpSearch.months = ' . Horde_Serialize::serialize(Horde_UI_JsCalendar::months(), Horde_Serialize::JSON, $charset),
+    'ImpSearch.need_criteria = ' . Horde_Serialize::serialize(_("Please select at least one search criteria."), Horde_Serialize::JSON, $charset),
+    'ImpSearch.need_folder = ' . Horde_Serialize::serialize(_("Please select at least one folder to search."), Horde_Serialize::JSON, $charset),
+    'ImpSearch.need_vfolder_label = ' . Horde_Serialize::serialize(_("Virtual Folders require a label."), Horde_Serialize::JSON, $charset),
+    'ImpSearch.types = ' . Horde_Serialize::serialize($types, Horde_Serialize::JSON, $charset)
+));
+Horde::addInlineScript($on_domload, 'dom');
 
-$title = _("Message Search");
-Horde::addScriptFile('stripe.js', 'horde', true);
+$title = _("Search");
+Horde::addScriptFile('horde.js', 'horde', true);
 Horde::addScriptFile('search.js', 'imp', true);
 IMP::prepareMenu();
 require IMP_TEMPLATES . '/common-header.inc';
 IMP::menu();
 IMP::status();
 
-Horde::addInlineScript(array(
-    'ImpSearch.search_date = ' . Horde_Serialize::serialize(array('m' => date('m'), 'd' => date('d'), 'y' => date('Y')), Horde_Serialize::JSON, $charset),
-    'ImpSearch.not_search = ' . intval(empty($search['mbox'])),
-    'ImpSearch.inverse_sub = ' . intval($t->get('inverse_subscribe')),
-));
 echo $t->fetch(IMP_TEMPLATES . '/search/search.html');
 require $registry->get('templates', 'horde') . '/common-footer.inc';
index 0d90ac0..bc3c434 100644 (file)
@@ -54,9 +54,6 @@ $code = array(
         'folders_rename2' => _("Please enter the new name:"),
         'folders_no_rename' => _("This folder may not be renamed:"),
 
-        /* Strings used in search.js */
-        'search_select' => _("Please select at least one folder to search."),
-
         /* Strings used in imp.js */
         'popup_block' => _("A popup window could not be opened. Perhaps you have set your browser to block popup windows?"),
 
index d97beac..e451579 100644 (file)
-<form method="post" id="search" action="<tag:action />">
-<input type="hidden" id="actionID" name="actionID" value="update_search" />
-<input type="hidden" id="delete_field_id" name="delete_field_id" value="" />
-<input type="hidden" id="edit_query" name="edit_query" value="" />
-<input type="hidden" name="edit_query_vfolder" value="<tag:edit_query_vfolder />" />
-<if:mbox>
-<input type="hidden" name="mbox" value="<tag:mbox />" />
-<input id="preselected_folders" type="hidden" name="search_folders[]" value="<tag:mbox />" />
-<else:mbox>
-<if:subscribe>
-<input type="hidden" id="show_subscribed_only" name="show_subscribed_only" value="<tag:shown />" />
-</if:subscribe>
-</else:mbox></if:mbox>
+<form id="search_form" action="<tag:action />" method="post">
+ <input class="hidden" name="criteria_form" id="criteria_form" value="" />
 
-<h1 class="header">
-<if:edit_query_vfolder>
- <strong><gettext>Edit Virtual Folder</gettext></strong>
-<else:edit_query_vfolder>
- <strong><tag:search_title /></strong>
-</else:edit_query_vfolder></if:edit_query_vfolder>
- <tag:search_help />
-</h1>
+ <h1 class="header">
+  <strong><if:edit_query_vfolder><gettext>Edit Virtual Folder</gettext><else:edit_query_vfolder><gettext>Search</gettext></else:edit_query_vfolder></if:edit_query_vfolder></strong>
+ </h1>
 
-<div>
- <input type="submit" class="button searchSubmit" value="<if:edit_query_vfolder><gettext>Save</gettext><else:edit_query_vfolder><gettext>Submit</gettext></else:edit_query_vfolder></if:edit_query_vfolder>" />
- <input type="button" class="button searchReset" value="<gettext>Reset</gettext>" />
-</div>
+ <div id="recent_searches_div" class="smallheader leftAlign" style="display:none">
+  <span class="searchuiImg arrowExpanded" style="display:none"></span>
+  <span class="searchuiImg arrowCollapsed"></span>
+  <gettext>Recent Searches</gettext>
+ </div>
 
-<if:saved_searches>
-<div class="smallheader leftAlign">
- <gettext>Recent Searches</gettext>
-</div>
+ <div class="item" style="display:none">
+  <label for="recent_searches" class="hidden"><gettext>Recent Searches:</gettext></label>
+  <select id="recent_searches">
+   <option value=""><gettext>Recent Searches:</gettext></option>
+  </select>
+ </div>
 
-<div class="item">
- <label for="save_cache" class="hidden"><gettext>Recent Searches:</gettext></label>
- <select id="save_cache" name="save_cache">
-  <option value=""><gettext>Recent Searches:</gettext></option>
-<loop:saved_searches>
-  <option value="<tag:saved_searches.val />"><tag:saved_searches.text /></option>
-</loop:saved_searches>
- </select>
-</div>
-</if:saved_searches>
-
-<div class="smallheader leftAlign">
+ <div class="smallheader leftAlign">
+  <span class="searchuiImg arrowExpanded"></span>
+  <span class="searchuiImg arrowCollapsed" style="display:none"></span>
  <gettext>Search Criteria</gettext>
-</div>
+ </div>
 
-<div class="item">
- <input type="radio" class="checkbox" name="search_match" id="search_match_or" value="or"<if:match_or> checked="checked"</if:match_or> />
- <em><tag:label_or /></em>
- <input type="radio" class="checkbox" name="search_match" id="search_match_and" value="and"<if:match_and> checked="checked"</if:match_and> />
- <em><tag:label_and /></em>
-
-<table class="item" cellspacing="0">
-<loop:fields>
- <tr>
-<if:fields.first>
-  <td>&nbsp;</td>
-<else:fields.first><if:fields.i>
-  <td><strong><if:match_and><gettext>AND</gettext><else:match_and><gettext>OR</gettext></else:match_and></if:match_and></strong></td>
-</if:fields.i>
-</else:fields.first></if:fields.first>
-  <td>
-   <label for="field_<tag:fields.i />" class="hidden"><gettext>Select a field</gettext></label>
-   <select name="field[<tag:fields.i />]" id="field_<tag:fields.i />">
-<if:fields.last>
-    <option value=""><gettext>Select a field</gettext></option>
+ <div class="item">
+  <div>
+   <select id="search_criteria">
+    <option value=""><gettext>Add search criteria:</gettext></option>
     <option value="" disabled="disabled">- - - - - - - - -</option>
-</if:fields.last>
-<loop:fields.s_fields>
-    <option value="<tag:fields.s_fields.val />"<if:fields.s_fields.sel> selected="selected"</if:fields.s_fields.sel>><tag:fields.s_fields.label /></option>
-</loop:fields.s_fields>
+<loop:s_fields>
+    <option value="<tag:s_fields.val />"><tag:s_fields.label /></option>
+</loop:s_fields>
     <option value="" disabled="disabled">- - - - - - - - -</option>
-<loop:fields.f_fields>
-    <option value="<tag:fields.f_fields.val />"<if:fields.f_fields.sel> selected="selected"</if:fields.f_fields.sel>><tag:fields.f_fields.label /></option>
-</loop:fields.f_fields>
-   </select>
-  </td>
-<if:fields.curr>
-<if:fields.search_text>
-  <td class="leftAlign">
-   <input type="text" id="search_text_<tag:fields.i />" name="search_text[<tag:fields.i />]" size="40" <if:fields.search_text_val>value="<tag:fields.search_text_val />" </if:fields.search_text_val>/>
-   <label for="search_text_<tag:fields.i />" class="hidden"><gettext>Search Text</gettext></label>
-<if:fields.show_not>
-   <input type="checkbox" class="checkbox" id="search_text_not_<tag:fields.i />" name="search_text_not[<tag:fields.i />]" <if:fields.search_text_not>checked="checked" </if:fields.search_text_not>/>
-   <label for="search_text_not_<tag:fields.i />"><em><gettext>Do NOT match</gettext></em></label>
-</if:fields.show_not>
-  </td>
-</if:fields.search_text>
-<if:fields.search_date>
-  <td class="leftAlign">
-   <label for="search_date_<tag:fields.i />_month" class="hidden"><gettext>Month</gettext></label>
-   <select id="search_date_<tag:fields.i />_month" name="search_date[<tag:fields.i />][month]">
-<loop:fields.month>
-    <option value="<tag:fields.month.val />" <if:fields.month.sel>selected="selected" </if:fields.month.sel>><tag:fields.month.label /></option>
-</loop:fields.month>
+<loop:f_fields>
+    <option value="<tag:f_fields.val />"><tag:f_fields.label /></option>
+</loop:f_fields>
    </select>
-   <label for="search_date_<tag:fields.i />_day" class="hidden"><gettext>Day</gettext></label>
-   <select id="search_date_<tag:fields.i />_day" name="search_date[<tag:fields.i />][day]">
-<loop:fields.day>
-    <option value="<tag:fields.day.val />" <if:fields.day.sel>selected="selected" </if:fields.day.sel>><tag:fields.day.val /></option>
-</loop:fields.day>
-   </select>
-   <label for="search_date_<tag:fields.i />_year" class="hidden"><gettext>Year</gettext></label>
-   <select id="search_date_<tag:fields.i />_year" name="search_date[<tag:fields.i />][year]">
-<loop:fields.year>
-    <option value="<tag:fields.year.val />" <if:fields.year.sel>selected="selected"</if:fields.year.sel>><tag:fields.year.val /></option>
-</loop:fields.year>
-   </select>
-<if:fields.js_calendar>
-<if:fields.js_calendar_first>
-   <div id="goto" style="position:absolute;visibility:hidden;padding:1px"></div>
-</if:fields.js_calendar_first>
-   <tag:fields.js_calendar /><tag:fields.js_calendar_img /></a>
-</if:fields.js_calendar>
-  </td>
-</if:fields.search_date>
-<else:fields.curr>
-  <td>&nbsp;</td>
-</else:fields.curr></if:fields.curr>
+  </div>
+ </div>
 
-<if:fields.last>
-<if:fields.i>
-  <td>&nbsp;</td>
-</if:fields.i>
-<else:fields.last>
-  <td><input type="image" class="image searchDelete" fid="<tag:fields.i />" src="<tag:delete_img />" value="<tag:remove />" /></td>
-</else:fields.last></if:fields.last>
- </tr>
-</loop:fields>
-</table>
-</div>
-
-<if:mbox><else:mbox>
-<div class="smallheader leftAlign">
- <gettext>Search Folders</gettext>
-</div>
-
-<div class="item">
- <a id="link_sel_all" href="#"><gettext>Select all</gettext></a> |
- <a id="link_sel_none" href="#"><gettext>Select none</gettext></a>
+ <div class="smallheader leftAlign" id="search_folders_hdr">
+  <span class="searchuiImg arrowExpanded" style="display:none"></span>
+  <span class="searchuiImg arrowCollapsed"></span>
+  <gettext>Search Folders</gettext>
+  <span class="item" 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>
 <if:subscribe>
- | <a id="link_sub" href="#"><if:inverse_subscribe><gettext>Show Only Subscribed Folders</gettext><else:inverse_subscribe><gettext>Show All Folders</gettext></else:inverse_subscribe></if:inverse_subscribe></a>
+| <a id="link_sub" href="#"><span><gettext>Show All</gettext></span><span style="display:none"><gettext>Show Subscribed Only</gettext></span></a>
 </if:subscribe>
-</div>
+  </span>
+ </div>
 
-<table width="100%" cellspacing="0">
- <tr>
-  <td class="item leftAlign" valign="top">
-<loop:mboxes>
-   <input id="folder<tag:mboxes.count />" type="checkbox" class="checkbox"<if:mboxes.val>name="search_folders[]" value="<tag:mboxes.val />"<if:mboxes.sel> checked="checked"</if:mboxes.sel><else:mboxes.val>disabled="disabled"</else:mboxes.val></if:mboxes.val> />
-   <tag:mboxes.label /><br />
-<if:mboxes.newcol>
-  </td>
-  <td class="item" valign="top">
-</if:mboxes.newcol>
-</loop:mboxes>
-  </td>
- </tr>
-</table>
-</else:mbox></if:mbox>
+ <div class="item" style="display:none">
+ </div>
 
 <if:virtualfolder>
-<div class="smallheader leftAlign">
- <gettext>Virtual Folders</gettext>
-</div>
+ <div class="smallheader leftAlign">
+  <span class="searchuiImg arrowExpanded" style="display:none"></span>
+  <span class="searchuiImg arrowCollapsed"></span>
+  <gettext>Virtual Folders</gettext>
+ </div>
 
+ <div style="display:none">
 <if:edit_query_vfolder>
-<input type="hidden" name="save_vfolder" value="1" />
+  <input type="hidden" name="edit_query_vfolder" value="" />
 <else:edit_query_vfolder>
-<div class="item">
<label for="save_vfolder"><em><gettext>Save search as a virtual folder?</gettext></em></label> <input type="checkbox" class="checkbox" id="save_vfolder" name="save_vfolder" <if:save_vfolder>checked="checked" </if:save_vfolder>/>
-</div>
+  <div class="item">
  <label for="vfolder_save"><gettext>Save search as a virtual folder?</gettext></label> <input type="checkbox" class="checkbox" id="vfolder_save" name="vfolder_save" />
+  </div>
 </else:edit_query_vfolder></if:edit_query_vfolder>
-<div class="item">
- <label for="vfolder_label"><em><gettext>Virtual folder label:</gettext></em></label> <input type="text" id="vfolder_label" name="vfolder_label" <if:vfolder_label>value="<tag:vfolder_label />" </if:vfolder_label>/>
-</div>
+  <div class="item">
+   <label for="vfolder_label"><gettext>Label:</gettext></label> <input type="text" name="vfolder_label" id="vfolder_label" />
+  </div>
+ </div>
 </if:virtualfolder>
 
-<div>
<input type="submit" class="button searchSubmit" value="<if:edit_query_vfolder><gettext>Save</gettext><else:edit_query_vfolder><gettext>Submit</gettext></else:edit_query_vfolder></if:edit_query_vfolder>" />
<input type="button" class="button searchReset" value="<gettext>Reset</gettext>" />
-</div>
+ <div>
 <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>" />
 <input type="button" id="search_reset" class="button" value="<gettext>Reset</gettext>" />
+ </div>
 </form>
+
+<span id="text_criteria" style="display:none">
+ <em></em>
+ <input type="text" size="25" />
+ <span><input type="checkbox" /><gettext>Do NOT Match</gettext></input></span>
+</span>
+
+<span id="flag_criteria" style="display:none">
+ <em><gettext>Flag</gettext>: </em>
+</span>
+
+<span id="date_criteria" style="display:none">
+ <em></em>
+ <span></span>
+ <a href="#" class="calendarPopup" title="<gettext>Date Selection</gettext>"><span class="searchuiImg searchuiCalendar"></span></a>
+</span>
+
+<span id="delete_criteria" style="display:none">
+ <a href="#" class="searchuiImg searchuiDelete"></a>
+</span>
+
+<div id="folder_row" style="display:none">
+ <input type="checkbox" class="checkbox" name="search_folders_form[]" />
+</div>
diff --git a/imp/themes/graphics/calendar.png b/imp/themes/graphics/calendar.png
new file mode 100644 (file)
index 0000000..2712746
Binary files /dev/null and b/imp/themes/graphics/calendar.png differ
index 6fa678e..47fc99d 100644 (file)
@@ -161,8 +161,8 @@ div.msgActions, #fmanager div.folderActions {
 }
 
 /* Search page styling. */
-form#search div {
-    padding: 1px;
+.searchuiCalendar {
+    background-image: url("graphics/calendar.png");
 }
 
 /* Folder view. */
@@ -513,6 +513,14 @@ span.treeImg20 {
     background-image: url("graphics/tree/rev-plusbottom.png");
 }
 
+/* Expand/Collapse graphic. */
+span.arrowCollapsed {
+    background-image: url("graphics/arrow_collapsed.png");
+}
+span.arrowExpanded {
+    background-image: url("graphics/arrow_expanded.png");
+}
+
 /* Loading graphic */
 span.loadingImg {
     z-index: 1000;
@@ -575,7 +583,7 @@ td.addressTr span.loadingImg {
     white-space: nowrap;
 }
 
-.downloadAtc, .downloadZipAtc, .saveImgAtc, .closeImg, .stripAtc, .foldersImg {
+.downloadAtc, .downloadZipAtc, .saveImgAtc, .closeImg, .stripAtc, .foldersImg, .searchuiImg {
     display: -moz-inline-stack;
     display: inline-block;
     height: 16px;
@@ -593,7 +601,7 @@ td.addressTr span.loadingImg {
 .saveImgAtc {
     background-image: url("graphics/gallery.png");
 }
-.stripAtc {
+.stripAtc, .searchuiDelete {
     background-image: url("graphics/delete.png");
 }
 .closeImg {
diff --git a/imp/themes/silver/graphics/calendar.png b/imp/themes/silver/graphics/calendar.png
new file mode 100644 (file)
index 0000000..783c833
Binary files /dev/null and b/imp/themes/silver/graphics/calendar.png differ
index b94c380..11d04d1 100644 (file)
@@ -83,3 +83,6 @@ span.trashImg {
 .closeImg {
     background-image: url("graphics/close.png");
 }
+.searchuiCalendar {
+    background-image: url("graphics/calendar.png");
+}
diff --git a/imp/themes/tango-blue/graphics/calendar.png b/imp/themes/tango-blue/graphics/calendar.png
new file mode 100644 (file)
index 0000000..f6978d7
Binary files /dev/null and b/imp/themes/tango-blue/graphics/calendar.png differ
index f00b496..163a592 100644 (file)
@@ -10,3 +10,6 @@
 .closeImg {
     background-image: url("graphics/close.png");
 }
+.searchuiCalendar {
+    background-image: url("graphics/calendar.png");
+}