You are here

search_api_grouping.module in Search API Grouping 7.2

Same filename and directory in other branches
  1. 7 search_api_grouping.module

Module search_api_grouping.

File

search_api_grouping.module
View source
<?php

/**
 * @file
 * Module search_api_grouping.
 */

/**
 * Define the separator for the permutation key.
 */
define('SEARCH_API_GROUPING_ENTITY_FIELD_SEPERATOR', '-');

/**
 * Implements hook_search_api_item_type_info().
 */
function search_api_grouping_search_api_item_type_info() {
  $types = array();
  foreach (entity_get_info() as $entity_type => $entity_info) {
    if (!empty($entity_info['fieldable'])) {
      $types[search_api_grouping_get_type($entity_type)] = array(
        'name' => t('Denormalized @entity', array(
          '@entity' => $entity_info['label'],
        )),
        'datasource controller' => 'SearchApiDenormalizedEntityDataSourceController',
        'entity_type' => $entity_type,
      );
    }
  }
  return $types;
}

/**
 * Implements hook_search_api_solr_field_mapping_alter().
 */
function search_api_grouping_search_api_solr_field_mapping_alter(SearchApiIndex $index, array &$fields) {
  $entity_type = search_api_grouping_get_entity_from_type($index->item_type);
  $entity_type_info = entity_get_property_info($entity_type);
  if (!empty($entity_type_info)) {

    // Ge configured fields for denormalization and grouping.
    $grouping_fields = DenormalizedEntityIndexHijack::getGroupingProcessorFields($index);
    $denormalize_fields = DenormalizedEntityIndexHijack::getDenormalizeProcessorFields($index);

    // Adjust all field list types to non-list.
    foreach ($fields as $field => $map) {

      // Split field and property.
      $field_name = $field;
      $property = NULL;
      if (stristr($field, ':')) {
        list($field_name, $property) = explode(':', $field, 2);
      }

      // If denormalization based on this field is enabled we convert the field
      // from multi-value (Xm) to single value (Xs).
      if (isset($denormalize_fields[$field_name]) && strpos($map, 'm') === 1 && strpos($field, 'field_') === 0) {
        $fields[$field] = substr_replace($map, 's', 1, 1);
      }

      // If grouping is enabled based on this field we've to convert integer
      // fields to type string.
      if (isset($grouping_fields[$field_name]) && substr($map, 0, 1) != 's') {
        $fields[$field] = substr_replace($map, 's', 0, 1);
      }
    }
  }
}

/**
 * Implements hook_search_api_processor_info().
 */
function search_api_grouping_search_api_processor_info() {
  $processors['search_api_denormalized_entity_field'] = array(
    'name' => t('Denormalization'),
    'description' => t('This processor allows you to configure which multivalue fields are used for denormalization.'),
    'class' => 'SearchApiDenormalizedEntityField',
  );
  $processors['search_api_denormalized_entity_grouping'] = array(
    'name' => t('Grouping'),
    'description' => t('This processor will group the result items based on the configured fields.'),
    'class' => 'SearchApiDenormalizedEntityGrouping',
  );
  return $processors;
}

/**
 * Implements hook_entity_insert().
 *
 * Generates the necessary pseudo keys and adds the pseudo entities to to-index.
 */
function search_api_grouping_entity_insert($entity, $type) {

  // When inserting a new search index, the new index was already inserted into
  // the tracking table. This would lead to a duplicate-key issue, if we would
  // continue.
  // We also only react on entity operations for types with property
  // information, as we don't provide search integration for the others.
  if ($type == 'search_api_index' || !entity_get_property_info($type)) {
    return;
  }
  list($id) = entity_extract_ids($type, $entity);
  if (isset($id)) {

    // SearchApiDenormalizedEntityDataSourceController::trackItemChange()
    // accesses this static cache to ensure it uses the latest possible version
    // of the entity.
    $search_api_grouping_cache =& drupal_static('search_api_grouping_entity_op', array());
    $search_api_grouping_cache[$id] = clone $entity;
    search_api_track_item_insert(search_api_grouping_get_type($type), array(
      $id,
    ));

    // Clear cache.
    $search_api_grouping_cache = array();
  }

  // If index immediately is enabled we've to convert the ids.
  _search_api_grouping_index_immediatley_hijack($type, array(
    $id,
  ));
}

/**
 * Implements hook_entity_update().
 *
 * Marks the item as changed for all indexes on entities of the specified type.
 */
function search_api_grouping_entity_update($entity, $type) {

  // We only react on entity operations for types with property information, as
  // we don't provide search integration for the others.
  if (!entity_get_property_info($type)) {
    return;
  }
  list($id) = entity_extract_ids($type, $entity);
  if (isset($id)) {

    // SearchApiDenormalizedEntityDataSourceController::trackItemChange()
    // accesses this static cache to ensure it uses the latest possible version
    // of the entity.
    $search_api_grouping_cache =& drupal_static('search_api_grouping_entity_op', array());
    $search_api_grouping_cache[$id] = clone $entity;
    search_api_track_item_change(search_api_grouping_get_type($type), array(
      $id,
    ));

    // Clear cache.
    $search_api_grouping_cache = array();
  }

  // If index immediately is enabled we've to convert the ids.
  _search_api_grouping_index_immediatley_hijack($type, array(
    $id,
  ));
}

/**
 * Ensure the ids are adjusted if index immediately is enabled.
 */
function _search_api_grouping_index_immediatley_hijack($type, $ids) {
  if ($queue =& search_api_index_specific_items_delayed()) {
    $indexes = search_api_index_load_multiple(FALSE, array(
      'enabled' => 1,
      'item_type' => search_api_grouping_get_type($type),
    ));
    foreach ($indexes as $index) {
      if (!empty($queue[$index->machine_name])) {
        $item_ids = db_select('search_api_denormalized_entity')
          ->fields('search_api_denormalized_entity', array(
          'item_id',
        ))
          ->condition('etid', $ids)
          ->condition('index_id', $index->id)
          ->condition('entity_type', $type)
          ->execute()
          ->fetchAll(PDO::FETCH_COLUMN, 0);
        $queue[$index->machine_name] = drupal_map_assoc($item_ids);
      }
    }
  }
}

/**
 * Implements hook_entity_delete().
 *
 * Removes the item from the tracking table and deletes it from all indexes.
 */
function search_api_grouping_entity_delete($entity, $entity_type) {

  // We only react on entity operations for types with property information, as
  // we don't provide search integration for the others.
  if (!entity_get_property_info($entity_type)) {
    return;
  }
  $index_type = search_api_grouping_get_type($entity_type);
  $ids = search_api_grouping_get_ids($entity_type, $entity);
  if (!empty($ids)) {
    search_api_track_item_delete($index_type, array_keys($ids));
  }
}

/**
 * Return a set of existing pseudo keys keyed by our custom serial id.
 *
 * @param string $entity_type
 *   The entity type.
 * @param object $entity
 *   Optional if provided we return the keys for just this entity, otherwise all
 *   entities matching the given $entity_type
 *
 * @return array
 *   Array of pseudo_keys keyed by our custom serial id.
 */
function search_api_grouping_get_ids($entity_type, $entity = NULL) {
  $query = db_select('search_api_denormalized_entity', 's')
    ->fields('s', array(
    'id',
    'item_id',
  ))
    ->condition('entity_type', $entity_type);
  if (!empty($entity)) {
    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
    $query
      ->condition('etid', $id);
  }
  $result = $query
    ->execute()
    ->fetchAllKeyed();
  return $result;
}

/**
 * Generate our custom pseudo keys based on the entity data.
 *
 * @param object $entity
 *   The entity object to denormalize to generate.
 * @param string $entity_type
 *   The entity entity type of the entity.
 * @param array $fields
 *   The fieldnames of the fields to use for denormalizing.
 *
 * @see search_api_grouping_generate_keys()
 *
 * @return array
 *   Keys generated from this entity.
 */
function search_api_grouping_generate_pseudo_keys($entity, $entity_type, $fields) {
  $keystrings = array();
  $keys = array();

  // Now permute on each multi-valued key.
  if (!empty($fields)) {
    search_api_grouping_generate_keys($entity_type, $entity, $fields, $keys);
    list($id, $vid, $bundle) = entity_extract_ids($entity_type, $entity);
    if (!empty($keys)) {
      foreach ($keys as $key) {

        // Pre-pend our entity type and entity id and build the id string.
        array_unshift($key, $id);
        array_unshift($key, $entity_type);
        $keystrings[] = implode(SEARCH_API_GROUPING_ENTITY_FIELD_SEPERATOR, $key);
      }
    }
  }
  return $keystrings;
}

/**
 * Recursive callback to generate the elements of our pseudo keys.
 *
 * @param string $entity_type
 *   The entity type.
 * @param object $entity
 *   The entity object.
 * @param array $fields
 *   Associative array of field names to use when computing the cartesian
 *   product.
 *   The array key is the field name, the value the permutation limit.
 *   To disable limit use 0.
 * @param array $keys
 *   Recursive data holder stores array of field indexes to use in generating
 *   pseudo document.
 *
 * @see search_api_grouping_generate_pseudo_keys()
 */
function search_api_grouping_generate_keys($entity_type, $entity, $fields, &$keys) {
  if (!empty($fields)) {
    $field_name = key($fields);
    $permutation_limit = array_shift($fields);
    if (!empty($field_name)) {
      $values = field_get_items($entity_type, $entity, $field_name);

      // Limit values if the permutations are limited.
      if (is_array($values) && $permutation_limit) {
        $values = array_slice($values, 0, $permutation_limit);
      }
      if (!empty($keys)) {
        $newkeys = array();

        // Develop the cross-product of existing keys and our new values.
        if (!empty($values)) {
          foreach ($values as $id => $value) {
            foreach ($keys as $key) {
              $key[] = $id;
              $newkeys[] = $key;
            }
          }
        }
        else {

          // This field doesn't have any values, but we need a placeholder.
          $key[] = 0;
          $newkeys[] = $key;
        }
        $keys = $newkeys;
      }
      else {

        // Fresh key stack, add all of our values.
        if (!empty($values)) {
          foreach ($values as $id => $value) {
            $keys[] = array(
              $id,
            );
          }
        }
        else {

          // This field doesn't have any values, but we need a placeholder.
          $keys[] = array(
            0,
          );
        }
      }
    }
    search_api_grouping_generate_keys($entity_type, $entity, $fields, $keys);
  }
}

/**
 * Returns an index type for the given entity type.
 *
 * @param string $entity_type
 *   An entity type.
 *
 * @return string
 *   A search index type.
 */
function search_api_grouping_get_type($entity_type) {
  return 'denormalized-' . $entity_type;
}

/**
 * Returns an index type for the given entity type.
 *
 * @param string $type
 *   An entity type.
 *
 * @return string
 *   A search index type.
 */
function search_api_grouping_get_entity_from_type($type) {
  return str_replace('denormalized-', '', $type);
}

/**
 * Implements hook_cron().
 */
function search_api_grouping_cron() {

  // Iterate over all indexes with grouping enabled and queue the permutation
  // generation.
  $indexes = search_api_index_load_multiple(FALSE);
  foreach ($indexes as $index) {
    if ($index
      ->datasource() instanceof SearchApiDenormalizedEntityDataSourceController) {

      // Since this is datasource specific we simply use the first index to
      // handle the overall maintenance.
      // Ensure the data in table are consistent.
      $index
        ->datasource()
        ->cleanTable();

      // Ensure all the required permutations are available.
      $index
        ->datasource()
        ->queuePermutationGeneration();
      break;
    }
  }
}

/**
 * Implements hook_cron_queue_info().
 */
function search_api_grouping_cron_queue_info() {
  $queues['search_api_grouping_generate_permuatations'] = array(
    'worker callback' => 'search_api_grouping_generate_permuatations',
    'time' => 60,
  );
  return $queues;
}

/**
 * Execution callback for the cron queue item.
 */
function search_api_grouping_generate_permuatations($queue_item) {

  // Bundle the items by entity type. That way we can send bundles of items to
  // method and reduce the amount of db writes.
  $entity_type_items = array();
  foreach ($queue_item as $item) {
    $entity_type_items[$item->entity_type][$item->etid] = $item;
  }
  $indexes = search_api_index_load_multiple(FALSE);

  // Now iterate over all entity type bundles.
  foreach ($entity_type_items as $entity_type => $items) {

    // Prepare the entities to process.
    $entities = entity_load($entity_type, array_keys($items));

    // Iterate over all indexes and generate the item permutations for
    // denormalized indexes.
    foreach ($indexes as $index) {
      if ($index
        ->datasource() instanceof SearchApiDenormalizedEntityDataSourceController) {
        $index
          ->datasource()
          ->createPermutationItems($index, $entity_type, $entities);
      }
    }
  }
}

/**
 * Implements hook_views_api().
 */
function search_api_grouping_views_api() {
  return array(
    'api' => 3,
  );
}

Functions

Namesort descending Description
search_api_grouping_cron Implements hook_cron().
search_api_grouping_cron_queue_info Implements hook_cron_queue_info().
search_api_grouping_entity_delete Implements hook_entity_delete().
search_api_grouping_entity_insert Implements hook_entity_insert().
search_api_grouping_entity_update Implements hook_entity_update().
search_api_grouping_generate_keys Recursive callback to generate the elements of our pseudo keys.
search_api_grouping_generate_permuatations Execution callback for the cron queue item.
search_api_grouping_generate_pseudo_keys Generate our custom pseudo keys based on the entity data.
search_api_grouping_get_entity_from_type Returns an index type for the given entity type.
search_api_grouping_get_ids Return a set of existing pseudo keys keyed by our custom serial id.
search_api_grouping_get_type Returns an index type for the given entity type.
search_api_grouping_search_api_item_type_info Implements hook_search_api_item_type_info().
search_api_grouping_search_api_processor_info Implements hook_search_api_processor_info().
search_api_grouping_search_api_solr_field_mapping_alter Implements hook_search_api_solr_field_mapping_alter().
search_api_grouping_views_api Implements hook_views_api().
_search_api_grouping_index_immediatley_hijack Ensure the ids are adjusted if index immediately is enabled.

Constants

Namesort descending Description
SEARCH_API_GROUPING_ENTITY_FIELD_SEPERATOR Define the separator for the permutation key.