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 a0f86fd..56480ef 100644
--- a/handlers/views_handler_filter.inc
+++ b/handlers/views_handler_filter.inc
@@ -64,6 +64,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'])) {
@@ -104,6 +105,26 @@ 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'),
+        'remember' => array('default' => 0),
+        'default_group' => array('default' => 'All'),
+        'group_items' => array('default' => array()),
+      ),
+    );
     return $options;
   }
 
@@ -120,6 +141,18 @@ 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);
+  }
+
+  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
@@ -130,19 +163,37 @@ 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);
+      }
     }
   }
 
@@ -152,9 +203,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);
+    }
   }
 
   /**
@@ -162,11 +216,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);
+    }
   }
 
   /**
@@ -248,6 +306,79 @@ 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) {
@@ -405,6 +536,58 @@ class views_handler_filter extends views_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.'));
+          }
+        }
+
+      }
+    }
+  }
+
+  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;
+    }
+
+    $form_state['values']['options']['group_info']['group_items'] = $groups;
+   }
+
+  /**
    * Provide default options for exposed filters.
    */
   function expose_options() {
@@ -420,6 +603,49 @@ 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',
+      'remember' => FALSE,
+      'default_group' => 'All',
+      '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'])) {
+      $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,
+      );
+      $this->options['expose']['label'] = '';
+    }
+  }
+
+  /**
    * Render our chunk of the exposed filter form when selecting
    *
    * You can override this if it doesn't do what you expect.
@@ -471,6 +697,186 @@ 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']['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-remove-' . $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,
+    );
+
+    $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.
    */
@@ -546,6 +952,13 @@ class views_handler_filter extends views_handler {
       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'],
@@ -553,6 +966,75 @@ 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.
+   */
+  function convert_exposed_input(&$input) {
+    if ($this->is_a_group()) {
+      $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'];
+        $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;
+      }
+    }
+  }
+
+  /**
+   * 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.
@@ -698,6 +1180,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 f795b9f..8c89e46 100644
--- a/handlers/views_handler_filter_boolean_operator.inc
+++ b/handlers/views_handler_filter_boolean_operator.inc
@@ -123,6 +123,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 b89d003..1f25f92 100644
--- a/handlers/views_handler_filter_date.inc
+++ b/handlers/views_handler_filter_date.inc
@@ -90,6 +90,33 @@ 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 8054542..a010593 100644
--- a/handlers/views_handler_filter_in_operator.inc
+++ b/handlers/views_handler_filter_in_operator.inc
@@ -284,6 +284,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 71d8b9e..b0a751f 100644
--- a/handlers/views_handler_filter_numeric.inc
+++ b/handlers/views_handler_filter_numeric.inc
@@ -257,6 +257,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 eec7a07..264d658 100644
--- a/handlers/views_handler_filter_string.inc
+++ b/handlers/views_handler_filter_string.inc
@@ -143,6 +143,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..5984bcd 100644
--- a/help/filter.html
+++ b/help/filter.html
@@ -15,6 +15,19 @@ 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>
+
 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 ac23e7a..82cc751 100644
--- a/includes/admin.inc
+++ b/includes/admin.inc
@@ -3491,6 +3491,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']);
   }
@@ -3521,6 +3522,73 @@ function theme_views_ui_expose_filter_form($variables) {
 }
 
 /**
+ * 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']['All']['#title'] = '';
+
+  $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,
+    ),
+  );
+
+  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]),
+      '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
  */
 function views_ui_rearrange_form_submit($form, &$form_state) {
@@ -4483,6 +4551,46 @@ function views_ui_config_item_form_expose($form, &$form_state) {
 }
 
 /**
+ * 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.
  */
 function views_ui_config_item_extra_form($form, &$form_state) {
diff --git a/includes/handlers.inc b/includes/handlers.inc
index 45a3a45..1ec2790 100644
--- a/includes/handlers.inc
+++ b/includes/handlers.inc
@@ -663,6 +663,8 @@ class views_handler extends views_object {
     return !empty($this->options['exposed']);
   }
 
+  function is_a_group() { return FALSE; }
+
   /**
    * Take input from exposed handlers and assign to this handler, if necessary.
    */
diff --git a/includes/view.inc b/includes/view.inc
index 85b70e6..f30af60 100644
--- a/includes/view.inc
+++ b/includes/view.inc
@@ -1042,6 +1042,14 @@ class view extends views_db_object {
       if (!empty($handlers[$id]) && is_object($handlers[$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);
+            $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) {
diff --git a/js/views-admin.js b/js/views-admin.js
index e2cd16f..d627256 100644
--- a/js/views-admin.js
+++ b/js/views-admin.js
@@ -815,7 +815,7 @@ Drupal.behaviors.viewsRemoveIconClass.attach = function (context, settings) {
     $('.icon', $this).removeClass('icon');
     $('.horizontal', $this).removeClass('horizontal');
   });
-}
+};
 
 /**
  * Change "Expose filter" buttons into checkboxes.
@@ -823,7 +823,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++) {
@@ -840,13 +840,13 @@ 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.$checkbox.click($.proxy(this, 'clickHandler'));
+  this.$input.click($.proxy(this, 'clickHandler'));
 };
 
 /**
diff --git a/views.module b/views.module
index 1b637e4..3c315f3 100644
--- a/views.module
+++ b/views.module
@@ -1907,7 +1907,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 a4480a9..1cc7e1a 100644
--- a/views_ui.module
+++ b/views_ui.module
@@ -203,6 +203,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),
