diff --git a/css/views-admin.theme.css b/css/views-admin.theme.css
index df45cdf..4564e7e 100644
--- a/css/views-admin.theme.css
+++ b/css/views-admin.theme.css
@@ -380,6 +380,7 @@ td.group-title {
   text-transform: uppercase;
 }
 
+.grouped-description,
 .exposed-description {
   float: left;
   padding-top: 3px;
diff --git a/handlers/views_handler_filter.inc b/handlers/views_handler_filter.inc
index b8c6ac1..a81caa1 100644
--- a/handlers/views_handler_filter.inc
+++ b/handlers/views_handler_filter.inc
@@ -44,6 +44,11 @@ class views_handler_filter extends views_handler {
   var $operator = '=';
 
   /**
+   * Contains the information of the selected item in a gruped filter.
+   */
+  var $group_info = NULL;
+
+  /**
    * @var bool
    * Disable the possibility to force a single value.
    */
@@ -72,6 +77,7 @@ class views_handler_filter extends views_handler {
 
     $this->operator = $this->options['operator'];
     $this->value = $this->options['value'];
+    $this->group_info = $this->options['group_info']['default_group'];
 
     // Compatibility: The new UI changed several settings.
     if (!empty($options['exposed']) && !empty($options['expose']['optional']) && !isset($options['expose']['required'])) {
@@ -84,6 +90,11 @@ class views_handler_filter extends views_handler {
       $this->options['expose']['operator_id'] = $options['expose']['operator_id'] = $options['expose']['operator'];
     }
 
+    if ($this->multiple_exposed_input()) {
+      $this->group_info = array_filter($options['group_info']['default_group_multiple']);
+      $this->options['expose']['multiple'] = TRUE;
+    }
+
     // If there are relationships in the view, allow empty should be true
     // so that we can do IS NULL checks on items. Not all filters respect
     // allow empty, but string and numeric do and that covers enough.
@@ -112,6 +123,29 @@ class views_handler_filter extends views_handler {
       ),
     );
 
+    // A group is a combination of a filter, an operator and a value
+    // operating like a single filter.
+    // Users can choose from a select box which group they want to apply.
+    // Views will filter the view according to the defined values.
+    // Because it acts as a standard filter, we have to define
+    // an identifier and other settings like the widget and the label.
+    // This settings are saved in another array to allow users to switch
+    // between a normal filter and a group of filters with a single click.
+    $options['is_grouped'] = array('default' => FALSE, 'bool' => TRUE);
+    $options['group_info'] = array(
+      'contains' => array(
+        'label' => array('default' => '', 'translatable' => TRUE),
+        'identifier' => array('default' => ''),
+        'optional' => array('default' => TRUE, 'bool' => TRUE),
+        'widget' => array('default' => 'select'),
+        'multiple' => array('default' => FALSE, 'bool' => TRUE),
+        'remember' => array('default' => 0),
+        'default_group' => array('default' => 'All'),
+        'default_group_multiple' => array('default' => array()),
+        'group_items' => array('default' => array()),
+      ),
+    );
+
     return $options;
   }
 
@@ -128,6 +162,21 @@ class views_handler_filter extends views_handler {
   function can_expose() { return TRUE; }
 
   /**
+   * Determine if a filter can be converted into a group.
+   * Only exposed filters with operators available can be converted into groups.
+   */
+  function can_build_group() {
+    return $this->is_exposed() && (count($this->operator_options()) > 0);
+  }
+
+  /**
+   * Returns TRUE if the exposed filter works like a grouped filter.
+   */
+  function is_a_group() {
+    return !empty($this->options['is_grouped']);
+  }
+
+  /**
    * Provide the basic form which calls through to subforms.
    * If overridden, it is best to call through to the parent,
    * or to at least make sure all of the functions in this form
@@ -138,19 +187,36 @@ class views_handler_filter extends views_handler {
     if ($this->can_expose()) {
       $this->show_expose_button($form, $form_state);
     }
+    if ($this->can_build_group()) {
+      $this->show_build_group_button($form, $form_state);
+    }
     $form['clear_markup_start'] = array(
       '#markup' => '<div class="clearfix">',
     );
-    // Add the subform from operator_form().
-    $this->show_operator_form($form, $form_state);
-    // Add the subform from value_form().
-    $this->show_value_form($form, $form_state);
-    $form['clear_markup_end'] = array(
-      '#markup' => '</div>',
-    );
-    if ($this->can_expose()) {
-      // Add the subform from expose_form().
-      $this->show_expose_form($form, $form_state);
+    if ($this->is_a_group()) {
+      if ($this->can_build_group()) {
+        $form['clear_markup_start'] = array(
+          '#markup' => '<div class="clearfix">',
+        );
+        // Render the build group form.
+        $this->show_build_group_form($form, $form_state);
+        $form['clear_markup_end'] = array(
+          '#markup' => '</div>',
+        );
+      }
+    }
+    else {
+      // Add the subform from operator_form().
+      $this->show_operator_form($form, $form_state);
+      // Add the subform from value_form().
+      $this->show_value_form($form, $form_state);
+      $form['clear_markup_end'] = array(
+        '#markup' => '</div>',
+      );
+      if ($this->can_expose()) {
+        // Add the subform from expose_form().
+        $this->show_expose_form($form, $form_state);
+      }
     }
   }
 
@@ -160,9 +226,12 @@ class views_handler_filter extends views_handler {
   function options_validate(&$form, &$form_state) {
     $this->operator_validate($form, $form_state);
     $this->value_validate($form, $form_state);
-    if (!empty($this->options['exposed'])) {
+    if (!empty($this->options['exposed']) && !$this->is_a_group()) {
       $this->expose_validate($form, $form_state);
     }
+    if ($this->is_a_group()) {
+      $this->build_group_validate($form, $form_state);
+    }
   }
 
   /**
@@ -170,11 +239,15 @@ class views_handler_filter extends views_handler {
    */
   function options_submit(&$form, &$form_state) {
     unset($form_state['values']['expose_button']); // don't store this.
+    unset($form_state['values']['group_button']); // don't store this.
     $this->operator_submit($form, $form_state);
     $this->value_submit($form, $form_state);
     if (!empty($this->options['exposed'])) {
       $this->expose_submit($form, $form_state);
     }
+    if ($this->is_a_group()) {
+      $this->build_group_submit($form, $form_state);
+    }
   }
 
   /**
@@ -256,6 +329,78 @@ class views_handler_filter extends views_handler {
   function value_submit($form, &$form_state) { }
 
   /**
+   * Shortcut to display the exposed options form.
+   */
+  function show_build_group_form(&$form, &$form_state) {
+    if (empty($this->options['is_grouped'])) {
+      return;
+    }
+
+    $this->build_group_form($form, $form_state);
+
+    // When we click the expose button, we add new gadgets to the form but they
+    // have no data in $_POST so their defaults get wiped out. This prevents
+    // these defaults from getting wiped out. This setting will only be TRUE
+    // during a 2nd pass rerender.
+    if (!empty($form_state['force_build_group_options'])) {
+      foreach (element_children($form['group_info']) as $id) {
+        if (isset($form['group_info'][$id]['#default_value']) && !isset($form['group_info'][$id]['#value'])) {
+          $form['group_info'][$id]['#value'] = $form['group_info'][$id]['#default_value'];
+        }
+      }
+    }
+  }
+
+  /**
+   * Shortcut to display the build_group/hide button.
+   */
+  function show_build_group_button(&$form, &$form_state) {
+
+    $form['group_button'] = array(
+      '#prefix' => '<div class="views-grouped clearfix">',
+      '#suffix' => '</div>',
+      // Should always come after the description and the relationship.
+      '#weight' => -190,
+    );
+
+    $grouped_description = t('Grouped filters allow a choice between predefined operator|value pairs.');
+    $form['group_button']['radios'] = array(
+      '#theme_wrappers' => array('container'),
+      '#attributes' => array('class' => array('js-only')),
+    );
+    $form['group_button']['radios']['radios'] = array(
+      '#title' => t('Filter type to expose'),
+      '#description' => $grouped_description,
+      '#type' => 'radios',
+      '#options' => array(
+        t('Single filter'),
+        t('Grouped filters'),
+      ),
+    );
+
+    if (empty($this->options['is_grouped'])) {
+      $form['group_button']['markup'] = array(
+        '#markup' => '<div class="description grouped-description">' . $grouped_description . '</div>',
+      );
+      $form['group_button']['button'] = array(
+        '#limit_validation_errors' => array(),
+        '#type' => 'submit',
+        '#value' => t('Grouped filters'),
+        '#submit' => array('views_ui_config_item_form_build_group'),
+      );
+      $form['group_button']['radios']['radios']['#default_value'] = 0;
+    }
+    else {
+      $form['group_button']['button'] = array(
+        '#limit_validation_errors' => array(),
+        '#type' => 'submit',
+        '#value' => t('Single filter'),
+        '#submit' => array('views_ui_config_item_form_build_group'),
+      );
+      $form['group_button']['radios']['radios']['#default_value'] = 1;
+    }
+  }
+  /**
    * Shortcut to display the expose/hide button.
    */
   function show_expose_button(&$form, &$form_state) {
@@ -411,6 +556,66 @@ class views_handler_filter extends views_handler {
       form_error($form['expose']['identifier'], t('This identifier is used by another handler.'));
     }
   }
+   /**
+   * Validate the build group options form.
+   */
+  function build_group_validate($form, &$form_state) {
+    foreach ($form_state['values']['options']['group_info']['group_items'] as $id => $group) {
+      if (empty($group['remove'])) {
+
+        // Check if the title is defined but value wasn't defined.
+        if (!empty($group['title'])) {
+          if ((!is_array($group['value']) && trim($group['value']) == "") ||
+              (is_array($group['value']) && count(array_filter($group['value'], '_views_array_filter_zero')) == 0)) {
+            form_error($form['group_info']['group_items'][$id]['value'],
+                       t('The value is required if title for this item is defined.'));
+          }
+        }
+
+        // Check if the value is defined but title wasn't defined.
+        if ((!is_array($group['value']) && trim($group['value']) != "") ||
+            (is_array($group['value']) && count(array_filter($group['value'], '_views_array_filter_zero')) > 0)) {
+          if (empty($group['title'])) {
+            form_error($form['group_info']['group_items'][$id]['title'],
+                       t('The title is required if value for this item is defined.'));
+          }
+        }
+      }
+    }
+  }
+
+  /**
+   * Save new group items, re-enumerates and remove groups marked to delete.
+   */
+  function build_group_submit($form, &$form_state) {
+    $groups = array();
+    uasort($form_state['values']['options']['group_info']['group_items'], 'drupal_sort_weight');
+    // Filter out removed items.
+
+    // Start from 1 to avoid problems with #default_value in the widget.
+    $new_id = 1;
+    $new_default = 'All';
+    foreach ($form_state['values']['options']['group_info']['group_items'] as $id => $group) {
+      if (empty($group['remove'])) {
+        // Don't store this.
+        unset($group['remove']);
+        unset($group['weight']);
+        $groups[$new_id] = $group;
+
+        if ($form_state['values']['options']['group_info']['default_group'] === $id) {
+          $new_default = $new_id;
+        }
+      }
+      $new_id++;
+    }
+    if ($new_default != 'All') {
+      $form_state['values']['options']['group_info']['default_group'] = $new_default;
+    }
+    $filter_default_multiple = array_filter($form_state['values']['options']['group_info']['default_group_multiple']);
+    $form_state['values']['options']['group_info']['default_group_multiple'] = $filter_default_multiple;
+
+    $form_state['values']['options']['group_info']['group_items'] = $groups;
+  }
 
   /**
    * Provide default options for exposed filters.
@@ -427,6 +632,68 @@ class views_handler_filter extends views_handler {
     );
   }
 
+   /**
+   * Provide default options for exposed filters.
+   */
+  function build_group_options() {
+    $this->options['group_info'] = array(
+      'label' => $this->definition['title'],
+      'identifier' => $this->options['id'],
+      'optional' => TRUE,
+      'widget' => 'select',
+      'multiple' => FALSE,
+      'remember' => FALSE,
+      'default_group' => 'All',
+      'default_group_multiple' => array(),
+      'group_items' => array(),
+    );
+  }
+
+  /**
+   * Build a form containing a group of operator | values to apply as a
+   * single filter.
+   */
+  function group_form(&$form, &$form_state) {
+    if (!empty($this->options['group_info']['optional']) && !$this->multiple_exposed_input()) {
+
+      $old_any = $this->options['group_info']['widget'] == 'select' ? '<Any>' : '&lt;Any&gt;';
+      $any_label = variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? $old_any : t('- Any -');
+      $groups = array('All' => $any_label);
+    }
+    foreach ($this->options['group_info']['group_items'] as $id => $group) {
+      if (!empty($group['title'])) {
+        $groups[$id] = $id != 'All' ? t($group['title']) : $group['title'];
+      }
+    }
+
+    if (count($groups)) {
+      $value = $this->options['group_info']['identifier'];
+
+      $form[$value] = array(
+        '#type' => $this->options['group_info']['widget'],
+        '#default_value' => $this->group_info,
+        '#options' => $groups,
+      );
+      if (!empty($this->options['group_info']['multiple'])) {
+        if (count($groups) < 5) {
+          $form[$value]['#type'] = 'checkboxes';
+        }
+        else {
+          $form[$value]['#type'] = 'select';
+          $form[$value]['#size'] = 5;
+          $form[$value]['#multiple'] = TRUE;
+        }
+        unset($form[$value]['#default_value']);
+        if (empty($form_state['input'])) {
+          $form_state['input'][$value] = $this->group_info;
+        }
+      }
+
+      $this->options['expose']['label'] = '';
+    }
+  }
+
+
   /**
    * Render our chunk of the exposed filter form when selecting
    *
@@ -479,6 +746,247 @@ class views_handler_filter extends views_handler {
   }
 
   /**
+   * Build the form to let users create the group of exposed filters.
+   * This form is displayed when users click on button 'Build group'
+   */
+  function build_group_form(&$form, &$form_state) {
+    if (empty($this->options['exposed']) || empty($this->options['is_grouped'])) {
+      return;
+    }
+    $form['#theme'] = 'views_ui_build_group_filter_form';
+
+    // #flatten will move everything from $form['group_info'][$key] to $form[$key]
+    // prior to rendering. That's why the pre_render for it needs to run first,
+    // so that when the next pre_render (the one for fieldsets) runs, it gets
+    // the flattened data.
+    array_unshift($form['#pre_render'], 'views_ui_pre_render_flatten_data');
+    $form['group_info']['#flatten'] = TRUE;
+
+    if (!empty($this->options['group_info']['identifier'])) {
+      $identifier = $this->options['group_info']['identifier'];
+    }
+    else {
+      $identifier = 'group_' . $this->options['expose']['identifier'];
+    }
+    $form['group_info']['identifier'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $identifier,
+      '#title' => t('Filter identifier'),
+      '#size' => 40,
+      '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
+      '#fieldset' => 'more',
+    );
+    $form['group_info']['label'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $this->options['group_info']['label'],
+      '#title' => t('Label'),
+      '#size' => 40,
+    );
+    $form['group_info']['optional'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Optional'),
+      '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'),
+      '#default_value' => $this->options['group_info']['optional'],
+    );
+    $form['group_info']['multiple'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Allow multiple selections'),
+      '#description' => t('Enable to allow users to select multiple items.'),
+      '#default_value' => $this->options['group_info']['multiple'],
+    );
+    $form['group_info']['widget'] = array(
+      '#type' => 'radios',
+      '#default_value' => $this->options['group_info']['widget'],
+      '#title' => t('Widget type'),
+      '#options' => array(
+        'radios' => t('Radios'),
+        'select' => t('Select'),
+      ),
+      '#description' => t('Select which kind of widget will be used to render the group of filters'),
+    );
+    $form['group_info']['remember'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Remember'),
+      '#description' => t('Remember the last setting the user gave this filter.'),
+      '#default_value' => $this->options['group_info']['remember'],
+    );
+
+    if (!empty($this->options['group_info']['identifier'])) {
+      $identifier = $this->options['group_info']['identifier'];
+    }
+    else {
+      $identifier = 'group_' . $this->options['expose']['identifier'];
+    }
+    $form['group_info']['identifier'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $identifier,
+      '#title' => t('Filter identifier'),
+      '#size' => 40,
+      '#description' => t('This will appear in the URL after the ? to identify this filter. Cannot be blank.'),
+      '#fieldset' => 'more',
+    );
+    $form['group_info']['label'] = array(
+      '#type' => 'textfield',
+      '#default_value' => $this->options['group_info']['label'],
+      '#title' => t('Label'),
+      '#size' => 40,
+    );
+    $form['group_info']['optional'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Optional'),
+      '#description' => t('This exposed filter is optional and will have added options to allow it not to be set.'),
+      '#default_value' => $this->options['group_info']['optional'],
+    );
+    $form['group_info']['widget'] = array(
+      '#type' => 'radios',
+      '#default_value' => $this->options['group_info']['widget'],
+      '#title' => t('Widget type'),
+      '#options' => array(
+        'radios' => t('Radios'),
+        'select' => t('Select'),
+      ),
+      '#description' => t('Select which kind of widget will be used to render the group of filters'),
+    );
+    $form['group_info']['remember'] = array(
+      '#type' => 'checkbox',
+      '#title' => t('Remember'),
+      '#description' => t('Remember the last setting the user gave this filter.'),
+      '#default_value' => $this->options['group_info']['remember'],
+    );
+
+    $groups = array('All' => '- Any -'); // The string '- Any -' will not be rendered see @theme_views_ui_build_group_filter_form
+
+    // Provide 3 options to start when we are in a new group.
+    if (count($this->options['group_info']['group_items']) == 0) {
+      $this->options['group_info']['group_items'] = array_fill(1, 3, array());
+    }
+
+    // After the general settings, comes a table with all the existent groups.
+    $default_weight = 0;
+    foreach ($this->options['group_info']['group_items'] as $item_id => $item) {
+      if (!empty($form_state['values']['options']['group_info']['group_items'][$item_id]['remove'])) {
+        continue;
+      }
+      // Each rows contains three widgets:
+      // a) The title, where users define how they identify a pair of operator | value
+      // b) The operator
+      // c) The value (or values) to use in the filter with the selected operator
+
+      // In each row, we have to display the operator form and the value from
+      // $row acts as a fake form to render each widget in a row.
+      $row = array();
+      $groups[$item_id] = '';
+      $this->operator_form($row, $form_state);
+      // Force the operator form to be a select box. Some handlers uses
+      // radios and they occupy a lot of space in a table row.
+      $row['operator']['#type'] = 'select';
+      $row['operator']['#title'] = '';
+      $this->value_form($row, $form_state);
+
+      // Fix the dependencies to update value forms when operators
+      // changes. This is needed because forms are inside a new form and
+      // their ids changes. Dependencies are used when operator changes
+      // from to 'Between', 'Not Between', etc, and two or more widgets
+      // are displayed.
+      $without_children = TRUE;
+      foreach (element_children($row['value']) as $children) {
+        if (isset($row['value'][$children]['#dependency']['edit-options-operator'])) {
+          $row['value'][$children]['#dependency']["edit-options-group-info-group-items-$item_id-operator"] = $row['value'][$children]['#dependency']['edit-options-operator'];
+          unset($row['value'][$children]['#dependency']['edit-options-operator']);
+          $row['value'][$children]['#title'] = '';
+
+          if (!empty($this->options['group_info']['group_items'][$item_id]['value'][$children])) {
+            $row['value'][$children]['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'][$children];
+          }
+        }
+        $without_children = FALSE;
+      }
+
+      if ($without_children) {
+        if (!empty($this->options['group_info']['group_items'][$item_id]['value'])) {
+          $row['value']['#default_value'] = $this->options['group_info']['group_items'][$item_id]['value'];
+        }
+      }
+
+      if (!empty($this->options['group_info']['group_items'][$item_id]['operator'])) {
+        $row['operator']['#default_value'] = $this->options['group_info']['group_items'][$item_id]['operator'];
+      }
+
+      $default_title = '';
+      if (!empty($this->options['group_info']['group_items'][$item_id]['title'])) {
+        $default_title = $this->options['group_info']['group_items'][$item_id]['title'];
+      }
+
+      // Per item group, we have a title that identifies it.
+      $form['group_info']['group_items'][$item_id] = array(
+        'title' => array(
+          '#type' => 'textfield',
+          '#size' => 20,
+          '#default_value' => $default_title,
+        ),
+        'operator' => $row['operator'],
+        'value' => $row['value'],
+        'remove' => array(
+          '#type' => 'checkbox',
+          '#id' => 'views-removed-' . $item_id,
+          '#attributes' => array('class' => array('views-remove-checkbox')),
+          '#default_value' => 0,
+        ),
+        'weight' => array(
+          '#type' => 'weight',
+          '#delta' => 10,
+          '#default_value' => $default_weight++,
+          '#attributes' => array('class' => array('weight')),
+        ),
+      );
+    }
+    // From all groups, let chose which is the default.
+    $form['group_info']['default_group'] = array(
+      '#type' => 'radios',
+      '#options' => $groups,
+      '#default_value' => $this->options['group_info']['default_group'],
+      '#required' => TRUE,
+      '#attributes' => array(
+        'class' => array('default-radios'),
+      )
+    );
+    // From all groups, let chose which is the default.
+    $form['group_info']['default_group_multiple'] = array(
+      '#type' => 'checkboxes',
+      '#options' => $groups,
+      '#default_value' => $this->options['group_info']['default_group_multiple'],
+      '#attributes' => array(
+        'class' => array('default-checkboxes'),
+      )
+    );
+
+    $form['group_info']['add_group'] = array(
+      '#prefix' => '<div class="views-build-group clear-block">',
+      '#suffix' => '</div>',
+      '#type' => 'submit',
+      '#value' => t('Add another item'),
+      '#submit' => array('views_ui_config_item_form_add_group'),
+    );
+
+    $js = array();
+    $js['tableDrag']['views-filter-groups']['weight'][0] = array(
+      'target' => 'weight',
+      'source' => NULL,
+      'relationship' => 'sibling',
+      'action' => 'order',
+      'hidden' => TRUE,
+      'limit' => 0,
+    );
+    if (!empty($form_state['js settings']) && is_array($js)) {
+      $form_state['js settings'] = array_merge($form_state['js settings'], $js);
+    }
+    else {
+      $form_state['js settings'] = $js;
+    }
+  }
+
+
+  /**
    * Make some translations to a form item to make it more suitable to
    * exposing.
    */
@@ -549,16 +1057,26 @@ class views_handler_filter extends views_handler {
    * overridden for particularly complex forms. And maybe not even then.
    *
    * @return array|null
-   *   An array with the following keys:
+   *   For standard exposed filters. An array with the following keys:
    *   - operator: The $form key of the operator. Set to NULL if no operator.
    *   - value: The $form key of the value. Set to NULL if no value.
    *   - label: The label to use for this piece.
+   *   For grouped exposed filters. An array with the following keys:
+   *   - value: The $form key of the value. Set to NULL if no value.
+   *   - label: The label to use for this piece.
    */
   function exposed_info() {
     if (empty($this->options['exposed'])) {
       return;
     }
 
+    if ($this->is_a_group()) {
+      return array(
+        'value' => $this->options['group_info']['identifier'],
+        'label' => $this->options['group_info']['label'],
+      );
+    }
+
     return array(
       'operator' => $this->options['expose']['operator_id'],
       'value' => $this->options['expose']['identifier'],
@@ -566,6 +1084,110 @@ class views_handler_filter extends views_handler {
     );
   }
 
+  /*
+   * Transform the input from a grouped filter into a standard filter.
+   *
+   * When a filter is a group, find the set of operator and values
+   * that the choosed item represents, and inform views that a normal
+   * filter was submitted by telling the operator and the value selected.
+   *
+   * The param $selected_group_id is only passed when the filter uses the
+   * checkboxes widget, and this function will be called for each item
+   * choosed in the checkboxes.
+   */
+  function convert_exposed_input(&$input, $selected_group_id = NULL) {
+    if ($this->is_a_group()) {
+      // If it is already defined the selected group, use it. Only valid
+      // when the filter uses checkboxes for widget.
+      if (!empty($selected_group_id)) {
+        $selected_group = $selected_group_id;
+      }
+      else {
+        $selected_group = $input[$this->options['group_info']['identifier']];
+      }
+      if ($selected_group == 'All' && !empty($this->options['group_info']['optional'])) {
+        return NULL;
+      }
+      if ($selected_group != 'All' && empty($this->options['group_info']['group_items'][$selected_group])) {
+        return FALSE;
+      }
+      if (isset($selected_group) && isset($this->options['group_info']['group_items'][$selected_group])) {
+        $input[$this->options['expose']['operator']] = $this->options['group_info']['group_items'][$selected_group]['operator'];
+
+        // Value can be optional, For example for 'empty' and 'not empty' filters.
+        if (!empty($this->options['group_info']['group_items'][$selected_group]['value'])) {
+          $input[$this->options['expose']['identifier']] = $this->options['group_info']['group_items'][$selected_group]['value'];
+        }
+        $this->options['expose']['use_operator'] = TRUE;
+
+        $this->group_info = $input[$this->options['group_info']['identifier']];
+        return TRUE;
+      }
+      else {
+        return FALSE;
+      }
+    }
+  }
+
+  /**
+   * Returns the options available for a grouped filter that users checkboxes
+   * as widget, and therefore has to be applied several times, one per
+   * item selected.
+   */
+  function group_multiple_exposed_input(&$input) {
+    if (!empty($input[$this->options['group_info']['identifier']])) {
+      return array_filter($input[$this->options['group_info']['identifier']]);
+    }
+    return array();
+  }
+
+  /**
+   * Returns TRUE if users can select multiple groups items of a
+   * grouped exposed filter.
+   */
+  function multiple_exposed_input() {
+    return $this->is_a_group() && !empty($this->options['group_info']['multiple']);
+  }
+
+  /**
+   * If set to remember exposed input in the session, store it there.
+   * This function is similar to store_exposed_input but modified to
+   * work properly when the filter is a group.
+   */
+  function store_group_input($input, $status) {
+    if (!$this->is_a_group() || empty($this->options['group_info']['identifier'])) {
+      return TRUE;
+    }
+
+    if (empty($this->options['group_info']['remember'])) {
+      return;
+    }
+
+    // Figure out which display id is responsible for the filters, so we
+    // know where to look for session stored values.
+    $display_id = ($this->view->display_handler->is_defaulted('filters')) ? 'default' : $this->view->current_display;
+
+    // false means that we got a setting that means to recuse ourselves,
+    // so we should erase whatever happened to be there.
+    if ($status === FALSE && isset($_SESSION['views'][$this->view->name][$display_id])) {
+      $session = &$_SESSION['views'][$this->view->name][$display_id];
+
+      if (isset($session[$this->options['group_info']['identifier']])) {
+        unset($session[$this->options['group_info']['identifier']]);
+      }
+    }
+
+    if ($status !== FALSE) {
+      if (!isset($_SESSION['views'][$this->view->name][$display_id])) {
+        $_SESSION['views'][$this->view->name][$display_id] = array();
+      }
+
+      $session = &$_SESSION['views'][$this->view->name][$display_id];
+
+      $session[$this->options['group_info']['identifier']] = $input[$this->options['group_info']['identifier']];
+    }
+  }
+
   /**
    * Check to see if input from the exposed filters should change
    * the behavior of this filter.
@@ -711,6 +1333,15 @@ class views_handler_filter_broken extends views_handler_filter {
   function broken() { return TRUE; }
 }
 
+/**
+ * Filter by no empty values, though allow to use "0".
+ * @param $var
+ * @return bool
+ */
+function _views_array_filter_zero($var) {
+  return trim($var) != "";
+}
+
 
 /**
  * @}
diff --git a/handlers/views_handler_filter_boolean_operator.inc b/handlers/views_handler_filter_boolean_operator.inc
index a5fd8f0..56365e1 100644
--- a/handlers/views_handler_filter_boolean_operator.inc
+++ b/handlers/views_handler_filter_boolean_operator.inc
@@ -129,6 +129,9 @@ class views_handler_filter_boolean_operator extends views_handler_filter {
   }
 
   function admin_summary() {
+    if ($this->is_a_group()) {
+      return t('grouped');
+    }
     if (!empty($this->options['exposed'])) {
       return t('exposed');
     }
diff --git a/handlers/views_handler_filter_date.inc b/handlers/views_handler_filter_date.inc
index 3082c78..4ef61b4 100644
--- a/handlers/views_handler_filter_date.inc
+++ b/handlers/views_handler_filter_date.inc
@@ -95,6 +95,34 @@ class views_handler_filter_date extends views_handler_filter_numeric {
     }
   }
 
+  /**
+   * Validate the build group options form.
+   */
+  function build_group_validate($form, &$form_state) {
+    // Special case to validate grouped date filters, this is because the
+    // $group['value'] array contains the type of filter (date or offset)
+    // and therefore the number of items the comparission has to be done
+    // against 'one' instead of 'zero'.
+    foreach ($form_state['values']['options']['group_info']['group_items'] as $id => $group) {
+      if (empty($group['remove'])) {
+        // Check if the title is defined but value wasn't defined.
+        if (!empty($group['title'])) {
+          if ((!is_array($group['value']) && empty($group['value'])) || (is_array($group['value']) && count(array_filter($group['value'])) == 1)) {
+            form_error($form['group_info']['group_items'][$id]['value'], t('The value is required if title for this item is defined.'));
+          }
+        }
+
+        // Check if the value is defined but title wasn't defined.
+        if ((!is_array($group['value']) && !empty($group['value'])) || (is_array($group['value']) && count(array_filter($group['value'])) > 1)) {
+          if (empty($group['title'])) {
+            form_error($form['group_info']['group_items'][$id]['title'], t('The title is required if value for this item is defined.'));
+          }
+        }
+      }
+    }
+  }
+
+
   function accept_exposed_input($input) {
     if (empty($this->options['exposed'])) {
       return TRUE;
diff --git a/handlers/views_handler_filter_in_operator.inc b/handlers/views_handler_filter_in_operator.inc
index a801bad..fc2700b 100644
--- a/handlers/views_handler_filter_in_operator.inc
+++ b/handlers/views_handler_filter_in_operator.inc
@@ -290,6 +290,9 @@ class views_handler_filter_in_operator extends views_handler_filter {
   }
 
   function admin_summary() {
+    if ($this->is_a_group()) {
+      return t('grouped');
+    }
     if (!empty($this->options['exposed'])) {
       return t('exposed');
     }
diff --git a/handlers/views_handler_filter_numeric.inc b/handlers/views_handler_filter_numeric.inc
index 46ee858..982abd8 100644
--- a/handlers/views_handler_filter_numeric.inc
+++ b/handlers/views_handler_filter_numeric.inc
@@ -262,6 +262,9 @@ class views_handler_filter_numeric extends views_handler_filter {
   }
 
   function admin_summary() {
+    if ($this->is_a_group()) {
+      return t('grouped');
+    }
     if (!empty($this->options['exposed'])) {
       return t('exposed');
     }
diff --git a/handlers/views_handler_filter_string.inc b/handlers/views_handler_filter_string.inc
index a42cd69..c50eff4 100644
--- a/handlers/views_handler_filter_string.inc
+++ b/handlers/views_handler_filter_string.inc
@@ -148,6 +148,9 @@ class views_handler_filter_string extends views_handler_filter {
   }
 
   function admin_summary() {
+    if ($this->is_a_group()) {
+      return t('grouped');
+    }
     if (!empty($this->options['exposed'])) {
       return t('exposed');
     }
diff --git a/help/filter.html b/help/filter.html
index 752617f..cb84f8a 100644
--- a/help/filter.html
+++ b/help/filter.html
@@ -15,6 +15,21 @@ When you click the Rearrange Icon you can first rearrange your filters, easily d
 
 When you want that the user to select their own filter, you can expose the filter. A selection box will show for the user and they will be able to select one item. After that the view will reload and only the selected item will be displayed. You can also choose to expose the selection to a block, see <a href="topic:views/exposed-form">here</a>.
 
+For exposed filters, you can create a grouped filter. When filters are in a group, each item of the group represents a set of operators and values. The following table illustrates how this feature works. The values of the first column of the table are displayed as options of a single select box:
+
+<table>
+<thead>
+<tr><th>What the user see</th><th>What views does</th></tr>
+</thead>
+<tbody>
+<tr><td>Is lower than 10</td><td><strong>Operator:</strong> Is Lower than. <strong>Value:</strong> 10</td></tr>
+<tr><td>Is between 10 and 20</td><td><strong>Operator:</strong> Is between. <strong>Value:</strong> 10 and 20</td></tr>
+<tr><td>Is greater than 20</td><td><strong>Operator:</strong> Is Greater. <strong>Value:</strong> 20</td></tr>
+</tbody>
+</table>
+
+<strong>Please note:</strong> When using grouped filters with the option: 'Enable to allow users to select multiple items' checked, you probably may want to to place the filter in a separated group and define the operator of the group as 'OR'. This may be neccesary because in order to use multiple times the same filter, all options have to be applied using the OR operator, if not, probably you will get nothing listed since usually items in a group are mutually exclusive.
+
 Taxonomy filters have been significantly altered in Views 7.x-3.x. D7 significantly re-organized taxonomy, there was a lot of duplicate taxonomy related fields and filters. Some of them were removed to try and reduce confusion between them. Implicit relationships to taxonomy have been removed, in favor of explicit relationships. If the filters you can find don't do what you need, try adding either the related taxonomy terms relationship, or a relationship on the specific taxonomy field. That will give you the term specific filters.
 
 You can override the complete filter section - see <a href="topic:views/overrides">here</a> for more information.
diff --git a/includes/admin.inc b/includes/admin.inc
index 035cffb..98500a7 100644
--- a/includes/admin.inc
+++ b/includes/admin.inc
@@ -3508,6 +3508,7 @@ function theme_views_ui_expose_filter_form($variables) {
 
   $output = drupal_render($form['form_description']);
   $output .= drupal_render($form['expose_button']);
+  $output .= drupal_render($form['group_button']);
   if (isset($form['required'])) {
     $output .= drupal_render($form['required']);
   }
@@ -3537,6 +3538,77 @@ function theme_views_ui_expose_filter_form($variables) {
   return $output;
 }
 
+ /**
+ * Theme the build group filter form.
+ */
+function theme_views_ui_build_group_filter_form($variables) {
+  $form = $variables['form'];
+  $more = drupal_render($form['more']);
+
+  $output = drupal_render($form['form_description']);
+  $output .= drupal_render($form['expose_button']);
+  $output .= drupal_render($form['group_button']);
+  if (isset($form['required'])) {
+    $output .= drupal_render($form['required']);
+  }
+
+  $output .= drupal_render($form['operator']);
+  $output .= drupal_render($form['value']);
+
+  $output .= '<div class="views-left-40">';
+  $output .= drupal_render($form['optional']);
+  $output .= drupal_render($form['remember']);
+  $output .= '</div>';
+
+  $output .= '<div class="views-right-60">';
+  $output .= drupal_render($form['widget']);
+  $output .= drupal_render($form['label']);
+  $output .= '</div>';
+
+
+  $header = array(
+    t('Default'),
+    t('Weight'),
+    t('Label'),
+    t('Operator'),
+    t('Value'),
+    t('Operations'),
+  );
+
+  $form['default_group'] = form_process_radios($form['default_group']);
+  $form['default_group_multiple'] = form_process_checkboxes($form['default_group_multiple']);
+  $form['default_group']['All']['#title'] = '';
+
+  drupal_render($form['default_group_multiple']['All']); // Don't render
+  $rows[] = array(
+    drupal_render($form['default_group']['All']),
+    '',
+    array(
+      'data' => variable_get('views_exposed_filter_any_label', 'new_any') == 'old_any' ? t('&lt;Any&gt;') : t('- Any -'),
+      'colspan' => 4,
+      'class' => array('class' => 'any-default-radios-row'),
+    ),
+  );
+
+  foreach (element_children($form['group_items']) as $group_id) {
+    $form['group_items'][$group_id]['value']['#title'] = '';
+    $data = array(
+      'default' => drupal_render($form['default_group'][$group_id]) . drupal_render($form['default_group_multiple'][$group_id]),
+      'weight' => drupal_render($form['group_items'][$group_id]['weight']),
+      'title' => drupal_render($form['group_items'][$group_id]['title']),
+      'operator' => drupal_render($form['group_items'][$group_id]['operator']),
+      'value' => drupal_render($form['group_items'][$group_id]['value']),
+      'remove' => drupal_render($form['group_items'][$group_id]['remove']) . l('<span>' . t('Remove') . '</span>', 'javascript:void()', array('attributes' => array('id' => 'views-remove-link-' . $group_id, 'class' => array('views-hidden', 'views-button-remove', 'views-groups-remove-link', 'views-remove-link'), 'alt' => t('Remove this item'), 'title' => t('Remove this item')), 'html' => true)),
+    );
+    $rows[] = array('data' => $data, 'id' => 'views-row-' . $group_id, 'class' => array('draggable'));
+  }
+  $table = theme('table', array('header' => $header, 'rows' => $rows, 'attributes' => array('class' => array('views-filter-groups'), 'id' => 'views-filter-groups'))) . drupal_render($form['add_group']);
+  drupal_add_tabledrag('views-filter-groups', 'order', 'sibling', 'weight');
+  $render_form = drupal_render_children($form);
+  return $output . $render_form . $table . $more;
+}
+
+
 /**
  * Submit handler for rearranging form
  */
@@ -4146,6 +4218,45 @@ function views_ui_add_item_form_submit($form, &$form_state) {
   views_ui_cache_set($form_state['view']);
 }
 
+/**
+ * Override handler for views_ui_edit_display_form
+ */
+function views_ui_config_item_form_build_group($form, &$form_state) {
+  $item = &$form_state['handler']->options;
+  // flip. If the filter was a group, set back to a standard filter.
+  $item['is_grouped'] = empty($item['is_grouped']);
+
+  // If necessary, set new defaults:
+  if ($item['is_grouped']) {
+    $form_state['handler']->build_group_options();
+  }
+
+  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+  views_ui_add_form_to_stack($form_state['form_key'], $form_state['view'], $form_state['display_id'], array($form_state['type'], $form_state['id']), TRUE, TRUE);
+
+  views_ui_cache_set($form_state['view']);
+  $form_state['rerender'] = TRUE;
+  $form_state['rebuild'] = TRUE;
+  $form_state['force_build_group_options'] = TRUE;
+}
+
+/**
+ * Add a new group to the exposed filter groups.
+ */
+function views_ui_config_item_form_add_group($form, &$form_state) {
+  $item =& $form_state['handler']->options;
+
+  // Add a new row.
+  $item['group_info']['group_items'][] = array();
+
+  $form_state['view']->set_item($form_state['display_id'], $form_state['type'], $form_state['id'], $item);
+
+  views_ui_cache_set($form_state['view']);
+  $form_state['rerender'] = TRUE;
+  $form_state['rebuild'] = TRUE;
+  $form_state['force_build_group_options'] = TRUE;
+}
 
 /**
  * Form to config_item items in the views UI.
diff --git a/includes/handlers.inc b/includes/handlers.inc
index 680ef67..a643f45 100644
--- a/includes/handlers.inc
+++ b/includes/handlers.inc
@@ -672,6 +672,18 @@ class views_handler extends views_object {
   }
 
   /**
+   * Returns TRUE if the exposed filter works like a grouped filter.
+   */
+  function is_a_group() { return FALSE; }
+
+  /**
+   * Define if the exposed input has to be submitted multiple times.
+   * This is TRUE when exposed filters grouped are using checkboxes as
+   * widgets.
+   */
+  function multiple_exposed_input() { return FALSE; }
+
+  /**
    * Take input from exposed handlers and assign to this handler, if necessary.
    */
   function accept_exposed_input($input) { return TRUE; }
diff --git a/includes/view.inc b/includes/view.inc
index 4523e72..4d63fd7 100644
--- a/includes/view.inc
+++ b/includes/view.inc
@@ -120,6 +120,9 @@ class view extends views_db_object {
   // Is the current stored view runned as an attachment to another view.
   var $is_attachment = NULL;
 
+  // Chache views exposed form. Usually switched to FALSE while running tests.
+  var $cache_exposed_forms = TRUE;
+
   // Stores the next steps of form items to handle.
   // It's an array of stack items, which contain the form id, the type of form,
   // the view, the display and some additional arguments.
@@ -1065,17 +1068,32 @@ class view extends views_db_object {
   function _build($key) {
     $handlers = &$this->$key;
     foreach ($handlers as $id => $data) {
+
       if (!empty($handlers[$id]) && is_object($handlers[$id])) {
-        // Give this handler access to the exposed filter input.
-        if (!empty($this->exposed_data)) {
-          $rc = $handlers[$id]->accept_exposed_input($this->exposed_data);
-          $handlers[$id]->store_exposed_input($this->exposed_data, $rc);
-          if (!$rc) {
-            continue;
+        $multiple_exposed_input = array(0 => NULL);
+        if ($handlers[$id]->multiple_exposed_input()) {
+          $multiple_exposed_input = $handlers[$id]->group_multiple_exposed_input($this->exposed_data);
+        }
+        foreach ($multiple_exposed_input as $group_id) {
+          // Give this handler access to the exposed filter input.
+          if (!empty($this->exposed_data)) {
+            $converted = FALSE;
+            if ($handlers[$id]->is_a_group()) {
+              $converted = $handlers[$id]->convert_exposed_input($this->exposed_data, $group_id);
+              $handlers[$id]->store_group_input($this->exposed_data, $converted);
+              if (!$converted) {
+                continue;
+              }
+            }
+            $rc = $handlers[$id]->accept_exposed_input($this->exposed_data);
+            $handlers[$id]->store_exposed_input($this->exposed_data, $rc);
+            if (!$rc) {
+              continue;
+            }
           }
+          $handlers[$id]->set_relationship();
+          $handlers[$id]->query($this->display_handler->use_group_by());
         }
-        $handlers[$id]->set_relationship();
-        $handlers[$id]->query($this->display_handler->use_group_by());
       }
     }
   }
diff --git a/js/views-admin.js b/js/views-admin.js
index 84c9639..935dc43 100644
--- a/js/views-admin.js
+++ b/js/views-admin.js
@@ -827,7 +827,7 @@ Drupal.behaviors.viewsRemoveIconClass.attach = function (context, settings) {
     $('.icon', $this).removeClass('icon');
     $('.horizontal', $this).removeClass('horizontal');
   });
-}
+};
 
 /**
  * Change "Expose filter" buttons into checkboxes.
@@ -835,7 +835,7 @@ Drupal.behaviors.viewsRemoveIconClass.attach = function (context, settings) {
 Drupal.behaviors.viewsUiCheckboxify = {};
 Drupal.behaviors.viewsUiCheckboxify.attach = function (context, settings) {
   var $ = jQuery;
-  var $buttons = $('#edit-options-expose-button-button').once('views-ui-checkboxify');
+  var $buttons = $('#edit-options-expose-button-button, #edit-options-group-button-button').once('views-ui-checkboxify');
   var length = $buttons.length;
   var i;
   for (i = 0; i < length; i++) {
@@ -844,6 +844,33 @@ Drupal.behaviors.viewsUiCheckboxify.attach = function (context, settings) {
 };
 
 /**
+ * Change the default widget to select the default group according to the
+ * selected widget for the exposed group.
+ */
+Drupal.behaviors.viewsUiChangeDefaultWidget = {};
+Drupal.behaviors.viewsUiChangeDefaultWidget.attach = function (context, settings) {
+  var $ = jQuery;
+  function change_default_widget(multiple) {
+    if (multiple) {
+      $('input.default-radios').hide();
+      $('td.any-default-radios-row').parent().hide();
+      $('input.default-checkboxes').show();
+    }
+    else {
+      $('input.default-checkboxes').hide();
+      $('td.any-default-radios-row').parent().show();
+      $('input.default-radios').show();
+    }
+  }
+  // Update on widget change.
+  $('input[name="options[group_info][multiple]"]').change(function() {
+    change_default_widget($(this).attr("checked"));
+  });
+  // Update the first time the form is rendered.
+  $('input[name="options[group_info][multiple]"]').trigger('change');
+};
+
+/**
  * Attaches an expose filter button to a checkbox that triggers its click event.
  *
  * @param button
@@ -852,13 +879,14 @@ Drupal.behaviors.viewsUiCheckboxify.attach = function (context, settings) {
 Drupal.viewsUi.Checkboxifier = function (button) {
   var $ = jQuery;
   this.$button = $(button);
-  this.$parent = this.$button.parent('div.views-expose');
-  this.$checkbox = this.$parent.find('input:checkbox');
+  this.$parent = this.$button.parent('div.views-expose, div.views-grouped');
+  this.$input = this.$parent.find('input:checkbox, input:radio');
   // Hide the button and its description.
   this.$button.hide();
-  this.$parent.find('.exposed-description').hide();
+  this.$parent.find('.exposed-description, .grouped-description').hide();
+
+  this.$input.click($.proxy(this, 'clickHandler'));
 
-  this.$checkbox.click($.proxy(this, 'clickHandler'));
 };
 
 /**
diff --git a/tests/handlers/views_handler_filter_date.test b/tests/handlers/views_handler_filter_date.test
index bd313ac..34ccba6 100644
--- a/tests/handlers/views_handler_filter_date.test
+++ b/tests/handlers/views_handler_filter_date.test
@@ -33,7 +33,7 @@ class ViewsHandlerFilterDateTest extends ViewsSqlTest {
   }
 
   /**
-  /* Test the general offset functionality.
+   * Test the general offset functionality.
    */
   function testOffset() {
     $view = $this->views_test_offset();
diff --git a/tests/handlers/views_handler_filter_equality.test b/tests/handlers/views_handler_filter_equality.test
index 43e1398..5bb48c8 100644
--- a/tests/handlers/views_handler_filter_equality.test
+++ b/tests/handlers/views_handler_filter_equality.test
@@ -55,6 +55,24 @@ class ViewsHandlerFilterEqualityTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  public function testEqualGroupedExposed() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: =, Value: Ringo
+    $filters['name']['group_info']['default_group'] = 1;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'Ringo',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testNotEqual() {
     $view = $this->getBasicView();
 
@@ -87,4 +105,69 @@ class ViewsHandlerFilterEqualityTest extends ViewsSqlTest {
     );
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
+
+  public function testEqualGroupedNotExposed() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: !=, Value: Ringo
+    $filters['name']['group_info']['default_group'] = 2;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'John',
+      ),
+      array(
+        'name' => 'George',
+      ),
+      array(
+        'name' => 'Paul',
+      ),
+      array(
+        'name' => 'Meredith',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+
+  protected function getGroupedExposedFilters() {
+    $filters = array(
+      'name' => array(
+        'id' => 'name',
+        'table' => 'views_test',
+        'field' => 'name',
+        'relationship' => 'none',
+        'exposed' => TRUE,
+        'expose' => array(
+          'operator' => 'name_op',
+          'label' => 'name',
+          'identifier' => 'name',
+        ),
+        'is_grouped' => TRUE,
+        'group_info' => array(
+          'label' => 'name',
+          'identifier' => 'name',
+          'default_group' => 'All',
+          'group_items' => array(
+            1 => array(
+              'title' => 'Name is equal to Ringo',
+              'operator' => '=',
+              'value' => array('value' => 'Ringo'),
+            ),
+            2 => array(
+              'title' => 'Name is not equal to Ringo',
+              'operator' => '!=',
+              'value' => array('value' => 'Ringo'),
+            ),
+          ),
+        ),
+      ),
+    );
+    return $filters;
+  }
+
 }
diff --git a/tests/handlers/views_handler_filter_in_operator.test b/tests/handlers/views_handler_filter_in_operator.test
index 4f35be9..3a20f8c 100644
--- a/tests/handlers/views_handler_filter_in_operator.test
+++ b/tests/handlers/views_handler_filter_in_operator.test
@@ -94,4 +94,103 @@ class ViewsHandlerFilterInOperator extends ViewsSqlTest {
       'views_test_age' => 'age',
     ));
   }
+
+  public function testFilterInOperatorGroupedExposedSimple() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: in, Value: 26, 30
+    $filters['age']['group_info']['default_group'] = 1;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $expected_result = array(
+      array(
+        'name' => 'Paul',
+        'age' => 26,
+      ),
+      array(
+        'name' => 'Meredith',
+        'age' => 30,
+      ),
+    );
+
+    $this->assertEqual(2, count($view->result));
+    $this->assertIdenticalResultset($view, $expected_result, array(
+      'views_test_name' => 'name',
+      'views_test_age' => 'age',
+    ));
+  }
+
+  public function testFilterNotInOperatorGroupedExposedSimple() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: in, Value: 26, 30
+    $filters['age']['group_info']['default_group'] = 2;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $expected_result = array(
+      array(
+        'name' => 'John',
+        'age' => 25,
+      ),
+      array(
+        'name' => 'George',
+        'age' => 27,
+      ),
+      array(
+        'name' => 'Ringo',
+        'age' => 28,
+      ),
+    );
+
+    $this->assertEqual(3, count($view->result));
+    $this->assertIdenticalResultset($view, $expected_result, array(
+      'views_test_name' => 'name',
+      'views_test_age' => 'age',
+    ));
+  }
+
+  protected function getGroupedExposedFilters() {
+    $filters = array(
+      'age' => array(
+        'id' => 'age',
+        'table' => 'views_test',
+        'field' => 'age',
+        'relationship' => 'none',
+        'exposed' => TRUE,
+        'expose' => array(
+          'operator' => 'age_op',
+          'label' => 'age',
+          'identifier' => 'age',
+        ),
+        'is_grouped' => TRUE,
+        'group_info' => array(
+          'label' => 'age',
+          'identifier' => 'age',
+          'default_group' => 'All',
+          'group_items' => array(
+            1 => array(
+              'title' => 'Age is one of 26, 30',
+              'operator' => 'in',
+              'value' => array(26, 30),
+            ),
+            2 => array(
+              'title' => 'Age is not one of 26, 30',
+              'operator' => 'not in',
+              'value' => array(26, 30),
+            ),
+          ),
+        ),
+      ),
+    );
+    return $filters;
+  }
+
 }
diff --git a/tests/handlers/views_handler_filter_numeric.test b/tests/handlers/views_handler_filter_numeric.test
index d5f14d5..2ab1aea 100644
--- a/tests/handlers/views_handler_filter_numeric.test
+++ b/tests/handlers/views_handler_filter_numeric.test
@@ -13,7 +13,7 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest {
 
   public static function getInfo() {
     return array(
-      'name' => 'Handlers: filter_numeric',
+      'name' => 'Filter: Numeric',
       'description' => 'Tests the numeric filter handler',
       'group' => 'Views Handlers',
     );
@@ -60,6 +60,25 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  public function testFilterNumericExposedGroupedSimple() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: =, Value: 28
+    $filters['age']['group_info']['default_group'] = 1;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'Ringo',
+        'age' => 28,
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   public function testFilterNumericBetween() {
     $view = $this->getBasicView();
 
@@ -132,6 +151,63 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  public function testFilterNumericExposedGroupedBetween() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: between, Value: 26 and 29
+    $filters['age']['group_info']['default_group'] = 2;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'George',
+        'age' => 27,
+      ),
+      array(
+        'name' => 'Ringo',
+        'age' => 28,
+      ),
+      array(
+        'name' => 'Paul',
+        'age' => 26,
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+  public function testFilterNumericExposedGroupedNotBetween() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: between, Value: 26 and 29
+    $filters['age']['group_info']['default_group'] = 3;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'John',
+        'age' => 25,
+      ),
+      array(
+        'name' => 'Paul',
+        'age' => 26,
+      ),
+      array(
+        'name' => 'Meredith',
+        'age' => 30,
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+
   public function testFilterNumericEmpty() {
     $view = $this->getBasicView();
 
@@ -191,6 +267,60 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+
+  public function testFilterNumericExposedGroupedEmpty() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: empty, Value:
+    $filters['age']['group_info']['default_group'] = 4;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+    $this->executeView($view);
+    $resultset = array(
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+  public function testFilterNumericExposedGroupedNotEmpty() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Age, Operator: empty, Value:
+    $filters['age']['group_info']['default_group'] = 5;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+
+    $this->executeView($view);
+    $resultset = array(
+    array(
+        'name' => 'John',
+        'age' => 25,
+      ),
+      array(
+        'name' => 'George',
+        'age' => 27,
+      ),
+      array(
+        'name' => 'Ringo',
+        'age' => 28,
+      ),
+      array(
+        'name' => 'Paul',
+        'age' => 26,
+      ),
+      array(
+        'name' => 'Meredith',
+        'age' => 30,
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+
   public function testAllowEmpty() {
     $view = $this->getBasicView();
 
@@ -220,4 +350,60 @@ class ViewsHandlerFilterNumericTest extends ViewsSqlTest {
     $this->assertTrue(isset($age_operators['empty']));
     $this->assertTrue(isset($age_operators['not empty']));
   }
+
+  protected function getGroupedExposedFilters() {
+    $filters = array(
+      'age' => array(
+        'id' => 'age',
+        'table' => 'views_test',
+        'field' => 'age',
+        'relationship' => 'none',
+        'exposed' => TRUE,
+        'expose' => array(
+          'operator' => 'age_op',
+          'label' => 'age',
+          'identifier' => 'age',
+        ),
+        'is_grouped' => TRUE,
+        'group_info' => array(
+          'label' => 'age',
+          'identifier' => 'age',
+          'default_group' => 'All',
+          'group_items' => array(
+            1 => array(
+              'title' => 'Age is 28',
+              'operator' => '=',
+              'value' => array('value' => 28),
+            ),
+            2 => array(
+              'title' => 'Age is between 26 and 29',
+              'operator' => 'between',
+              'value' => array(
+                'min' => 26,
+                'max' => 29,
+              ),
+            ),
+            3 => array(
+              'title' => 'Age is not between 26 and 29',
+              'operator' => 'not between',
+              'value' => array(
+                'min' => 26,
+                'max' => 29,
+              ),
+            ),
+            4 => array(
+              'title' => 'Age is empty',
+              'operator' => 'empty',
+            ),
+            5 => array(
+              'title' => 'Age is not empty',
+              'operator' => 'not empty',
+            ),
+          ),
+        ),
+      ),
+    );
+    return $filters;
+  }
+
 }
diff --git a/tests/handlers/views_handler_filter_string.test b/tests/handlers/views_handler_filter_string.test
index 73b845c..ee74a28 100644
--- a/tests/handlers/views_handler_filter_string.test
+++ b/tests/handlers/views_handler_filter_string.test
@@ -94,9 +94,29 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
       ),
     );
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
-    $view->destroy();
+  }
+
+  function testFilterStringGroupedExposedEqual() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: =, Value: Ringo
+    $filters['name']['group_info']['default_group'] = 1;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'Ringo',
+      ),
+    );
 
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
 
+  function testFilterStringNotEqual() {
     $view = $this->getBasicView();
 
     // Change the filtering
@@ -129,6 +149,36 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedNotEqual() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: !=, Value: Ringo
+    $filters['name']['group_info']['default_group'] = '2';
+
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'John',
+      ),
+      array(
+        'name' => 'George',
+      ),
+      array(
+        'name' => 'Paul',
+      ),
+      array(
+        'name' => 'Meredith',
+      ),
+    );
+
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringContains() {
     $view = $this->getBasicView();
 
@@ -153,6 +203,28 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+
+  function testFilterStringGroupedExposedContains() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: contains, Value: ing
+    $filters['name']['group_info']['default_group'] = '3';
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'Ringo',
+      ),
+    );
+
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+
   function testFilterStringWord() {
     $view = $this->getBasicView();
 
@@ -203,6 +275,47 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+
+  function testFilterStringGroupedExposedWord() {
+      $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: contains, Value: ing
+    $filters['name']['group_info']['default_group'] = '3';
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'Ringo',
+      ),
+    );
+
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+    $view->destroy();
+
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Description, Operator: contains, Value: actor
+    $filters['description']['group_info']['default_group'] = '1';
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'George',
+      ),
+      array(
+        'name' => 'Ringo',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringStarts() {
     $view = $this->getBasicView();
 
@@ -227,6 +340,25 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedStarts() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: starts, Value: George
+    $filters['description']['group_info']['default_group'] = 2;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'George',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringNotStarts() {
     $view = $this->getBasicView();
 
@@ -258,6 +390,32 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedNotStarts() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: not_starts, Value: George
+    $filters['description']['group_info']['default_group'] = 3;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'John',
+      ),
+      array(
+        'name' => 'Ringo',
+      ),
+      array(
+        'name' => 'Paul',
+      ),
+      // There is no Meredith returned because his description is empty
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringEnds() {
     $view = $this->getBasicView();
 
@@ -285,6 +443,28 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedEnds() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Descriptino, Operator: ends, Value: Beatles
+    $filters['description']['group_info']['default_group'] = 4;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'George',
+      ),
+      array(
+        'name' => 'Ringo',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringNotEnds() {
     $view = $this->getBasicView();
 
@@ -313,6 +493,29 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedNotEnds() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Description, Operator: not_ends, Value: Beatles
+    $filters['description']['group_info']['default_group'] = 5;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'John',
+      ),
+      array(
+        'name' => 'Paul',
+      ),
+      // There is no Meredith returned because his description is empty
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringNot() {
     $view = $this->getBasicView();
 
@@ -341,6 +544,31 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+
+  function testFilterStringGroupedExposedNot() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Description, Operator: not (does not contains), Value: Beatles
+    $filters['description']['group_info']['default_group'] = 6;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+
+    $resultset = array(
+      array(
+        'name' => 'John',
+      ),
+      array(
+        'name' => 'Paul',
+      ),
+      // There is no Meredith returned because his description is empty
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+
+  }
+
   function testFilterStringShorter() {
     $view = $this->getBasicView();
 
@@ -368,6 +596,27 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedShorter() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: shorterthan, Value: 5
+    $filters['name']['group_info']['default_group'] = 4;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'John',
+      ),
+      array(
+        'name' => 'Paul',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
   function testFilterStringLonger() {
     $view = $this->getBasicView();
 
@@ -392,6 +641,25 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
 
+  function testFilterStringGroupedExposedLonger() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Name, Operator: longerthan, Value: 4
+    $filters['name']['group_info']['default_group'] = 5;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'Meredith',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+
   function testFilterStringEmpty() {
     $view = $this->getBasicView();
 
@@ -414,4 +682,129 @@ class ViewsHandlerFilterStringTest extends ViewsSqlTest {
     );
     $this->assertIdenticalResultset($view, $resultset, $this->column_map);
   }
+
+  function testFilterStringGroupedExposedEmpty() {
+    $filters = $this->getGroupedExposedFilters();
+    $view = $this->getBasicPageView();
+
+    // Filter: Description, Operator: empty, Value:
+    $filters['description']['group_info']['default_group'] = 7;
+    $view->set_display('page_1');
+    $view->display['page_1']->handler->override_option('filters', $filters);
+
+    $this->executeView($view);
+    $resultset = array(
+      array(
+        'name' => 'Meredith',
+      ),
+    );
+    $this->assertIdenticalResultset($view, $resultset, $this->column_map);
+  }
+
+  protected function getGroupedExposedFilters() {
+    $filters = array(
+      'name' => array(
+        'id' => 'name',
+        'table' => 'views_test',
+        'field' => 'name',
+        'relationship' => 'none',
+        'exposed' => TRUE,
+        'expose' => array(
+          'operator' => 'name_op',
+          'label' => 'name',
+          'identifier' => 'name',
+        ),
+        'is_grouped' => TRUE,
+        'group_info' => array(
+          'label' => 'name',
+          'identifier' => 'name',
+          'default_group' => 'All',
+          'group_items' => array(
+            1 => array(
+              'title' => 'Is Ringo',
+              'operator' => '=',
+              'value' => 'Ringo',
+            ),
+            2 => array(
+              'title' => 'Is not Ringo',
+              'operator' => '!=',
+              'value' => array('value' => 'Ringo'),
+            ),
+            3 => array(
+              'title' => 'Contains ing',
+              'operator' => 'contains',
+              'value' => 'ing',
+            ),
+            4 => array(
+              'title' => 'Shorter than 5 letters',
+              'operator' => 'shorterthan',
+              'value' => 5,
+            ),
+            5 => array(
+              'title' => 'Longer than 7 letters',
+              'operator' => 'longerthan',
+              'value' => 7,
+            ),
+          ),
+        ),
+      ),
+      'description' => array(
+        'id' => 'description',
+        'table' => 'views_test',
+        'field' => 'description',
+        'relationship' => 'none',
+        'exposed' => TRUE,
+        'expose' => array(
+          'operator' => 'description_op',
+          'label' => 'description',
+          'identifier' => 'description',
+        ),
+        'is_grouped' => TRUE,
+        'group_info' => array(
+          'label' => 'description',
+          'identifier' => 'description',
+          'default_group' => 'All',
+          'group_items' => array(
+            1 => array(
+              'title' => 'Contains the word: Actor',
+              'operator' => 'word',
+              'value' => 'actor',
+            ),
+            2 => array(
+              'title' => 'Starts with George',
+              'operator' => 'starts',
+              'value' => 'George',
+            ),
+            3 => array(
+              'title' => 'Not Starts with: George',
+              'operator' => 'not_starts',
+              'value' => 'George',
+            ),
+            4 => array(
+              'title' => 'Ends with: Beatles',
+              'operator' => 'ends',
+              'value' => 'Beatles.',
+            ),
+            5 => array(
+              'title' => 'Not Ends with: Beatles',
+              'operator' => 'not_ends',
+              'value' => 'Beatles.',
+            ),
+            6 => array(
+              'title' => 'Does not contain: Beatles',
+              'operator' => 'not',
+              'value' => 'Beatles.',
+            ),
+            7 => array(
+              'title' => 'Empty description',
+              'operator' => 'empty',
+              'value' => '',
+            ),
+          ),
+        ),
+      ),
+    );
+    return $filters;
+  }
+
 }
diff --git a/tests/views_query.test b/tests/views_query.test
index f44306a..4574d61 100644
--- a/tests/views_query.test
+++ b/tests/views_query.test
@@ -413,4 +413,20 @@ abstract class ViewsSqlTest extends ViewsTestCase {
 
     return $view;
   }
+
+  /**
+   * Build and return a Page view of the views_test table.
+   *
+   * @return view
+   */
+  protected function getBasicPageView() {
+    views_include('view');
+    $view = $this->getBasicView();
+
+    // In order to test exposed filters, we have to disable
+    // the exposed forms cache.
+    $view->cache_exposed_forms = FALSE;
+    $display = $view->new_display('page', 'Page', 'page_1');
+    return $view;
+  }
 }
diff --git a/views.module b/views.module
index 77caac4..a475f26 100644
--- a/views.module
+++ b/views.module
@@ -1876,8 +1876,10 @@ function views_exposed_form($form, &$form_state) {
   // Let form plugins know this is for exposed widgets.
   $form_state['exposed'] = TRUE;
   // Check if the form was already created
-  if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
-    return $cache;
+  if ($view->cache_exposed_forms) {
+    if ($cache = views_exposed_form_cache($view->name, $view->current_display)) {
+      return $cache;
+    }
   }
 
   $form['#info'] = array();
@@ -1893,7 +1895,18 @@ function views_exposed_form($form, &$form_state) {
   foreach ($view->display_handler->handlers as $type => $value) {
     foreach ($view->$type as $id => $handler) {
       if ($handler->can_expose() && $handler->is_exposed()) {
-        $handler->exposed_form($form, $form_state);
+        // Grouped exposed filters have their own forms.
+        // Instead of render the standard exposed form, a new Select or
+        // Radio form field is rendered with the available groups.
+        // When an user choose an option the selected value is split
+        // into the operator and value that the item represents.
+        if ($handler->is_a_group()) {
+          $handler->group_form($form, $form_state);
+          $id = $handler->options['group_info']['identifier'];
+        }
+        else {
+          $handler->exposed_form($form, $form_state);
+        }
         if ($info = $handler->exposed_info()) {
           $form['#info']["$type-$id"] = $info;
         }
diff --git a/views_ui.module b/views_ui.module
index 1a6f454..c2ea1ff 100644
--- a/views_ui.module
+++ b/views_ui.module
@@ -195,6 +195,12 @@ function views_ui_theme() {
       'file' => "includes/admin.inc",
     ),
 
+    // Group of filters.
+    'views_ui_build_group_filter_form' => array(
+      'render element' => 'form',
+      'file' => 'includes/admin.inc',
+    ),
+
     // tab themes
     'views_tabset' => array(
       'variables' => array('tabs' => NULL),
