Drupal Solr Search with Domain Access filtering

Metal Toad has had the privilege to work over the past two years with DC Comics. What makes this partnership even more exciting, is that the main dccomics.com site also includes sites for Vertigo Comics and Mad Magazine. Most recently Metal Toad was given the task of building the new search feature for all three sites. However, while its an awesome privilege to work with such a well known brand as DC, this does not come without a complex set of issues for the three sites when working with Apache Solr search and Drupal.

Problem: Say you have a Drupal installation that presents three separate domains to the public (using the great Domain Access module), and you'd like to set up a search system for these sites. On this project we used Search API (the base search module), with the Search API Solr Search (tying it to the solr server) & Search API Views (letting you use Views to create your Solr queries) modules giving us Solr integration. These modules together give us Solr access and the ability to create one Solr index for all three domains.

I quickly found Search API Solr doesn't currently have a way to store each entity's Domain Access information - example: the Batman character entity has Domain Access assigned to it, which is a list of the domains that entity is available on. You cannot currently query by that information when searching, meaning searches were returning results from all three domains, not the site the user was currently browsing.

One Solution: Add a field to each of the indexed entities with its Domain Access ID's (keep in mind that an entity can belong to multiple domains) on insertion into the Solr index, and alter the Solr query with a Filter Query (fq) on the current domain.

This thread on drupal.org has some good direction on this, but I needed a slightly more generic solution. So without further discussion, here's the code:

/**
 * Alter Solr documents before they are sent to Solr for indexing.
 *
 * @param array $documents
 *   An array of SearchApiSolrDocument objects ready to be indexed, generated
 *   from $items array.
 * @param SearchApiIndex $index
 *   The search index for which items are being indexed.
 * @param array $items
 *   An array of items being indexed.
*/
 
function MY_MODULE_search_api_solr_documents_alter(array &$documents, SearchApiIndex $index, array $items) {
  // Adds a domain field to all documents.
  foreach ($documents as $document) {
    // Assumes the nid is an indexed field
    $nid = $document->getField('is_nid');
    // Grab the node from the db for it's Domain Access info
    $node = node_load($nid['value']);
    // Insert the domains into the document
    foreach ($node->domains as $key => $value) {
      $document->addField('im_domain_access', $value, null);
    }
  }
}
 
/**
 * Lets modules alter a Solr search request before sending it.
 *
 * Apache_Solr_Service::search() is called afterwards with these parameters.
 * Please see this method for details on what should be altered where and what
 * is set afterwards.
 *
 * @param array $call_args
 *   An associative array containing all three arguments to the
 *   SearchApiSolrConnectionInterface::search() call ("query", "params" and
 *   "method") as references.
 * @param SearchApiQueryInterface $query
 *   The SearchApiQueryInterface object representing the executed search query.
*/
function MY_MODULE_search_api_solr_query_alter(array &$call_args, SearchApiQueryInterface $query) {
  $domain = domain_get_domain();
  $id = $domain['domain_id'];
  // Add our domain field to the Solr filter querys
  $call_args['params']['fq'][] = 'im_domain_access:' . $id;
}

I hope this helps someone else who has the same problem to solve. Feel free to make any suggestions/ask any questions in the comments.

Filed under:

Comments

Hey Jeff,
I actually came across your solution in my search for any sort of direct documentation on this. Since the hooks are a bit different I couldn't use your exact approach, but I'm glad we've got these two module directions covered now.
Cheers!

Thanks for posting, great tip!

However, it's also possible to make this work regardless of the backend (Solr, database, …) by creating a data alteration that does this. As an example, take the "Node access" data alteration (the class, the definition hook and the query alter hook).

But I understand this way is much easier for a custom solution, and should work just as well. (You just can't disable it in the UI.)
My apologies, the "proper way" should become a lot simpler to implement in Drupal 8!

No need to apologize, I totally understand that maintaining a module doesn't always give one the time to integrate it with every other contrib module. I'm really happy that Search Api has hooks implemented that gave me a pretty straightforward way to make this work.

A data alteration would be a cleaner, more user friendly approach though. I appreciate you taking the time to point me in that direction.

Cheers! And thanks so much for all your work on Search API.

Thanks for this very useful information. There is one catch: The domain option "send to all affiliates" is not detected this way. Did not find a nice way to detect this besides checking the subdomains array for "All affiliates".

Add new comment

Restricted HTML

  • Allowed HTML tags: <a href hreflang> <em> <strong> <cite> <blockquote cite> <code> <ul type> <ol start type> <li> <dl> <dt> <dd> <h2 id> <h3 id> <h4 id> <h5 id> <h6 id>
  • You can enable syntax highlighting of source code with the following tags: <code>, <blockcode>, <cpp>, <java>, <php>. The supported tag styles are: <foo>, [foo].
  • Web page addresses and email addresses turn into links automatically.
  • Lines and paragraphs break automatically.

Ready for transformation?