Nesting Filter Groups

Created on 8 November 2017, about 7 years ago
Updated 21 May 2024, 6 months ago

This feature is needed more than you think.

If you do not agree with me, can anyone please tell me how to do this in a hook with a concrete code example?

I need this (it's only one example):


- Field: Some filter

- AND Field: Some filter

- AND Filter Group

    - Field: Some filter

    - OR Filter Group

            - Field: Some filter

            - AND Field: Some filter

Some filters are also exposed.

✨ Feature request
Status

Active

Version

11.0 πŸ”₯

Component
ViewsΒ  β†’

Last updated about 5 hours ago

Created by

πŸ‡¨πŸ‡­Switzerland handkerchief

Live updates comments and jobs are added and updated live.
  • views

    Involves, uses, or integrates with views. In Drupal 8 core, use the β€œVDC” tag instead.

Sign in to follow issues

Comments & Activities

Not all content is available!

It's likely this issue predates Contrib.social: some issue and comment data are missing.

  • πŸ‡ΈπŸ‡°Slovakia lubwn

    For whoever stumbled upon this issue I have a coding solution, you can basicly use HOOK_views_query_alter() to add nested WHERE condition to your already existing views query.

    You have to actually use $query->addWhereExpression() from QueryPluginBase class to achieve that.

    It basicly gives you the freedom to write whatever query you see fit in WHERE condition, and is incredibly easy to debug (you simply turn-on views SQL queries in views settings and after each "update preview" button you see the executed query no matter how bad or good it is)

    Code I have used goes as follows:

    use Drupal\views\ViewExecutable;
    use Drupal\views\Plugin\views\query\QueryPluginBase;
    
    function MYMODULE_views_query_alter(ViewExecutable $view, QueryPluginBase $query) {
    
    	if ($view->id() == 'YOUR_VIEW_ID') {
    
    		// Add LEFT JOIN to First Field
    		$configuration = [
    		  'table' => 'node__field_YOUR_FIELD_NAME',
    		  'field' => 'entity_id',
    		  'left_table' => 'node_field_data',
    		  'left_field' => 'nid'
    		];
    		$join = Drupal::service('plugin.manager.views.join')->createInstance('standard', $configuration);
    		$query->addRelationship('node__field_YOUR_FIELD_NAME', $join, 'node_field_data');
    
    		 // Here you can add LEFT JOIN to second field...
    		
    		/*
    		 * VIEWS ALTER LAYOUT:
    		 *
    		 
    		- Exposed Filters -
    		AND
    		(
    			(
    				Field 1 = Value 1 AND
    				Field 2 = Value 2
    			)
    			OR
    			(
    				Field 2 = Value 3
    			)
    		)
    
    		// Here you basicly write whatever will run inside WHERE clause in query
    		$queryExpression = "
    		(
    			(node__field_YOUR_FIELD_NAME.field_YOUR_FIELD_NAME_value IN('1'))
    			AND
    			(node__field_SECOND_FIELD_NAME.field_SECOND_FIELD_NAME_value IN('2'))
    		)
    		OR
    		(
    			node__field_SECOND_FIELD_NAME.field_SECOND_FIELD_NAME_value IN('3')
    		)
    		";
    		
    		$group_id = $query->setWhereGroup('AND');
    		$query->addWhereExpression($group_id, $queryExpression);
    		
    	}
    	
    }
    

    Downsides are that you need to write your own JOINs, because if there is no filter added by Views UI you have no access to fields you probably need, so I left the relationship in code above.

    I searched the whole internet for the solution and this is the closest and arguably easiest way to achieve nested groups in current views version. It would be a nice addition in UI but I doubt it will ever happen, seeing this is not the feature many needs. Hope the code helps someone in the future.

  • First commit to issue fork.
  • πŸ‡©πŸ‡ͺGermany tgauges

    I changed the existing code in 2921987-nesting-filter-groups to allow for nested condition groups.
    With this change, one would still need to implement the views query alter hook, but at least now the nested conditions defined there will work.

    I'm not sure about the consequences of this new implementation, I just implemented what seemed to make sense. Surely there will need to be new tests and maybe also documentation?

    The next step would be to implement the definition of nested condition groups via the Views UI.

  • πŸ‡ΊπŸ‡ΈUnited States loopy1492

    Yeah, nested filter groups is definitely something we need.

  • πŸ‡ΊπŸ‡ΈUnited States loopy1492

    I was able to write this alter for a search API filter. Yes, you can nest Condition Groups within other Condition Groups with this, which is a real lifesaver. This function was added to a Service, but I suspect it could be added in a .module file instead. I think. I'm also pretty sure this method (or similar) can be used to alter a sql query.

    I left the debug code in (commented out) so you could see what objects we're dealing with here.

    use Drupal\views\ViewExecutable;
    use Drupal\views\Plugin\views\query\QueryPluginBase;
    use Symfony\Component\HttpFoundation\Request;
    use Drupal\search_api\Plugin\views\query\SearchApiQuery;
    use Drupal\search_api\Query\ConditionGroup;
    use Drupal\taxonomy\Entity\Term;
    
    /**
       * Filter Frame View - query alter, custom updates to the view query.
       *
       * @param \Drupal\views\ViewExecutable $view
       *   Drupal view.
       * @param \Drupal\views\Plugin\views\query\QueryPluginBase $query
       *   Query.
       */
      public function myFilteredViewViewQueryAlter(ViewExecutable $view, QueryPluginBase $query) {
    
        if ($view->id() == 'my_view_id') {
          // Check if the query is an instance of SearchApiQuery
          if ($query instanceof SearchApiQuery) {
            // Get the Search API query object
            $search_api_query = $query->getSearchApiQuery();
            // Get the current request object
            $request = \Drupal::request();
            // Get the exposed filter values from the query string
            $queryParams = $request->query->all();
            // Debug variable
            $term_values = "";
            // Check if the field_my_filter_term_field_one or field_my_filter_term_field_two parameters exist and are arrays
            if ((isset($queryParams['field_my_filter_term_field_one']) && is_array($queryParams['field_my_filter_term_field_one'])) ||
              (isset($queryParams['field_my_filter_term_field_two']) && is_array($queryParams['field_my_filter_term_field_two']))
            ) {
              // Taxonomy variables
              $term_storage = \Drupal::entityTypeManager()->getStorage('taxonomy_term');
              // Create a new condition group for the wrapper
              $conditionGroup = new ConditionGroup('AND');
              // ID tracker
              $old_parent_tid = null;
    
              // Helper function to process filter terms
              $process_filter_terms = function ($field_name) use (&$term_values, $term_storage, &$conditionGroup, &$old_parent_tid, $queryParams) {
                $innerConditionGroup = new ConditionGroup('OR');
                foreach ($queryParams[$field_name] as $value) {
                  // Get the taxonomy term from the filter
                  $term = Term::load($value);
                  // Check if the term is loaded successfully.
                  if ($term) {
                    // Get information from the taxonomy term.
                    $term_name = $term->getName();
                    $term_id = $term->id();
                    $parent_tid = $term->get('parent')->target_id;
                    if (!empty($parent_tid)) {
                      // Load the parent term using the correct term storage.
                      $parent_term = $term_storage->load($parent_tid);
                      // Check if the parent term is loaded.
                      if ($parent_term) {
                        if ($old_parent_tid != $parent_tid) {
                          // If this parent term is different from the previous one
                          if ($old_parent_tid != null) {
                            // Assign the inner condition group to the outer condition group
                            $conditionGroup->addConditionGroup($innerConditionGroup);
                            // Re-cast the inner condition group
                            $innerConditionGroup = new ConditionGroup('OR');
                          }
                          // set the ID tracker
                          $old_parent_tid = $parent_tid;
                        }
                      }
                    }
                    // Add the condition to the current condition group
                    $innerConditionGroup->addCondition($field_name, $term_id, '=');
                    // Concatenate values for debugging.
                    // $term_values .= $parent_tid . ' : ' . $term_name . " : " . $term_id . ", ";
                  }
                }
                // Add the last inner condition group to the outer condition group if it's not empty
                if (!empty($innerConditionGroup->getConditions())) {
                  $conditionGroup->addConditionGroup($innerConditionGroup);
                }
              };
    
              // Process both field_my_filter_term_field_one and field_my_filter_term_field_two using our helper function
              if (isset($queryParams['field_my_filter_term_field_one']) && is_array($queryParams['field_my_filter_term_field_one'])) {
                $process_filter_terms('field_my_filter_term_field_one');
              }
              if (isset($queryParams['field_my_filter_term_field_two']) && is_array($queryParams['field_my_filter_term_field_two'])) {
                $process_filter_terms('field_my_filter_term_field_two');
              }
    
              // Set the condition group to the search API query
              $search_api_query->addConditionGroup($conditionGroup);
              // debugging
              // if (isset($queryParams['field_my_filter_term_field_one'])) dpm($queryParams['field_my_filter_term_field_one'], 'Exposed Filter Values');
              // if (isset($queryParams['field_my_filter_term_field_two'])) dpm($queryParams['field_my_filter_term_field_two'], 'Exposed MT Filter Values');
              // dpm($term_values);
            }
    
            // Debug the modified conditions in the Search API query
            // dpm($search_api_query->getConditionGroup(), 'Condition Groups in Search API Query');
            // Debug the modified search API query object
            // dpm($search_api_query);
          }
      }
    
Production build 0.71.5 2024