- πΈπ°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); } }