2

Using SearchAPI and FacetAPi with Solr I have a facet block filled by one Solr indexed field i.e. "region".

Example:

  • Region A
  • Region B
  • Region C

I need to programmatically combine two of the active facets to show the accumulated results, while keeping the original facets untouched.

How to achieve this?

  • Region A
  • Region AB <= new facet which accumulates Region A + Region B
  • Region B
  • Region C

There is a similar use case described in How can I merge or combine facets?

The difference in my use case is that I need the "combined facet" as an additional item, not replacing an existing facet item.

Any idea or hint is highly appreciated. Thank you.

1 Answer 1

2

The above use case may be an exotic one but I post my solution in case anybody else has a similar one and maybe it's a motivation for a more elegant solution.

Versions used:
- Drupal 7.38
- facetapi 7.x-1.5+1-dev
- facetapi_bonus 7.x-1.1+7-dev
- search_api 7.x-1.15
- search_api_solr 7.x-1.8

Steps in short

  1. Add a new facetapi plugin to filter active facet items, using hook_facetapi_filters() [1]
  2. Plugin method execute(): Add the additional facet items to the $build array of the active facet items
  3. Alter the Solr query for the newly added facet items to combine the two with operator OR, using hook_search_api_solr_query_alter() [2]
  4. Activate the new plugin in the index facet configuration

Steps in detail

  1. Add a new facetapi plugin with hook_facetapi_filters()

Documentation see facetapi.api.php and class FacetapiFilter [3]. Note the programmers comments in the docbloc of FacetapiFilter where another solution is suggested by adding an "alter callback". Unfortunately I couldn't get the alter callback to do what I like to do.

/**
 * Implements hook_facetapi_filters().
 * Define the plugin class to add additional facet items to an active facet.
 */
function mymodule_facetapi_filters() {
 return array(
   'add_items' => array(
     'handler' => array(
       'label' => t('Add facet items to facet "region"'),
       'class' => 'MyModuleFacetapiFilterAddItems',
       'query types' => array('term'),
     ),
   ),
 );
}      
  1. Plugin method execute(): Add the additional facet item to the $build array of the active facets

Sample plugin code.

/**
 * Plugin that adds additional facet item(s) to an active facet.
 */
class MyModuleFacetapiFilterAddItems extends FacetapiFilter {

  // Execute active facets.
  public function execute(array $build) {
    // Taxonomy term ids of the facet items to combine into a new facet item.
    $tid1 = 1;
    $tid2 = 2;
    // Create the additional facet item. Check that the original facet items are
    // available.
    if (isset($build[$tid1])  && isset($build[$tid2])) {
      $add = array();
      $add['#markup'] = 'Region AB';  // the label of the new facet item
      $add['#path'] = 'search';  // your base search path
      $add['#html'] = FALSE;
      $add['#indexed_value'] = $tid1;  // @see comments below
      $add['#count'] = $build[$tid1]]['#count'] + $build[$tid2]['#count'];  // accumulate the #count of the two combined facet items
      $add['#active'] = 0;
      $add['#item_parents'] = array();
      $add['#item_children'] = array_merge($build[$tid1]['#item_children'], $build[$tid2]['#item_children']);  // merge the item children
      $add['#query'] = array(
        'f' => array(
          0 => 'field_region:' . $tid1,  // add both term ids in the #query f parameter
          1 => 'field_region:' . $tid2,  // leads to URL parameters like this: f[0]=field_region:1&f[1]=field_region:2
        ),
      );
      $build[] = $add;
    }
    return $build;
  }

  // Adds settings to the filter form, here only used for users' documentation. Inspired by facetapi_bonus.
  public function settingsForm(&$form, &$form_state) {
    $form['add_items'] = array(
      '#title' => t('Add a facet item for facet "region"'),
      '#type' => 'checkbox',
      '#default_value' => TRUE,
      '#suffix' => t('
        By enabling this filter, an additional facet item is added to facet "region".
        <br/>
        At the moment this list is hard coded and consists of
        <br/>
        - Region AB (combines Region A + Region B, with operator OR)
      '),
    );

    // TODO
    // - add UI settings to avoid hard coding stuff in code
    // - would be nice to be able to specify the operator (conjuntion) OR or AND in the $form array to pass it to the Solr query
  }
}   

So far is achieved that the new facet item in facet "region" shows up. But it has zero results.

Region A (10)
Region AB (0)
Region B (15)

As the operator (conjunction) for the facet "region" in my case is configured with "AND", the result of the newly added facet item "Region AB" is zero. So I need to combine the two filters with OR in the Solr query.

  1. Alter the Solr query for the newly added facet items to combine the two with operator OR.

Sample hook_search_api_solr_query_alter() code.

/**
 * Implementation of hook_search_api_solr_query_alter()
 */
function mymodule_search_api_solr_query_alter(array &$call_args, SearchApiQueryInterface $query) {
  // Only alter queries on the node index.
  if ($query->getIndex()->machine_name != 'node_index') {
    return;
  }

  // Facet query params array.
  $fq = $call_args['params']['fq'];

  // Get the key of the query that we need to alter. Depending on the active facets and if the
  // visitor is logged in or not, the fq array contains more or less queries in our environment.
  // The code below assures we modify the correct query.
  //
  // In this demonstration we use taxonomy term ids: tid1 = 1, tid2 = 2 for Region AB.
  // If both facet queries are present in the fq array, combine them to one query with operator OR.
  // Afterwards unset the second query.
  if ( (false !== $key1 = array_search('{!tag=facet:field_region_ref}im_field_region_ref:"1"', $fq))
  && (false !== $key2 = array_search('{!tag=facet:field_region_ref}im_field_region_ref:"2"', $fq))
  ) {
    $call_args['params']['fq'][$key1] = '{!tag=facet:field_region_ref}im_field_region_ref:"1" OR im_field_region_ref:"2"';
    unset($call_args['params']['fq'][$key2]);
    return;
  }
}

Now the counter of the newly added facet item shows up correctly.

Region A (10)
Region AB (25)
Region B (15)

That's it about the coding. Of course "mymodule" has to be changed to the name of your module.

Remains just the last step to active the new plugin.

  1. Activate the new facetapi plugin in the index facet configuration

In the Drupal administration UI visit "Configuration" > "Search and metadata" > Search API
- edit the Solr node index, visit tab "Facets"
- find facet "region" and select the "configure filters"
- active the new plugin "Add facet items .."
- store the changed settings

Note some drawbacks with this solution

1) Hard coded facet item properties
2) Facet item property: #indexed_value: can only specify one value. Don't know if this has implications on the counters when dependent facets are clicked. So far everything seems to be fine in my implementation.
3) Kind of complicated code to alter the query of the newly added combined facet items. It would be much more elegant if I could specify the operator as a parameter of the #query property when creating a new facet item. Or even easier: specify the fq query right in the wanted way like this: fq[0]=tid:(1 OR 2) -- only possible right now in module "ApacheSolr".

Links

[1] http://www.drupalcontrib.org/api/drupal/contributions!facetapi!facetapi.api.php/function/hook_facetapi_filters/7
[2] http://www.drupalcontrib.org/api/drupal/contributions!search_api_solr!search_api_solr.api.php/function/hook_search_api_solr_query_alter/7
[3] http://www.drupalcontrib.org/api/drupal/contributions!facetapi!plugins!facetapi!filter.inc/class/FacetapiFilter/7

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge that you have read and understand our privacy policy and code of conduct.

Not the answer you're looking for? Browse other questions tagged or ask your own question.