From 1ebf9c48801f2b732c1d6ec28b1672cb694ee404 Mon Sep 17 00:00:00 2001 From: Michael M Slusarz Date: Tue, 25 Aug 2009 14:52:44 -0600 Subject: [PATCH] Request #6875: Improved advanced search query interface --- imp/docs/CHANGES | 1 + imp/folders.php | 6 +- imp/js/search.js | 325 +++++++++++++++++------ imp/lib/Search.php | 302 +++++++++++++-------- imp/lib/UI/Search.php | 184 +++---------- imp/locale/en_US/help.xml | 23 -- imp/mailbox.php | 24 +- imp/search-basic.php | 9 +- imp/search.php | 395 ++++++++-------------------- imp/templates/javascript_defs.php | 3 - imp/templates/search/search.html | 249 ++++++------------ imp/themes/graphics/calendar.png | Bin 0 -> 368 bytes imp/themes/screen.css | 16 +- imp/themes/silver/graphics/calendar.png | Bin 0 -> 626 bytes imp/themes/silver/screen.css | 3 + imp/themes/tango-blue/graphics/calendar.png | Bin 0 -> 604 bytes imp/themes/tango-blue/screen.css | 3 + 17 files changed, 715 insertions(+), 828 deletions(-) create mode 100644 imp/themes/graphics/calendar.png create mode 100644 imp/themes/silver/graphics/calendar.png create mode 100644 imp/themes/tango-blue/graphics/calendar.png diff --git a/imp/docs/CHANGES b/imp/docs/CHANGES index 5dc235ff0..84bfcad83 100644 --- a/imp/docs/CHANGES +++ b/imp/docs/CHANGES @@ -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. diff --git a/imp/folders.php b/imp/folders.php index f43f40e82..f266bca44 100644 --- a/imp/folders.php +++ b/imp/folders.php @@ -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") . ''; - $val['editvfolder'] = Horde::link($imp_search->editURL($val['value']), _("Edit Virtual Folder")) . _("Edit") . ''; + if (!empty($val['vfolder']) && $val['editvfolder']) { + $val['delvfolder'] = Horde::link($imp_search->deleteUrl($val['value']), _("Delete Virtual Folder")) . _("Delete") . ''; + $val['editvfolder'] = Horde::link($imp_search->editUrl($val['value']), _("Edit Virtual Folder")) . _("Edit") . ''; } $val['cname'] = (++$rowct % 2) ? 'item0' : 'item1'; diff --git a/imp/js/search.js b/imp/js/search.js index 0ec910397..1c9152737 100644 --- a/imp/js/search.js +++ b/imp/js/search.js @@ -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. @@ -7,88 +7,232 @@ 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); } }; diff --git a/imp/lib/Search.php b/imp/lib/Search.php index ef4405d31..da1db54ab 100644 --- a/imp/lib/Search.php +++ b/imp/lib/Search.php @@ -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) { diff --git a/imp/lib/UI/Search.php b/imp/lib/UI/Search.php index 2b097bfb4..9cc82e239 100644 --- a/imp/lib/UI/Search.php +++ b/imp/lib/UI/Search.php @@ -13,111 +13,10 @@ */ 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); } } diff --git a/imp/locale/en_US/help.xml b/imp/locale/en_US/help.xml index 9dd1af190..a0fc21137 100644 --- a/imp/locale/en_US/help.xml +++ b/imp/locale/en_US/help.xml @@ -205,29 +205,6 @@ - - Message Search - Search Criteria - - You may search for messages from your folders using different search criteria. - - - Step 1: Select whether you want to match all queries (an AND search) or match any query (an OR search). - - - Step 2: Choose the fields(s) to add to your search criteria and, if necessary, identify the search parameters for these fields. - - - Step 3: Choose the folder(s) from which to search for the messages. - - - Step 4: Click "Search". - - - If any messages matched your search criteria, then the subjects of these messages will be shown in the "Search Results" view. - - - Folders: Folder Actions diff --git a/imp/mailbox.php b/imp/mailbox.php index 4d479c0bf..86999652f 100644 --- a/imp/mailbox.php +++ b/imp/mailbox.php @@ -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") . ']'; - $title .= ' [' . _("Virtual Folder") . ']'; - } else { - $pagetitle = Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . $pagetitle . ''; - } +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") . ']'; + $title .= ' [' . _("Virtual Folder") . ']'; } else { - $pagetitle = htmlspecialchars($title); + $pagetitle = Horde::linkTooltip('#', $query_text, '', '', '', $query_text) . $pagetitle . ''; } } 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) . ''); - } 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) . ''); + } 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")) . ''); @@ -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) . ''); + $hdr_template->set('search', Horde::link($imp_search->editUrl(), $edit_search) . Horde::img('edit.png', $edit_search, '', $graphicsdir) . ''); } } } diff --git a/imp/search-basic.php b/imp/search-basic.php index 5d20d64ec..ae05418a3 100644 --- a/imp/search-basic.php +++ b/imp/search-basic.php @@ -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 diff --git a/imp/search.php b/imp/search.php index 0710afedc..043eca615 100644 --- a/imp/search.php +++ b/imp/search.php @@ -1,12 +1,22 @@ 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'])) - . '')); - } -} -$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(' ', ' ', $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'; diff --git a/imp/templates/javascript_defs.php b/imp/templates/javascript_defs.php index 0d90ac0bc..bc3c4343f 100644 --- a/imp/templates/javascript_defs.php +++ b/imp/templates/javascript_defs.php @@ -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?"), diff --git a/imp/templates/search/search.html b/imp/templates/search/search.html index d97beaca2..e45157926 100644 --- a/imp/templates/search/search.html +++ b/imp/templates/search/search.html @@ -1,185 +1,108 @@ - + + + + + + + + + + diff --git a/imp/themes/graphics/calendar.png b/imp/themes/graphics/calendar.png new file mode 100644 index 0000000000000000000000000000000000000000..2712746411c9d05bb343df1e3d6b6168a9f752f9 GIT binary patch literal 368 zcmeAS@N?(olHy`uVBq!ia0vp^0wB!63?wyl`GbK}U4T!BE0F#V0zdx$|9$$@ft4#3 zOr6quVE^{XlX_;)nzCxe!tGnvZQHW;@2?M2CiTpiHu2Ao_xpBl*}Y@qrE|wl9^H58 z-0}ZA+-6RjxZ2n8dz8=N0FNI(eti7+@y(kz4<0VfmhE0gZNZnrz6T65%cROt3ynNA$v-L+*JgbUd8 zCTR3MnKWlh`7P1NZHz?|XBu=hzw#^1PqX@ycZQu?+k3vqv%T-T{_|>f3GP%ne!EnJ d({^4pGe?!!v&+wvy@5_(@O1TaS?83{1OTA8!dw6V literal 0 HcmV?d00001 diff --git a/imp/themes/screen.css b/imp/themes/screen.css index 6fa678e8c..47fc99dc0 100644 --- a/imp/themes/screen.css +++ b/imp/themes/screen.css @@ -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 index 0000000000000000000000000000000000000000..783c83357fdf90a1c7c024358e1d768b5c09c135 GIT binary patch literal 626 zcmV-&0*(ENP)5OC%H;f`~O(q$Q#t2<^v$A>fbmv%e#dKTwK=Ku{5lS|}<-`a#7b zzTCOnnT>at)D}AMFuOZ5&%EqFN(lyumd$2ASF6=;nM~%2?gqc@U=#|4PqkX@EBo-9 z7pD#bO_RUa>*faM`8;MYfVi$JnB-zcBFc6gjl$d!bF98Q!!!(Z1_R~P?e!pt#6CHJ9S&n_n&@=9 z%GP;!@Co4c*at+6vNz7o(6en^Q1%qHrc;1)9IRaz-$@S$Z-qdC^ds3X0NvQH;KS)D z-dh&rW&@X;1cS(45z)J&BVt+tv&GMVJ%!EiW) zLBGZW)#Z+gl-Lih&?>X3SS-S#ujQ;9JRXmIB7X)8`d6ETj)D#Q2+$s|<_b7-B9Xvq zwNfqlEp%y3$uY`h{Y$(Gn5@}sqEsq95lpAkFO5dyBmP6^H-51G4J|rN2Ujt<`2YX_ M07*qoM6N<$fU}^a|TU+0#)oKwB;}Av|oIf{$5Q1I~plNy^uh;8jGMT7P zZ!{VJn5IcGnPg-0E05+2q*5vJ*KcrXe1etLGL~gwSr(>g0ubsV3-NdyUDvT^U7o%u zA*JN(nbSBoXLvYYKq-Y%ifXOeXF>mSyZ7(x9b|a;I9I1<7#kglazUrlzXRXj0ie}t z?K{Wu&0`fxnXI_ZOqNZ3Zh7bZ>*AYU{Y&NOZssus6?(PoRY?hbHCAPPH zLS1B`_qHL@>GZyQyWQsFr}aGxN~w+UiOWX^{R={fH2@9JPMkc|y*PG(a(V4Q;?G|; qSzlc_1au7`22flq4LZaT;QLSFb>&0