Create search filters UI in IMP.
authorMichael M Slusarz <slusarz@curecanti.org>
Mon, 20 Sep 2010 06:38:18 +0000 (00:38 -0600)
committerMichael M Slusarz <slusarz@curecanti.org>
Wed, 22 Sep 2010 05:30:40 +0000 (23:30 -0600)
These are generic search queries that can be applied to any mailbox.
Presently, these filters are only usable in IMP.  The dimp interface has
not yet been written.

See, e.g., Request #8659.

16 files changed:
imp/config/prefs.php.dist
imp/docs/CHANGES
imp/js/mailbox.js
imp/js/search.js
imp/js/searchesprefs.js
imp/lib/Prefs/Ui.php
imp/lib/Search.php
imp/lib/Search/Filter.php [new file with mode: 0644]
imp/lib/Search/Filter/Bulk.php [new file with mode: 0644]
imp/mailbox.php
imp/search.php
imp/templates/imp/mailbox/form_start.html
imp/templates/imp/mailbox/navbar.html
imp/templates/imp/search/search.html
imp/templates/prefs/searches.html
imp/themes/screen.css

index bae0024..512f8ab 100644 (file)
@@ -397,6 +397,16 @@ $_prefs['vfolder'] = array(
     'value' => 'a:1:{i:0;C:25:"IMP_Search_Vfolder_Vinbox":175:{a:3:{s:1:"c";a:2:{i:0;C:23:"IMP_Search_Element_Flag":24:{[1,{"f":"\\seen","s":0}]}i:1;C:23:"IMP_Search_Element_Flag":27:{[1,{"f":"\\deleted","s":0}]}}s:1:"e";i:1;s:1:"v";i:1;}}}'
 );
 
+$_prefs['filter'] = array(
+    // 'value' => serialize(array(
+    //     // Bulk filter, enabled by default
+    //     new IMP_Search_Filter_Bulk(array(
+    //         'disable' => false
+    //     ))
+    // ))
+    'value' => 'a:1:{i:0;C:22:"IMP_Search_Filter_Bulk":88:{a:3:{s:1:"c";a:1:{i:0;C:23:"IMP_Search_Element_Bulk":5:{[1,0]}}s:1:"e";i:1;s:1:"v";i:1;}}}'
+);
+
 
 
 // *** Compose Preferences ***
index a3a05ec..53c6ba8 100644 (file)
@@ -2,6 +2,8 @@
 v5.0-git
 --------
 
+[mms] Add ability to define search criteria to be applied to any mailbox
+      (Request #8659).
 [mms] Use recipient search (To/Cc/Bcc) by default instead of To search.
 [mms] Virtual folders now editable via a preferences group.
 [mms] Search code has been entirely rewritten.
index 0744645..fb7c66e 100644 (file)
@@ -24,9 +24,17 @@ var ImpMailbox = {
 
     submit: function(actID)
     {
-        if (!this.anySelected()) {
-            alert(IMP.text.mailbox_submit);
-            return;
+        switch (actID) {
+        case 'filter_messages':
+            // No-op
+            break;
+
+        default:
+            if (!this.anySelected()) {
+                alert(IMP.text.mailbox_submit);
+                return;
+            }
+            break;
         }
 
         switch (actID) {
@@ -160,6 +168,17 @@ var ImpMailbox = {
         }
     },
 
+    filterMessages: function(form)
+    {
+        var f1 = $('filter1'), f2 = $('filter2');
+
+        if ((form == 1 && $F(f1) != "") ||
+            (form == 2 && $F(f2) != "")) {
+            $('messages').down('[name=filter]').setValue((form == 1) ? $F(f1) : $F(f2));
+            this.submit('filter_messages');
+        }
+    },
+
     getMessage: function(id, offset)
     {
         if (!offset) {
@@ -186,6 +205,8 @@ var ImpMailbox = {
         if (id) {
             if (id.startsWith('flag')) {
                 this.flagMessages(id.substring(4));
+            } else if (id.startsWith('filter')) {
+                this.filterMessages(id.substring(6));
             } else if (id.startsWith('targetMailbox')) {
                 this.updateFolders(id.substring(13));
             }
@@ -365,7 +386,7 @@ var ImpMailbox = {
 
     submitHandler: function(e)
     {
-        if (e.element().readAttribute('id').startsWith('select')) {
+        if (e.element().hasClassName('navbarselect')) {
             e.stop();
         }
     }
index 1e13264..f804b18 100644 (file)
@@ -101,7 +101,13 @@ var ImpSearch = {
 
     updateSelectedFolders: function(folders)
     {
-        var tmp = $('search_folders_hdr').next();
+        var tmp = $('search_folders_hdr');
+
+        if (!tmp) {
+            return;
+        }
+
+        tmp = tmp.next();
         this.selectFolders(false);
         folders.each(function(f) {
             var i = tmp.down('INPUT[value=' + f + ']');
@@ -313,9 +319,10 @@ var ImpSearch = {
     {
         var data = [], tmp;
 
-        if (!this._getAll().findAll(function(i) { return i.checked; }).size()) {
+        if ($('search_folders_hdr') &&
+            !this._getAll().findAll(function(i) { return i.checked; }).size()) {
             alert(this.text.need_folder);
-        } else if ($F('search_save') && !$('search_label').present()) {
+        } else if ($F('search_type') && !$('search_label').present()) {
             alert(this.text.need_label);
         } else {
             tmp = $('search_criteria_table').childElements().pluck('id');
index 9153b85..1e1fac1 100644 (file)
@@ -6,7 +6,8 @@
  */
 
 var ImpSearchesPrefs = {
-    // Variables set by other code: confirm_delete_vfolder, mailboxids
+    // Variables set by PHP script: confirm_delete_filter,
+    //     confirm_delete_vfolder, mailboxids
 
     clickHandler: function(e)
     {
@@ -17,7 +18,13 @@ var ImpSearchesPrefs = {
         var elt = e.element();
 
         while (Object.isElement(elt)) {
-            if (elt.hasClassName('vfolderdelete')) {
+            if (elt.hasClassName('filterdelete')) {
+                if (window.confirm(this.confirm_delete_filter)) {
+                    this._sendData('delete', elt.up().previous('.enabled').down('INPUT').readAttribute('name'));
+                }
+                e.stop();
+                return;
+            } else if (elt.hasClassName('vfolderdelete')) {
                 if (window.confirm(this.confirm_delete_vfolder)) {
                     this._sendData('delete', elt.up().previous('.enabled').down('INPUT').readAttribute('name'));
                 }
index cf47d74..719052b 100644 (file)
@@ -235,11 +235,7 @@ class IMP_Prefs_Ui
             break;
 
         case 'searches':
-            if ($prefs->isLocked('vfolder')) {
-                $ui->suppress[] = 'searchesmanagement';
-            } else {
-                Horde::addScriptFile('searchesprefs.js', 'imp');
-            }
+            Horde::addScriptFile('searchesprefs.js', 'imp');
             break;
 
         case 'server':
@@ -1256,20 +1252,24 @@ class IMP_Prefs_Ui
      */
     protected function _searchesManagement()
     {
-        $t = $GLOBALS['injector']->createInstance('Horde_Template');
+        global $injector, $prefs;
+
+        $t = $injector->createInstance('Horde_Template');
         $t->setOption('gettext', true);
 
-        $imp_search = $GLOBALS['injector']->getInstance('IMP_Search');
-        $mailboxids = $out = array();
+        $imp_search = $injector->getInstance('IMP_Search');
+        $fout = $mailboxids = $vout = array();
         $view_mode = IMP::getViewMode();
 
         $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER | IMP_Search::LIST_DISABLED);
+        $vfolder_locked = $prefs->isLocked('vfolder');
+
         foreach ($imp_search as $key => $val) {
             if (!$val->prefDisplay) {
                 continue;
             }
 
-            $editable = $imp_search->isVFolder($val, true);
+            $editable = !$vfolder_locked && $imp_search->isVFolder($val, true);
             $m_url = ($val->enabled && ($view_mode == 'imp'))
                 ? IMP::generateIMPUrl('mailbox.php', strval($val))->link(array('class' => 'vfolderenabled'))
                 : null;
@@ -1278,22 +1278,48 @@ class IMP_Prefs_Ui
                 $mailboxids['enable_' . $key] = strval($val);
             }
 
-            $out[] = array(
+            $vout[] = array(
                 'description' => Horde_String::truncate($val->querytext, 200),
                 'edit' => ($editable ? $imp_search->editUrl($val) : null),
                 'enabled' => $val->enabled,
+                'enabled_locked' => $vfolder_locked,
                 'key' => $key,
                 'label' => htmlspecialchars($val->label),
                 'm_url' => $m_url
             );
+        }
+        $t->set('vfolders', $vout);
+
+        $imp_search->setIteratorFilter(IMP_Search::LIST_FILTER | IMP_Search::LIST_DISABLED);
+        $filter_locked = $prefs->isLocked('filter');
+
+        foreach ($imp_search as $key => $val) {
+            $editable = !$filter_locked && $imp_search->isFilter($val, true);
+
+            if ($view_mode == 'dimp') {
+                $mailboxids['enable_' . $key] = strval($val);
+            }
 
+            $fout[] = array(
+                'description' => Horde_String::truncate($val->querytext, 200),
+                'edit' => ($editable ? $imp_search->editUrl($val) : null),
+                'enabled' => $val->enabled,
+                'enabled_locked' => $filter_locked,
+                'key' => $key,
+                'label' => htmlspecialchars($val->label)
+            );
         }
-        $t->set('vfolders', $out);
+        $t->set('filters', $fout);
 
-        Horde::addInlineJsVars(array(
-            'ImpSearchesPrefs.confirm_delete_vfolder' => _("Are you sure you want to delete this virtual folder?"),
-            'ImpSearchesPrefs.mailboxids' => $mailboxids
-        ));
+        if (empty($fout) && empty($vout)) {
+            $t->set('nosearches', true);
+        } else {
+            Horde::addInlineJsVars(array(
+                'ImpSearchesPrefs.confirm_delete_filter' => _("Are you sure you want to delete this filter?"),
+                'ImpSearchesPrefs.confirm_delete_vfolder' => _("Are you sure you want to delete this virtual folder?"),
+                'ImpSearchesPrefs.mailboxids' => $mailboxids
+            ));
+        }
 
         return $t->fetch(IMP_TEMPLATES . '/prefs/searches.html');
     }
@@ -1312,13 +1338,17 @@ class IMP_Prefs_Ui
             /* Remove 'enable_' prefix. */
             $key = substr($ui->vars->searches_data, 7);
             if ($ob = $imp_search[$key]) {
-                $GLOBALS['notification']->push(sprintf(_("Virtual Folder \"%s\" deleted."), $ob->label), 'horde.success');
+                if ($imp_search->isVFolder($ob)) {
+                    $GLOBALS['notification']->push(sprintf(_("Virtual Folder \"%s\" deleted."), $ob->label), 'horde.success');
+                } elseif ($imp_search->isFilter($ob)) {
+                    $GLOBALS['notification']->push(sprintf(_("Filter \"%s\" deleted."), $ob->label), 'horde.success');
+                }
                 unset($imp_search[$key]);
             }
             break;
 
         default:
-            /* Update enabled status. */
+            /* Update enabled status for Virtual Folders. */
             $imp_search->setIteratorFilter(IMP_Search::LIST_VFOLDER | IMP_Search::LIST_DISABLED);
             $vfolders = array();
 
@@ -1333,6 +1363,17 @@ class IMP_Prefs_Ui
                 }
             }
             $imp_search->setVFolders($vfolders);
+
+            /* Update enabled status for Filters. */
+            $imp_search->setIteratorFilter(IMP_Search::LIST_FILTER | IMP_Search::LIST_DISABLED);
+            $filters = array();
+
+            foreach ($imp_search as $key => $val) {
+                $form_key = 'enable_' . $key;
+                $val->enabled = !empty($ui->vars->$form_key);
+                $filters[$key] = $val;
+            }
+            $imp_search->setFilters($filters);
             break;
         }
     }
index 5c17a8f..0b94593 100644 (file)
@@ -24,13 +24,15 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
     const DIMP_QUICKSEARCH = 'dimpqsearch';
 
     /* Bitmask filters for iterator. */
-    const LIST_SEARCH = 1;
-    const LIST_VFOLDER = 2;
-    const LIST_DISABLED = 4;
+    const LIST_FILTER = 1;
+    const LIST_QUERY = 2;
+    const LIST_VFOLDER = 4;
+    const LIST_DISABLED = 8;
 
     /* Query creation types. */
-    const CREATE_QUERY = 1;
-    const CREATE_VFOLDER = 2;
+    const CREATE_FILTER = 1;
+    const CREATE_QUERY = 2;
+    const CREATE_VFOLDER = 3;
 
     /**
      * Has the object data changed?
@@ -63,6 +65,7 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
      * @var array
      */
     protected $_search = array(
+        'filters' => array(),
         'query' => array(),
         'vfolders' => array()
     );
@@ -72,6 +75,7 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
      */
     public function init()
     {
+        $this->setFilters($this->getFilters(), false);
         $this->setVFolders($this->getVFolders(), false);
     }
 
@@ -197,8 +201,9 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
      * @param string $id       Use as the mailbox ID.
      *
      * @return IMP_Search_Query  Returns the query object.
+     * @throws InvalidArgumentException
      */
-    public function createQuery($criteria, $mboxes, $label = null,
+    public function createQuery($criteria, $mboxes = array(), $label = null,
                                 $type = self::CREATE_QUERY, $id = null)
     {
         if (!is_null($id)) {
@@ -206,12 +211,22 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
         }
 
         switch ($type) {
+        case self::CREATE_FILTER:
+            $cname = 'IMP_Search_Filter';
+            break;
+
         case self::CREATE_QUERY:
             $cname = 'IMP_Search_Query';
+            if (empty($mboxes)) {
+                throw new InvalidArgumentException('Search query requires at least one mailbox.');
+            }
             break;
 
         case self::CREATE_VFOLDER:
             $cname = 'IMP_Search_Vfolder';
+            if (empty($mboxes)) {
+                throw new InvalidArgumentException('Search query requires at least one mailbox.');
+            }
             break;
         }
 
@@ -223,6 +238,12 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
         )));
 
         switch ($type) {
+        case self::CREATE_FILTER:
+            /* This will overwrite previous value, if it exists. */
+            $this->_search['filters'][$ob->id] = $ob;
+            $this->setFilters($this->_search['filters']);
+            break;
+
         case self::CREATE_QUERY:
             $this->_search['query'][$ob->id] = $ob;
             break;
@@ -240,6 +261,83 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
     }
 
     /**
+     * Obtains the list of filters for the current user.
+     *
+     * @return array  The list of filters.  Keys are mailbox IDs, values are
+     *                IMP_Search_Filter objects.
+     */
+    public function getFilters()
+    {
+        if ($f_list = $GLOBALS['prefs']->getValue('filter')) {
+            $f_list = @unserialize($f_list);
+        }
+
+        $filters = array();
+
+        if (is_array($f_list)) {
+            foreach ($f_list as $val) {
+                if ($val instanceof IMP_Search_Filter) {
+                    $filters[$val->id] = $val;
+                }
+            }
+        }
+
+        return $filters;
+    }
+
+    /**
+     * Saves the list of filters for the current user.
+     *
+     * @param array $filters  The filter list.
+     * @param boolean $save   Save the filter list to the preference backend?
+     */
+    public function setFilters($filters, $save = true)
+    {
+        if ($save) {
+            $GLOBALS['prefs']->setValue('filter', serialize(array_values($filters)));
+        }
+
+        $this->_search['filters'] = $filters;
+        $this->changed = true;
+    }
+
+    /**
+     * Is a mailbox a filter query?
+     *
+     * @param string $id         The mailbox ID.
+     * @param boolean $editable  Is this an editable (i.e. not built-in)
+     *                           filter query?
+     */
+    public function isFilter($id, $editable = false)
+    {
+        return (isset($this->_search['filters'][$this->_strip($id)]) &&
+                (!$editable || $this[$id]->canEdit));
+    }
+
+    /**
+     * Converts a filter to a search query and stores it in the local
+     * session.
+     *
+     * @param string $id     The mailbox ID of the filter.
+     * @param array $mboxes  The list of mailboxes to apply the filter on.
+     *
+     * @return IMP_Search_Query  The created query object.
+     * @throws InvalidArgumentException
+     */
+    public function applyFilter($id, array $mboxes)
+    {
+        if (!$this->isFilter($id)) {
+            throw new InvalidArgumentException('Invalid filter ID given.');
+        }
+
+        $q_ob = $this[$id]->toQuery($mboxes);
+        $this->_search['query'][$q_ob->id] = $q_ob;
+        $this->changed = true;
+
+        return $q_ob;
+    }
+
+    /**
      * Obtains the list of virtual folders for the current user.
      *
      * @return array  The list of virtual folders.  Keys are mailbox IDs,
@@ -416,17 +514,24 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
     public function offsetExists($offset)
     {
         $id = $this->_strip($offset);
-        return (isset($this->_search['query'][$id]) ||
-                isset($this->_search['vfolders'][$id]));
+
+        foreach (array_keys($this->_search) as $key) {
+            if (isset($this->_search[$key][$id])) {
+                return true;
+            }
+        }
+
+        return false;
     }
 
     public function offsetGet($offset)
     {
         $id = $this->_strip($offset);
-        if (isset($this->_search['query'][$id])) {
-            return $this->_search['query'][$id];
-        } elseif (isset($this->_search['vfolders'][$id])) {
-            return $this->_search['vfolders'][$id];
+
+        foreach (array_keys($this->_search) as $key) {
+            if (isset($this->_search[$key][$id])) {
+                return $this->_search[$key][$id];
+            }
         }
 
         return null;
@@ -447,12 +552,12 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
         }
 
         $id = $this->_strip($offset);
-        if (isset($this->_search['query'][$id])) {
-            $this->_search['query'][$id] = $value;
-            return;
-        } elseif (isset($this->_search['vfolders'][$id])) {
-            $this->_search['vfolders'][$id] = $value;
-            return;
+
+        foreach (array_keys($this->_search) as $key) {
+            if (isset($this->_search[$key][$id])) {
+                $this->_search[$key][$id] = $value;
+                return;
+            }
         }
 
         throw new InvalidArgumentException('Creating search queries by array index is not supported. Use createQuery() instead.');
@@ -542,7 +647,8 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
      *
      * @param integer $mask  A mask with the following possible elements:
      * <pre>
-     * IMP_Search::LIST_SEARCH
+     * IMP_Search::LIST_FILTER
+     * IMP_Search::LIST_QUERY
      * IMP_Search::LIST_VFOLDER
      * </pre>
      */
@@ -569,17 +675,18 @@ class IMP_Search implements ArrayAccess, Iterator, Serializable
                 return true;
             }
 
-            if (($this->_filter & self::LIST_VFOLDER) &&
-                $this->isVfolder($ob)) {
+            if (($this->_filter & self::LIST_FILTER) &&
+                $this->isFilter($ob)) {
                 return true;
             }
 
-            if (($this->_filter & self::LIST_SEARCH) &&
-                !$this->isVfolder($ob)) {
+            if (($this->_filter & self::LIST_QUERY) &&
+                $this->isQuery($ob)) {
                 return true;
             }
 
-            if ($this->isQuery($ob, true)) {
+            if (($this->_filter & self::LIST_VFOLDER) &&
+                $this->isVfolder($ob)) {
                 return true;
             }
         }
diff --git a/imp/lib/Search/Filter.php b/imp/lib/Search/Filter.php
new file mode 100644 (file)
index 0000000..70f43de
--- /dev/null
@@ -0,0 +1,58 @@
+<?php
+/**
+ * This class provides a data structure for storing a stored filter.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Filter extends IMP_Search_Query
+{
+    /**
+     * Get object properties.
+     *
+     * @see parent::__get()
+     */
+    public function __get($name)
+    {
+        switch ($name) {
+        case 'querytext':
+            $text = array(_("Search"));
+
+            foreach ($this->_criteria as $elt) {
+                $text[] = $elt->queryText();
+                if (!($elt instanceof IMP_Search_Element_Or)) {
+                    $text[] = _("and");
+                }
+            }
+            array_pop($text);
+
+            return implode(' ', $text);
+        }
+
+        return parent::__get($name);
+    }
+
+    /**
+     * Creates a query object from this filter.
+     *
+     * @param array $mboxes  The list of mailboxes to apply the filter to.
+     *
+     * @return IMP_Search_Query  A query object.
+     */
+    public function toQuery(array $mboxes)
+    {
+        return new IMP_Search_Query(array(
+            'add' => $this->_criteria,
+            'label' => $this->label,
+            'mboxes' => $mboxes
+        ));
+    }
+
+}
diff --git a/imp/lib/Search/Filter/Bulk.php b/imp/lib/Search/Filter/Bulk.php
new file mode 100644 (file)
index 0000000..4fde711
--- /dev/null
@@ -0,0 +1,69 @@
+<?php
+/**
+ * This class provides a filter for bulk mail.
+ *
+ * Copyright 2010 The Horde Project (http://www.horde.org/)
+ *
+ * See the enclosed file COPYING for license information (GPL). If you
+ * did not receive this file, see http://www.fsf.org/copyleft/gpl.html.
+ *
+ * @author   Michael Slusarz <slusarz@horde.org>
+ * @category Horde
+ * @license  http://www.fsf.org/copyleft/gpl.html GPL
+ * @package  IMP
+ */
+class IMP_Search_Filter_Bulk extends IMP_Search_Filter
+{
+    /**
+     * Can this query be edited?
+     *
+     * @var boolean
+     */
+    protected $_canEdit = false;
+
+    /**
+     * List of serialize entries not to save.
+     *
+     * @var array
+     */
+    protected $_nosave = array('i', 'l');
+
+    /**
+     * Constructor.
+     *
+     * The 'add', 'id', 'label', and 'mboxes' parameters are ignored.
+     *
+     * @see parent::__construct()
+     */
+    public function __construct(array $opts = array())
+    {
+        $this->enabled = empty($opts['disable']);
+
+        $this->add(new IMP_Search_Element_Bulk());
+
+        $this->_init();
+    }
+
+    /**
+     * Initialization tasks.
+     */
+    protected function _init()
+    {
+        $this->_id = 'filter_bulk';
+        $this->_label = _("Bulk Messages");
+    }
+
+    /**
+     * Unserialization.
+     *
+     * @param string $data  Serialized data.
+     *
+     * @throws Exception
+     */
+    public function unserialize($data)
+    {
+        parent::unserialize($data);
+        $this->_init();
+    }
+
+}
index 14ea04b..4d57ac8 100644 (file)
@@ -90,7 +90,7 @@ if (!$search_mbox) {
 /* Determine if mailbox is readonly. */
 $readonly = $imp_imap->isReadOnly(IMP::$mailbox);
 if ($readonly &&
-    in_array($actionID, array('delete_messages', 'undelete_messages', 'move_messages', 'flag_messages', 'empty_mailbox', 'filter'))) {
+    in_array($actionID, array('delete_messages', 'undelete_messages', 'move_messages', 'flag_messages', 'filter_messages', 'empty_mailbox', 'filter'))) {
     $actionID = null;
 }
 
@@ -160,13 +160,21 @@ case 'copy_messages':
     break;
 
 case 'flag_messages':
-    $flag = Horde_Util::getPost('flag');
-    if ($flag && count($indices)) {
-        $flag = $imp_flags->parseFormId($flag);
+    if ($vars->flag && count($indices)) {
+        $flag = $imp_flags->parseFormId($vars->flag);
         $injector->getInstance('IMP_Message')->flag(array($flag['flag']), $indices, $flag['set']);
     }
     break;
 
+case 'filter_messages':
+    $filter = IMP::formMbox($vars->filter, false);
+    try {
+        $q_ob = $imp_search->applyFilter($filter, array(IMP::$mailbox));
+        Horde::url('mailbox.php', true)->add('mailbox', strval($q_ob))->redirect();
+        exit;
+    } catch (InvalidArgumentException $e) {}
+    break;
+
 case 'hide_deleted':
     $prefs->setValue('delhide', !$prefs->getValue('delhide'));
     IMP::hideDeletedMsgs(IMP::$mailbox, true);
@@ -467,6 +475,20 @@ if ($pageOb['msgcount']) {
         $n_template->set('flaglist_set', $tmp['set']);
         $n_template->set('flaglist_unset', $tmp['unset']);
 
+        if (!$search_mbox) {
+            $filters = array();
+            $imp_search->setIteratorFilter(IMP_Search::LIST_FILTER);
+            foreach ($imp_search as $val) {
+                $filters[] = array(
+                    'l' => htmlspecialchars($val->label),
+                    'v' => IMP::formMbox(strval($val), true)
+                );
+            }
+            if (!empty($filters)) {
+                $n_template->set('filters', $filters);
+            }
+        }
+
         if ($n_template->get('use_folders')) {
             $n_template->set('move', Horde::widget('#', _("Move to folder"), 'widget moveAction', '', '', _("Move"), true));
             $n_template->set('copy', Horde::widget('#', _("Copy to folder"), 'widget copyAction', '', '', _("Copy"), true));
index 84e66bc..36183f8 100644 (file)
@@ -8,13 +8,15 @@
  * ---------------
  * 'criteria_form' - (string) JSON representation of the search query.
  * 'edit_query' - (string) The search query to edit.
- * 'edit_query_vfolder' - (string) The name of the vfolder being edited.
+ * 'edit_query_filter' - (string) The name of the filter being edited.
+ * 'edit_query_vfolder' - (string) The name of the virtual folder being
+ *                        edited.
  * 'folder_list' - (array) The list of folders to add to the query.
  * 'search_label' - (string) The label to use when saving the search.
  * 'search_mailbox' - (string) Use this mailbox as the default value.
  *                    DEFAULT: INBOX
- * 'search_save' - (integer) If set, save search.
- * 'search_type' - (string) The type of saved search ('vfolder').
+ * 'search_type' - (string) The type of saved search ('filter', 'vfolder').
+ *                 If empty, the search should not be saved.
  *
  * Copyright 1999-2010 The Horde Project (http://www.horde.org/)
  *
@@ -145,10 +147,10 @@ $flist = $imp_flags->getFlagList($search_mailbox);
 /* Generate the search query if 'criteria_form' is present in the form
  * data. */
 if ($vars->criteria_form) {
-    $criteria = Horde_Serialize::unserialize($vars->criteria_form, Horde_Serialize::JSON);
+    $c_data = Horde_Serialize::unserialize($vars->criteria_form, Horde_Serialize::JSON);
     $c_list = array();
 
-    foreach ($criteria as $val) {
+    foreach ($c_data as $val) {
         switch ($val->t) {
         case 'from':
         case 'to':
@@ -233,45 +235,74 @@ if ($vars->criteria_form) {
         }
     }
 
-    /* Save the search if requested. */
-    if ($vars->search_save) {
-        switch ($vars->search_type) {
-        case 'vfolder':
-            $q_ob = $imp_search->createQuery(
-                $c_list,
-                $vars->folder_list,
-                $vars->search_label,
-                IMP_Search::CREATE_VFOLDER,
-                IMP::formMbox($vars->edit_query_vfolder, false)
-            );
+    $redirect_dimp = true;
+    $redirect_target = false;
+
+    switch ($vars->search_type) {
+    case 'filter':
+        $q_ob = $imp_search->createQuery(
+            $c_list,
+            array(),
+            $vars->search_label,
+            IMP_Search::CREATE_FILTER,
+            IMP::formMbox($vars->edit_query_filter, false)
+        );
 
-            if ($vars->edit_query_vfolder) {
-                $notification->push(sprintf(_("Virtual Folder \"%s\" edited successfully."), $vars->search_label), 'horde.success');
-                if ($dimp_view) {
-                    IMP_Dimp::returnToDimp(strval($q_ob));
-                }
-                Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
-                exit;
-            }
+        if ($vars->edit_query_filter) {
+            $notification->push(sprintf(_("Filter \"%s\" edited successfully."), $vars->search_label), 'horde.success');
+            $redirect_dimp = false;
+            $redirect_target = 'prefs';
+        } else {
+            $notification->push(sprintf(_("Filter \"%s\" created succesfully."), $vars->search_label), 'horde.success');
+        }
+        break;
 
+    case 'vfolder':
+        $q_ob = $imp_search->createQuery(
+            $c_list,
+            $vars->folder_list,
+            $vars->search_label,
+            IMP_Search::CREATE_VFOLDER,
+            IMP::formMbox($vars->edit_query_vfolder, false)
+        );
+
+        if ($vars->edit_query_vfolder) {
+            $notification->push(sprintf(_("Virtual Folder \"%s\" edited successfully."), $vars->search_label), 'horde.success');
+            $redirect_target = 'prefs';
+        } else {
             $notification->push(sprintf(_("Virtual Folder \"%s\" created succesfully."), $vars->search_label), 'horde.success');
-            break;
+            $redirect_target = 'mailbox';
         }
-    } else {
-        /* Set the search in the session. */
+        break;
+
+    default:
         $q_ob = $imp_search->createQuery(
             $c_list,
             $vars->folder_list
         );
+        $redirect_target = 'mailbox';
+        break;
     }
 
     /* Redirect to the mailbox page. */
-    if ($dimp_view) {
-        IMP_Dimp::returnToDimp(strval($q_ob));
-    }
+    if ($redirect_target) {
+        if ($dimp_view && $redirect_dimp) {
+            IMP_Dimp::returnToDimp(strval($q_ob));
+            exit;
+        }
 
-    Horde::url('mailbox.php', true)->add('mailbox', strval($q_ob))->redirect();
-    exit;
+        switch ($redirect_target) {
+        case 'mailbox':
+            Horde::url('mailbox.php', true)->add('mailbox', strval($q_ob))->redirect();
+            break;
+
+        case 'prefs':
+            Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
+            break;
+        }
+
+        exit;
+    }
 }
 
 /* Preselect mailboxes. */
@@ -288,19 +319,30 @@ if ($vars->edit_query && $imp_search->isSearchMbox($vars->edit_query)) {
     $q_ob = $imp_search[$vars->edit_query];
     if ($imp_search->isVFolder($q_ob)) {
         if (!$imp_search->isVFolder($q_ob, true)) {
-            $notification->push(_("Special Virtual Folders cannot be edited."), 'horde.error');
+            $notification->push(_("Built-in Virtual Folders cannot be edited."), 'horde.error');
             Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
         }
+        $t->set('edit_query', true);
         $t->set('edit_query_vfolder', IMP::formMbox($q_ob, true));
-        $t->set('search_label', htmlspecialchars($q_ob->label));
+    } elseif ($imp_search->isFilter($q_ob)) {
+        if (!$imp_search->isFilter($q_ob, true)) {
+            $notification->push(_("Built-in Filters cannot be edited."), 'horde.error');
+            Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->redirect();
+        }
+        $t->set('edit_query', true);
+        $t->set('edit_query_filter', IMP::formMbox($q_ob, true));
+    }
 
+    if ($t->get('edit_query')) {
+        $t->set('search_label', htmlspecialchars($q_ob->label));
         $js_vars['ImpSearch.prefsurl'] = strval(Horde::getServiceLink('prefs', 'imp')->add('group', 'searches')->setRaw(true));
     }
+
     $js_vars['ImpSearch.i_criteria'] = $q_ob->criteria;
 } else {
     /* Process list of recent searches. */
     $rs = array();
-    $imp_search->setIteratorFilter(IMP_Search::LIST_SEARCH);
+    $imp_search->setIteratorFilter(IMP_Search::LIST_QUERY);
     foreach ($imp_search as $val) {
         $rs[$val->id] = array(
             'c' => $val->criteria,
@@ -348,10 +390,12 @@ foreach ($flist['set'] as $val) {
 $t->set('flist', $flag_set);
 
 /* Generate master folder list. */
-$tree = $injector->getInstance('IMP_Imap_Tree')->createTree('imp_search', array(
-    'checkbox' => true,
-));
-$t->set('tree', $tree->getTree());
+if (!$t->get('edit_query_filter')) {
+    $tree = $injector->getInstance('IMP_Imap_Tree')->createTree('imp_search', array(
+        'checkbox' => true,
+    ));
+    $t->set('tree', $tree->getTree());
+}
 
 Horde_Core_Ui_JsCalendar::init();
 Horde::addScriptFile('horde.js', 'horde');
index 903a32a..6700b43 100644 (file)
@@ -7,3 +7,4 @@
  <input type="hidden" id="targetMbox" name="targetMbox" value="" />
  <input type="hidden" id="newMbox" name="newMbox" value="0" />
  <input type="hidden" id="flag" name="flag" value="" />
+ <input type="hidden" id="filter" name="filter" value="" />
index dc211fb..54ccd3c 100644 (file)
@@ -4,24 +4,37 @@
 <if:readonly><else:readonly>
 <if:use_pop><else:use_pop>
    <div class="leftFloat">
-    <form id="select<tag:id />">
-    <input type="hidden" name="mailbox" value="<tag:mailbox />" />
-    <label for="flag<tag:id />" class="hidden"><gettext>Mark Messages</gettext></label>
-    <select id="flag<tag:id />" name="flag">
-     <option value="" selected="selected"><gettext>Mark Messages</gettext></option>
-     <option value="" disabled="disabled">- - - - - - - -</option>
-     <option value=""><gettext>Mark as:</gettext></option>
+    <form class="navbarselect">
+     <label for="flag<tag:id />" class="hidden"><gettext>Mark Messages</gettext></label>
+     <select id="flag<tag:id />" name="flag">
+      <option value="" selected="selected"><gettext>Mark Messages</gettext></option>
+      <option value="" disabled="disabled">- - - - - - - -</option>
+      <option value=""><gettext>Mark as:</gettext></option>
 <loop:flaglist_set>
-     <option value="<tag:flaglist_set.f />">&nbsp;&nbsp;<tag:flaglist_set.l /></option>
+      <option value="<tag:flaglist_set.f />">&nbsp;&nbsp;<tag:flaglist_set.l /></option>
 </loop:flaglist_set>
-     <option value="" disabled="disabled">- - - - - - - -</option>
-     <option value=""><gettext>Unmark as:</gettext></option>
+      <option value="" disabled="disabled">- - - - - - - -</option>
+      <option value=""><gettext>Unmark as:</gettext></option>
 <loop:flaglist_unset>
-     <option value="<tag:flaglist_unset.f />">&nbsp;&nbsp;<tag:flaglist_unset.l /></option>
+      <option value="<tag:flaglist_unset.f />">&nbsp;&nbsp;<tag:flaglist_unset.l /></option>
 </loop:flaglist_unset>
-    </select>
+     </select>
+    </form>
+   </div>
+<if:filters>
+   <div class="leftFloat">
+    <form class="navbarselect">
+     <label for="filter<tag:id />" class="hidden"><gettext>Filter Messages</gettext></label>
+     <select id="filter<tag:id />" name="filter">
+      <option value="" selected="selected"><gettext>Filter Messages</gettext></option>
+      <option value="" disabled="disabled">- - - - - - - -</option>
+<loop:filters>
+      <option value="<tag:filters.v />"><tag:filters.l /></option>
+</loop:filters>
+     </select>
     </form>
    </div>
+</if:filters>
 </else:use_pop></if:use_pop>
 <if:use_folders>
    <div class="leftFloat" style="padding-left:10px">
index 7b2708c..c553b94 100644 (file)
@@ -6,7 +6,11 @@
 <if:edit_query_vfolder>
    <gettext>Edit Virtual Folder</gettext>
 <else:edit_query_vfolder>
+<if:edit_query_filter>
+   <gettext>Edit Filter</gettext>
+<else:edit_query_filter>
    <gettext>Search</gettext>
+</else:edit_query_filter></if:edit_query_filter>
 </else:edit_query_vfolder></if:edit_query_vfolder>
   </strong>
  </h1>
@@ -53,6 +57,7 @@
   </div>
  </div>
 
+<if:edit_query_filter><else:edit_query_filter>
  <div class="smallheader leftAlign" id="search_folders_hdr">
   <span class="searchuiImg arrowExpanded" style="display:none"></span>
   <span class="searchuiImg arrowCollapsed"></span>
@@ -66,6 +71,7 @@
  <div class="item" id="search_folders_tree" style="display:none">
   <tag:tree />
  </div>
+</else:edit_query_filter></if:edit_query_filter>
 
 <if:virtualfolder>
  <div class="smallheader leftAlign">
 
  <div style="display:none">
 <if:edit_query_vfolder>
-  <input type="hidden" name="search_save" id="search_save" value="1" />
-  <input type="hidden" name="search_type" value="vfolder" />
+  <input type="hidden" id="search_type" name="search_type" value="vfolder" />
   <input type="hidden" name="edit_query_vfolder" value="<tag:edit_query_vfolder />" />
 <else:edit_query_vfolder>
-  <div class="item">
-   <input type="checkbox" class="checkbox" id="search_save" name="search_save" /> <label for="search_save"><gettext>Save search?</gettext></label>
-  </div>
+<if:edit_query_filter>
+  <input type="hidden" id="search_type" name="search_type" value="filter" />
+  <input type="hidden" name="edit_query_filter" value="<tag:edit_query_filter />" />
+<else:edit_query_filter>
   <div class="item">
    <label for="search_type"><gettext>Type:</gettext></label>
    <select id="search_type" name="search_type">
+    <option value="" selected="selected">- - - - - -</option>
     <option value="filter"><gettext>Filter</gettext></option>
     <option value="vfolder"><gettext>Virtual Folder</gettext></option>
    </select>
   </div>
+</else:edit_query_filter></if:edit_query_filter>
 </else:edit_query_vfolder></if:edit_query_vfolder>
   <div class="item">
    <label for="search_label"><gettext>Label:</gettext></label> <input type="text" name="search_label" id="search_label"<if:search_label> value="<tag:search_label />"</if:search_label> />
 </if:virtualfolder>
 
  <div class="searchuiButtons">
-<if:edit_query_vfolder>
+<if:edit_query>
   <input type="button" id="search_submit" class="button" value="<gettext>Save</gettext>" />
   <input type="button" id="search_edit_query_cancel" class="button" value="<gettext>Cancel</gettext>" />
-<else:edit_query_vfolder>
+<else:edit_query>
   <input type="button" id="search_submit" class="button" value="<gettext>Submit</gettext>" />
   <input type="button" id="search_reset" class="button" value="<gettext>Reset</gettext>" />
-</else:edit_query_vfolder></if:edit_query_vfolder>
+</else:edit_query></if:edit_query>
 <if:return_mailbox_val>
   <input type="button" id="search_dimp_return" class="button" value="<tag:return_mailbox_val />" />
 </if:return_mailbox_val>
index 98d8501..9928219 100644 (file)
@@ -1,5 +1,7 @@
 <input type="hidden" name="searches_action" id="searches_action" />
 <input type="hidden" name="searches_data" id="searches_data" />
+
+<if:vfolders>
 <table class="searchesmanagement">
  <thead>
   <tr>
@@ -23,7 +25,7 @@
 </else:vfolders.enabled></if:vfolders.enabled>
    </td>
    <td class="enabled">
-    <input class="checkbox" type="checkbox" name="enable_<tag:vfolders.key />"<if:vfolders.enabled> checked="checked"</if:vfolders.enabled> />
+    <input class="checkbox" type="checkbox" name="enable_<tag:vfolders.key />"<if:vfolders.enabled> checked="checked"</if:vfolders.enabled><if:vfolders.enabled_locked> disabled="disabled"</if:vfolders.enabled_locked> />
    </td>
    <td>
 <if:vfolders.edit>
    </td>
   </tr>
   <tr>
-   <td colspan="3" class="fixed vfolderdescription">
+   <td colspan="3" class="fixed searchdescription">
     <tag:vfolders.description />
    </td>
 </loop:vfolders>
  </tbody>
 </table>
+</if:vfolders>
+
+<if:filters>
+<table class="searchesmanagement">
+ <thead>
+  <tr>
+   <td><gettext>Filter</gettext></td>
+   <td><gettext>Enabled?</gettext></td>
+   <td><gettext>Actions</gettext></td>
+  </tr>
+ </thead>
+ <tbody>
+<loop:filters>
+  <tr>
+   <td>
+    <tag:filters.label />
+   </td>
+   <td class="enabled">
+    <input class="checkbox" type="checkbox" name="enable_<tag:filters.key />"<if:filters.enabled> checked="checked"</if:filters.enabled><if:filters.enabled_locked> disabled="disabled"</if:filters.enabled_locked> />
+   </td>
+   <td>
+<if:filters.edit>
+    <a class="filteredit" href="<tag:filters.edit />"><span class="editImg"></span></a>
+    <a class="filterdelete" href="#"><span class="deleteImg"></span></a>
+<else:filters.edit>
+     <gettext>No Actions Available</gettext>
+</else:filters.edit></if:filters.edit>
+   </td>
+  </tr>
+  <tr>
+   <td colspan="3" class="fixed searchdescription">
+    <tag:filters.description />
+   </td>
+</loop:filters>
+ </tbody>
+</table>
+</if:filters>
+
+<if:nosearches>
+<div>
+ <em><gettext>No Saved Searches Defined.</gettext></em>
+</div>
+</if:nosearches>
index 51bb6b0..f474867 100644 (file)
@@ -401,7 +401,7 @@ table.flagmanagement tbody td.flagicon {
     text-align: center;
 }
 
-table.searchesmanagement td.vfolderdescription {
+table.searchesmanagement td.searchdescription {
     background-color: #ffa;
     width: 500px;
 }