Wondering if you can suggest a solution or guide me where to start in integrating Domain Access module with Search API? I'd like to be able to restrict the search results in Views by domain.

Comments

drunken monkey’s picture

Category: feature » support

Depends on what the Domain Access provides? New Field API field types? New entities?
In any case, integrating with the Entity API is what you'll have to do to use that data with the Search API.

ximo’s picture

I needed the same functionality (except I'm not using Views), and got it sort of working..

It only indexes the source domain of nodes, I couldn't get multivalue for all domains to work. I tried setting the type to list<integer>, but that would only get rewritten to integer in search_api_extract_fields() and stored as a single value.

@drunken monkey, any advice as to how I can get multivalue working? I'd like to index all the values of $node->domains, which is an array of domain IDs. I would also need to account for "Send to all affiliates", which is represented by -1.

/**
 * Implements hook_entity_property_info_alter().
 *
 * Adds an entity property for the source domain of the node.
 */
function custom_entity_property_info_alter(&$info) {
  $properties = &$info['node']['properties'];

  $properties['domain_source'] = array(
    'label' => t('Source domain'),
    'description' => t('The source domain of the node.'),
    'type' => 'integer',
    'getter callback' => 'entity_property_verbatim_get',
  );
}

/**
 * Implements hook_search_api_query_alter().
 *
 * Adds a filter for the current domain.
 */
function custom_search_api_query_alter(SearchApiQueryInterface $query) {
  $domain = domain_get_domain();
  $query->condition('domain_source', $domain['domain_id']);
}

I wouldn't know how to expose the filter to Views though.

Edit:
Meh, I crossposted when simplifying the code to use $node->domain_source instead of $node->domains. The smb_solr_entity_property_get_domain() function drunken monkey mentions below is this custom getter:

/**
 * Getter callback; returns the main domain that the node belongs to.
 */
function smb_solr_entity_property_get_domain($data, array $options, $name) {
  $property = entity_property_verbatim_get($data, $options, $name);
  return (!empty($property) ? array_shift($property) : NULL);
}
drunken monkey’s picture

So the domain is no Field API field?

Having 'type' => 'list<integer>' should work when you return an array from custom_entity_property_get_domain().
Also in custom_entity_property_get_domain() you can just check for -1 and return an array with all domain IDs in that case.

leewillis77’s picture

Hi, we've just come across this issue as well. Our final solution (For reference) was this:

/**
 * Implements hook_entity_property_info_alter().
 *
 * Adds an entity property for the domain access of the node.
 */

function hpsm_search_api_domain_filter_entity_property_info_alter(&$info) {

  $properties = &$info['node']['properties'];

  $properties['hpsm_domain_access'] = array(
    'label' => t('Domain Access Information'),
    'description' => t('The domains to which the node is published.'),
    'type' => 'list<integer>',
    'getter callback' => 'hpsm_search_api_domain_filter_get_domain_access_info',
  );

}



/**
 * Getter callback; returns the main domain that the node belongs to.
 */

function hpsm_search_api_domain_filter_get_domain_access_info ($data, $options, $name) {

  return (!empty($data->domains) ? $data->domains : NULL);

}



/**
 * Implements hook_search_api_query_alter().
 *
 * Adds a filter for the current domain.
 */

function hpsm_search_api_domain_filter_search_api_query_alter(SearchApiQueryInterface $query) {

  $domain = domain_get_domain();
  $query->condition('hpsm_domain_access', $domain['domain_id']);

}

NOTE: this DOESN'T deal with the -1 issue since that's not something we're using on this site. However if you wanted to account for it, I'd suggest filtering for the -1 in the search_api_query_alter since returning all domains at time of index means that content won't appear for domains created after the content is indexed. That would more closely reflect the behaviour of domain access' -1 functionality.

mikeskull’s picture

I have just done this a second ago but it wouldnt work until I added 2 extra args to my getter callback, see custom_get_domain_info() below, added $type and $info.

/**
* Implements hook_entity_property_info_alter().
*
* Adds an entity property for the domain access of the node.
*/

function custom_entity_property_info_alter(&$info) {

  $properties = &$info['node']['properties'];

  $properties['custom_domain_access'] = array(
    'label' => t('Domain Access Information'),
    'description' => t('The domains to which the node is published.'),
    'type' => 'list<integer>',
    'getter callback' => 'custom_get_domain_info',
  );

function custom_get_domain_info($data, array $options, $name, $type, $info) {
  return (!empty($data->domains) ? $data->domains : NULL);
}

/**
* Implements hook_search_api_query_alter().
*
* Adds a filter for the current domain.
*/

function custom_search_api_query_alter(SearchApiQueryInterface $query) {

  $domain = domain_get_domain();
  $query->condition('custom_domain_access', $domain['domain_id']);

}
        
}

Also this works fine with Views 3 and the "post to all affiliates" options being checked on a node.

So in terms of what else I did on top of this it was add the code above to a custom module, once done you can add the field to the index via search API's index edit page, clear the index and re index. My search view just has a filter of the search fulltext, showing the title and the extract (with highlighting turned on via the Search API server advanced settings, *hint* tick them all theres a hidden checkbox).

Job done, if anyone gets any issues post them here ill try and help out if I can.

leewillis77’s picture

Interesting. The example getters I can find all have varying numbers of parameters, it seems though that the "default" getter is entity_property_verbatim_get which accepts:

entity_property_verbatim_get($data, array $options, $name, $type, $info)

ref: http://drupalcontrib.org/api/drupal/contributions%21entity%21includes%21...

So - looks like your version is preferred.

mikeskull’s picture

there used to be:

http://drupalcontrib.org/api/drupal/contributions!entity!includes!entity...

which took the 3 you were passing in, these have calls at the bottom of entity.property.inc for backward compatibility, I assume theres been some mix up here.

example:

/**
 * Previously, hook_entity_property_info() has been provided by the removed
 * entity metadata module. To provide backward compatibility for provided
 * helpers that may be specified in hook_entity_property_info(), the following
 * (deprecated) functions are provided.
 */

/**
 * Deprecated.
 * Do not make use of this function, instead use the new one.
 */
function entity_metadata_verbatim_get($data, array $options, $name) {
  return entity_property_verbatim_get($data, $options, $name);
}
Anonymous’s picture

I also use solution #5 and it works. Just make sure to index the field.
Maybe this could be part of the Workflow tab? If that makes sense.
Cheers.

agileadam’s picture

I cleaned up a few things in #5 and it's working like a charm. Thank you!

Nuno Ricardo Da Silva’s picture

Hi,
I'm facing the same problem. Can you please tell in wich file should i put the code inb the #5 solution?

Thanks in advence.

BR.

tanc’s picture

Your custom module. Any time you see code which invokes a hook, like hook_search_api_query_alter you need to replace the word hook with your module name. So in the examples above the module is named 'custom' so all the hook functions are prefixed with custom.

leewillis77’s picture

Your best bet is to create a module for this code - it's the cleanest solution, and then re-usable on other projects if you need it.

Tim Jones Toronto’s picture

Tested code in #5 using a custom module.

1. In Search API, selected ‘Domain Access Information’ in 'select fields to index'.
2. When performing ‘Index now’, I get the following error:

An AJAX HTTP error occurred. HTTP Result Code: 200 Debugging information follows. Path: /batch?render=overlay&id=82&op=do StatusText: OK ResponseText: Fatal error: Call to undefined function custom_get_domain_info() in /usr/share/nginx/www/testsearch/sites/all/modules/entity/includes/entity.wrapper.inc on line 444

Thanks.

If we could get this working, could it be included as part of the Search API module with a check Domain Access module exists to add the extra field Domain Access Information'? This would be useful.

leewillis77’s picture

Sounds like you've changed the function names, but not updated the value of 'getter callback' in custom_entity_property_info_alter().

Tim Jones Toronto’s picture

Thanks Lee, the module is now called 'xyz' (i did 'custom' named module before, but have since double checked, and renamed it to be sure). With the new module name, I get the similar error:

'Call to undefined function xyz_get_domain_info() in /usr/share/nginx/www/TESTSITE/sites/all/modules/entity/includes/entity.wrapper.inc on line 444'

The code, from what I can see, is the same as #5? :


/**
* Implements hook_entity_property_info_alter().
*
* Adds an entity property for the domain access of the node.
*/

function xyz_entity_property_info_alter(&$info) {

  $properties = &$info['node']['properties'];

  $properties['xyz_domain_access'] = array(
    'label' => t('Domain Access Information'),
    'description' => t('The domains to which the node is published.'),
    'type' => 'list<integer>',
    'getter callback' => 'xyz_get_domain_info',
  );

function xyz_get_domain_info($data, array $options, $name, $type, $info) {
  return (!empty($data->domains) ? $data->domains : NULL);
}

/**
* Implements hook_search_api_query_alter().
*
* Adds a filter for the current domain.
*/

function xyz_search_api_query_alter(SearchApiQueryInterface $query) {

  $domain = domain_get_domain();
  $query->condition('xyz_domain_access', $domain['domain_id']);

}
        
}


leewillis77’s picture

You'll need to clear your cache - drupal will still be using the getter_callback you had defined previously, and not finding it.

Tim Jones Toronto’s picture

Yes, I had cleared the cache and restarted all server services, but still got the error as before.

Someone else had a similar error situation here: http://drupal.org/node/1324832 when using the 'getter callback' for new data types.

Their solution was to include the reference to the include in the custom module.info files[] section, but I haven’t had any joy so far.

Will keep testing and thanks for your time and help too Lee.

Tim.

leewillis77’s picture

Can you post your full module code somewhere we can take a look?

cyberschorsch’s picture

The error is simple to explain. There is a missing } after the first function.

Correct Code:

/**
* Implements hook_entity_property_info_alter().
*
* Adds an entity property for the domain access of the node.
*/

function custom_entity_property_info_alter(&$info) {

  $properties = &$info['node']['properties'];

  $properties['custom_domain_access'] = array(
    'label' => t('Domain Access Information'),
    'description' => t('The domains to which the node is published.'),
    'type' => 'list<integer>',
    'getter callback' => 'custom_get_domain_info',
  );
}

function custom_get_domain_info($data, array $options, $name, $type, $info) {
  return (!empty($data->domains) ? $data->domains : NULL);
}

/**
* Implements hook_search_api_query_alter().
*
* Adds a filter for the current domain.
*/

function custom_search_api_query_alter(SearchApiQueryInterface $query) {

  $domain = domain_get_domain();
  $query->condition('custom_domain_access', $domain['domain_id']);
      
}
Tim Jones Toronto’s picture

@Cyberschorsch Thanks!!

The missing } had slipped to the bottom of the code :)

ktrev’s picture

Hi,
I just used your code
But in the views I am getting the error undefined function custom_get_domain_info()
Please help

leewillis77’s picture

If you've renamed the functions to fit with your module definition, then you need to make sure you've renamed the callback:

'getter callback' => 'custom_get_domain_info',

drunken monkey’s picture

Status: Active » Fixed

I think this is fixed? Otherwise, please re-open!

Status: Fixed » Closed (fixed)

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

rp7’s picture

Issue summary: View changes

FYI

People who are still looking for this, would like to point you to https://drupal.org/sandbox/raf/2244229. Offers configurable filters by domain (on indexing time, as well as query time), as well as a domain facet. Planning to have it released as a full module as soon as it's polished & Views integration has been improved.

geek-merlin’s picture

Also crosslinking this *very* simple sandbox:
https://www.drupal.org/sandbox/svendecabooter/2010206

lesleyfernandes’s picture

@RaF7 and @axel.rutz, do you have plans to release your modules as full? Thank you.

crtlf’s picture

Is there any way to make it work for Drupal 8.x ?

nachosalvador’s picture

Hello #28

Hello,

I solved filter by current domain in Drupal 8 using this way:

  1. In search api configuration, add field_domain_access field for your index configuration.
  2. Use search_api hook to alter query and filter by current domain:
  3. /**
     * Implements hook_search_api_query_alter().
     */
    function mymodule_search_api_query_alter(\Drupal\search_api\Query\QueryInterface &$query) {
      $current_domain = \Drupal::service('domain.negotiator')->getActiveId();
      $query->addCondition('field_domain_access', $current_domain);
    }
    

I hope it can help you.

init90’s picture

Hi, guys. I try to use the solution with Elasticsearch backend, but during reindex I have got next errors:

Array ( 
[type] => mapper_parsing_exception 
[reason] => failed to parse [node:custom_domain_access] 
[caused_by] => Array ( 
  [type] => json_parse_exception 
  [reason] => Current token (START_OBJECT) not numeric, can not use numeric value accessors at [Source: org.elasticsearch.common.bytes.BytesReference$MarkSupportingStreamInputWrapper@50402025; line: 1, column: 218] ) 
) 

Maybe someone have ideas how to fix the problem?

init90’s picture

To fix my problem I have stored domain ids as text. Looks like it's not optimal solution but it works.


define('SEARCH_API_DOMAIN_FILTER_PROPERTY_NAME', 'allowed_domain_ids');

/**
* Implements hook_entity_property_info_alter().
*
* Adds an entity property for the domain access of the node.
*/
function search_api_domain_filter_entity_property_info_alter(&$info) {
  $info['node']['properties'][SEARCH_API_DOMAIN_FILTER_PROPERTY_NAME] = array(
    'label' => t('Domain Access Information'),
    'type' => 'text',
    'computed' => TRUE,
    'getter callback' => 'search_api_domain_filter_get_allowed_domain_ids',
  );
}

/**
 * Property callback: get allowed domains IDs.
 *
 * @return string
 */
function search_api_domain_filter_get_allowed_domain_ids($data, array $options, $name, $type, $info) {
  if (empty($data->domains)) {
    return '';
  }

  // If entity is allowed for all domains - 'Send to all affiliates' is checked,
  // then add 'all' to domain list and use it in further for correct filtering.
  // @see gm_search_domain_filter_search_api_query_alter().
  if (!empty($data->domain_site)) {
    $data->domains += array('all' => 'all');
  }

  // We use '|' as separator in order to get expected result in query alter.
  // @see gm_search_domain_filter_search_api_query_alter().
  return '|' . implode('|', $data->domains) . '|';
}

/**
 * Implements hook_search_api_query_alter().
 *
 * Adds a filter for the current domain.
 */
function search_api_domain_filter_search_api_query_alter(SearchApiQueryInterface $query) {
  $index = $query->getIndex();
  if (!search_api_domain_filter_ready_to_apply($index)) {
    return;
  }

  $domain_id = domain_get_domain()['domain_id'];
  $domain_property_name = search_api_domain_filter_get_property_name($index);

  $filter = new SearchApiQueryFilter('OR');
  // For fulltext fields operator '=' interpreted as 'contains'.
  $filter->condition($domain_property_name, "|{$domain_id}|", '=');
  // Don't filter entities that have checked 'Send to all affiliates' option.
  $filter->condition($domain_property_name, '|all|', '=');
  $query->filter($filter);
}

/**
 * Determine if filter ready to apply.
 *
 * The filter is ready when domain access field is selected in search
 * index fields section.
 *
 * @param \SearchApiIndex $index
 * @return bool
 *   TRUE if filter ready to apply, otherwise FALSE.
 */
function search_api_domain_filter_ready_to_apply(SearchApiIndex $index) {
  return array_key_exists(
    search_api_domain_filter_get_property_name($index),
    $index->getFields()
  );
}

/**
 * Get property name based on search index settings.
 *
 * @param \SearchApiIndex $index
 * @return string
 */
function search_api_domain_filter_get_property_name(SearchApiIndex $index) {
  if (search_api_domain_filter_is_multiple_index($index)) {
    return 'node:' . SEARCH_API_DOMAIN_FILTER_PROPERTY_NAME;
  }
  return SEARCH_API_DOMAIN_FILTER_PROPERTY_NAME;
}

/**
 * Determine if index is multiple.
 *
 * @param \SearchApiIndex $index
 * @return bool
 *   TRUE if index is multiple, otherwise FALSE.
 */
function search_api_domain_filter_is_multiple_index(SearchApiIndex $index) {
  return $index->datasource() instanceof SearchApiCombinedEntityDataSourceController;
}


flabel’s picture

@#29

If you use this solution remember to set domain access field type to full_text.