How to create a custom views filter for a seach api solr view?

Created on 20 December 2023, 11 months ago
Updated 16 January 2024, 10 months ago

Problem/Motivation

I am running Drupal Core 10.1.6 and Group module 2.2.1. I have created a Search API view in which I would like to add a group entity reference field filter that will allow users to select multiple groups as filters. It should look like this:

But instead, I get this when I add the existing group field as filter:

I can resolve this with a custom views group filter, which I have actually created. The problem is this filter works in the traditional views method, by joining database tables:

  public function query() {
    if (!empty($this->value)) {
	  // Views join handler plugin: https://api.drupal.org/api/drupal/core%21modules%21views%21src%21Plugin%21views%21join%21JoinPluginBase.php/group/views_join_handlers/8.2.x
      $configuration01 = [
        'type' => 'LEFT',
		'table' => 'group_content_field_data',
        'field' => 'entity_id',
        'left_table' => 'node_field_data',
        'left_field' => 'nid',
        'operator' => '=',
      ];
      $configuration02 = [
        'type' => 'INNER',
		'table' => 'groups_field_data',
        'field' => 'id',
        'left_table' => 'group_content_field_data',
        'left_field' => 'gid',
        'operator' => '=',
      ];
      $join01 = Views::pluginManager('join')->createInstance('standard', $configuration01);
      $this->query->addRelationship('group_content_field_data', $join01, 'node_field_data');
      $join02 = Views::pluginManager('join')->createInstance('standard', $configuration02);
      $this->query->addRelationship('groups_field_data', $join02, 'group_content_field_data');
	  $this->query->addGroupBy("node_field_data.nid");

	  $types = sbn_get_node_group_types();
      $this->query->addWhere('AND', 'group_content_field_data.type', $types, 'IN');
      $this->query->addWhere('AND', 'groups_field_data.label', $this->value, 'IN');
    }
  }

Which, of course, doesn't work when dealing with a Search API Solr view. So my question is, how to create a similar custom views filter which will make the modifications necessary to the Solr query?

Steps to reproduce

Create a node group entity reference field for assigning nodes to groups:

Create standard Search API fulltext view following this guidance:

https://www.drupal.org/docs/7/modules/search-api/getting-started/search-...

If you add the node group field entity as a filter, it does not give you the ability to display all available groups to the user. You can only enter in the group label you are looking for (only one).

I would like to be able to select multiple groups from a select list.

I will need a custom views filter for this.

Proposed resolution

I have searched around and actually created a custom views node group filter designed to a) display a select list of all available groups and b) alter the Solr query. It does not work because I don't know how to do it, and have been unable to find any guidance. This is what I have attempted so far:

namespace Drupal\sbn\Plugin\views\filter;

use Drupal\search_api\Plugin\views\filter\SearchApiFilter;
use Drupal\views\Plugin\views\display\DisplayPluginBase;
use Drupal\views\ViewExecutable;

/**
 * Filters by Node Group.
 *
 * @ingroup views_filter_handlers
 *
 * @ViewsFilter("search_api_node_group")
 */
class SearchApiNodeGroupFilter extends SearchApiFilter {

  /**
   * {@inheritdoc}
   */
  public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
    parent::init($view, $display, $options);
    $this->valueTitle = t('Search API Node Groups');
    $this->definition['options callback'] = [$this, 'generateOptions'];
  }

  /**
   * Helper function that generates the options.
   *
   * @return array
   *   An array of group labels.
   */
  public function generateOptions() {
    // Assuming sbn_get_group_labels() returns an associative array of group labels
    return sbn_get_group_labels();
  }

	/**
	 * {@inheritdoc}
	 */
	public function query() {
		if (!empty($this->value) && is_array($this->value)) {
			// Retrieve the underlying Solr query.
			$solr_query = $this->query->getSolrQuery();

			// Construct the filter query.
			$filter_query = 'sm_sbn_add_nodegroup_str:(';
			$filter_queries = [];
			foreach ($this->value as $val) {
				// Escape special characters in Solr query syntax.
				$escaped_val = $solr_query->getHelper()->escapePhrase($val);
				$filter_queries[] = '"' . $escaped_val . '"';
			}
			$filter_query .= implode(' OR ', $filter_queries) . ')';

			// Add the filter query to the Solr query.
			$solr_query->addFilterQuery(['key' => 'sm_sbn_add_nodegroup_str', 'query' => $filter_query]);		}
	}
}


This code is structured according to the custom views group filter I created that does work (for non search api solr views).

Remaining tasks

Just need some guidance on how to get the above code to work, and that should solve my problem.

Note: sbn_add_nodegroup_str is actually a Solr field created using a search api processor plugin. So, all I need the custom views filter to do is alter the Solr query and add the new constraints for this one field.

💬 Support request
Status

Fixed

Version

1.31

Component

General code

Created by

🇺🇸United States somebodysysop

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Comments & Activities

  • Issue created by @somebodysysop
  • 🇺🇸United States somebodysysop

    p.s.

    This is the general structural guidance I tried to follow to accomplish the custom views search api solr filter:

  • Status changed to Fixed 11 months ago
  • 🇦🇹Austria drunken monkey Vienna, Austria

    I don’t think you even need a custom filter, you can just use the existing SearchApiOptions filter plugin. Simply use hook_views_data_alter() to change the “Node Group » Group » Title” field’s filter plugin to search_api_options, plus set an appropriate options callback so the plugin knows the list of available values. (See _search_api_views_handler_adjustments(), under $type == 'options'.) That’s all that should be needed.

    If you do want to use your own filter plugin, you simply need to use one line to add the condition to the query:

        $this->getQuery()->addCondition('sbn_add_nodegroup_str', $this->value, 'IN');
    

    Here, sbn_add_nodegroup_str is the Search API field ID of the field, no need to make this Solr-specific in any way.
    Or you simply let your filter plugin inherit from SearchApiOptions and merely override the getValueOptions() method so it doesn’t need an options callback set.

    Hope this helps.

  • 🇺🇸United States somebodysysop

    @druken monkey

    Thank you so much! Worked like a charm. For anyone else who runs into this situation:

    First get the label, machine name and path property for the field for which you want a views filter. These can be found in Search API datasource fields. Also get the ID of the index.

    In hook_views_data_alter:

    	////////////////////////////
    	// node group filter plugin
    	////////////////////////////
        // Targeting the specific Search API index and field.
        if (isset($data['search_api_index_default_solr_index']['label_2'])) {
    
            // Change the filter plugin to 'search_api_options'.
            $data['search_api_index_default_solr_index']['label_2']['filter']['id'] = 'search_api_options';
    
            // Set an options callback to your custom function.
            $data['search_api_index_default_solr_index']['label_2']['filter']['options callback'] = 'sbn_get_group_titles';
        }
    
    

    The only other thing you'll need is the function to get the group titles (if you don't already have one)

    /**
     * Options callback to provide the list of group titles.
     */
    function sbn_get_group_titles() {
        $options = [];
        // Query to load all group entities or use an appropriate method to fetch group titles.
        $groups = \Drupal::entityTypeManager()->getStorage('group')->loadMultiple();
        foreach ($groups as $group) {
            $options[$group->id()] = $group->label();
        }
        return $options;
    }
    
    

    That's it. You should be good to go. Just add the filter to your view:

  • 🇺🇸United States somebodysysop

    While the SearchApiOptions filter plugin works great for constructing the groups filter, it doesn't filter both nodes and files (even though the files do contain the Solr sbn_add_nodegroup_str field which is created by the Search API processor plugin).

    So, I'm thinking the better route is to create my own filter that will modify the Solr query itself. Based upon your suggestions, here is my new search api group node filter class file:

    
    namespace Drupal\sbn\Plugin\views\filter;
    
    use Drupal\search_api\Plugin\views\filter\SearchApiFilter;
    use Drupal\views\Plugin\views\display\DisplayPluginBase;
    use Drupal\views\ViewExecutable;
    use Drupal\views\Plugin\views\filter\StringFilter;
    use Drupal\views\Plugin\views\filter\ManyToOne;
    use Drupal\views\Plugin\views\query\Sql;
    use Drupal\views\Views;
    use Drupal\views\Plugin\views\relationship\RelationshipPluginBase;
    use Drupal\Views\Plugin\views\join\JoinPluginBase;
    use Drupal\views\Plugin\ViewsHandlerManager;
    use Drupal\search_api\Plugin\views\query\SearchApiQuery;
    
    
    /**
     * Filters by Node Group.
     *
     * @ingroup views_filter_handlers
     *
     * @ViewsFilter("node_group_filter")
     */
    class NodeGroupFilter extends SearchApiFilter {
    
      /**
       * The current display.
       *
       * @var string
       *   The current display of the view.
       */
      protected $currentDisplay;
    
      /**
       * {@inheritdoc}
       */
      public function init(ViewExecutable $view, DisplayPluginBase $display, array &$options = NULL) {
        parent::init($view, $display, $options);
        $this->valueTitle = t('Search API Node Groups');
        $this->definition['options callback'] = [$this, 'generateOptions'];
        $this->currentDisplay = $view->current_display;
      }
    
      /**
       * Helper function that generates the options.
       *
       * @return array
       *   An array of group labels.
       */
      public function generateOptions() {
        // Assuming sbn_get_group_labels() returns an associative array of group labels
        return sbn_get_group_labels();
      }
    
    	/**
    	 * {@inheritdoc}
    	 */
    	public function query() {
    		if (!empty($this->value) && is_array($this->value)) {
    			$this->getQuery()->addCondition('sbn_add_nodegroup_str', $this->value, 'IN');		
    		}
    	}
    }
    

    But, views doesn't even recognize it. I can't find it in "Add filter criteria" to select it Can you see what the problem is?

    Thanks!

  • 🇺🇸United States somebodysysop

    So, it turns out the resolution in #4 💬 How to create a custom views filter for a seach api solr view? Fixed did in fact work. It turns out that the site I was testing on was using outdated Group labels. The easy fix was to re-index the site, and the Group filter works as it should.

  • Automatically closed - issue fixed for 2 weeks with no activity.

Production build 0.71.5 2024