123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381 |
- <?php
- /**
- * @file
- * Integrates the Search API with the Facet API.
- */
- /**
- * Implements hook_menu().
- */
- function search_api_facetapi_menu() {
- // We need to handle our own menu paths for facets because we need a facet
- // configuration page per index.
- $first = TRUE;
- foreach (facetapi_get_realm_info() as $realm_name => $realm) {
- if ($first) {
- $first = FALSE;
- $items['admin/config/search/search_api/index/%search_api_index/facets'] = array(
- 'title' => 'Facets',
- 'page callback' => 'search_api_facetapi_settings',
- 'page arguments' => array($realm_name, 5),
- 'weight' => -1,
- 'access arguments' => array('administer search_api'),
- 'type' => MENU_LOCAL_TASK,
- 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
- );
- $items['admin/config/search/search_api/index/%search_api_index/facets/' . $realm_name] = array(
- 'title' => $realm['label'],
- 'type' => MENU_DEFAULT_LOCAL_TASK,
- 'weight' => $realm['weight'],
- );
- }
- else {
- $items['admin/config/search/search_api/index/%search_api_index/facets/' . $realm_name] = array(
- 'title' => $realm['label'],
- 'page callback' => 'search_api_facetapi_settings',
- 'page arguments' => array($realm_name, 5),
- 'access arguments' => array('administer search_api'),
- 'type' => MENU_LOCAL_TASK,
- 'context' => MENU_CONTEXT_PAGE | MENU_CONTEXT_INLINE,
- 'weight' => $realm['weight'],
- );
- }
- }
- return $items;
- }
- /**
- * Implements hook_facetapi_searcher_info().
- */
- function search_api_facetapi_facetapi_searcher_info() {
- $info = array();
- $indexes = search_api_index_load_multiple(FALSE);
- foreach ($indexes as $index) {
- if ($index->enabled && $index->server()->supportsFeature('search_api_facets')) {
- $searcher_name = 'search_api@' . $index->machine_name;
- $info[$searcher_name] = array(
- 'label' => t('Search service: @name', array('@name' => $index->name)),
- 'adapter' => 'search_api',
- 'instance' => $index->machine_name,
- 'types' => array($index->item_type),
- 'path' => '',
- 'supports facet missing' => TRUE,
- 'supports facet mincount' => TRUE,
- 'include default facets' => FALSE,
- );
- }
- }
- return $info;
- }
- /**
- * Implements hook_facetapi_facet_info().
- */
- function search_api_facetapi_facetapi_facet_info(array $searcher_info) {
- $facet_info = array();
- if ('search_api' == $searcher_info['adapter']) {
- $index = search_api_index_load($searcher_info['instance']);
- if (!empty($index->options['fields'])) {
- $wrapper = $index->entityWrapper();
- $bundle_key = NULL;
- if (($entity_info = entity_get_info($index->item_type)) && !empty($entity_info['bundle keys']['bundle'])) {
- $bundle_key = $entity_info['bundle keys']['bundle'];
- }
- // Some type-specific settings. Allowing to set some additional callbacks
- // (and other settings) in the map options allows for easier overriding by
- // other modules.
- $type_settings = array(
- 'taxonomy_term' => array(
- 'hierarchy callback' => 'facetapi_get_taxonomy_hierarchy',
- ),
- 'date' => array(
- 'query type' => 'date',
- 'map options' => array(
- 'map callback' => 'facetapi_map_date',
- ),
- ),
- );
- // Iterate through the indexed fields to set the facetapi settings for
- // each one.
- foreach ($index->getFields() as $key => $field) {
- $field['key'] = $key;
- // Determine which, if any, of the field type-specific options will be
- // used for this field.
- $type = isset($field['entity_type']) ? $field['entity_type'] : $field['type'];
- $type_settings += array($type => array());
- $facet_info[$key] = $type_settings[$type] + array(
- 'label' => $field['name'],
- 'description' => t('Filter by @type.', array('@type' => $field['name'])),
- 'allowed operators' => array(
- FACETAPI_OPERATOR_AND => TRUE,
- FACETAPI_OPERATOR_OR => $index->server()->supportsFeature('search_api_facets_operator_or'),
- ),
- 'dependency plugins' => array('role'),
- 'facet missing allowed' => TRUE,
- 'facet mincount allowed' => TRUE,
- 'map callback' => 'search_api_facetapi_facet_map_callback',
- 'map options' => array(),
- 'field type' => $type,
- );
- if ($type === 'date') {
- $facet_info[$key]['description'] .= ' ' . t('(Caution: This may perform very poorly for large result sets.)');
- }
- $facet_info[$key]['map options'] += array(
- 'field' => $field,
- 'index id' => $index->machine_name,
- 'value callback' => '_search_api_facetapi_facet_create_label',
- );
- // Find out whether this property is a Field API field.
- if (strpos($key, ':') === FALSE) {
- if (isset($wrapper->$key)) {
- $property_info = $wrapper->$key->info();
- if (!empty($property_info['field'])) {
- $facet_info[$key]['field api name'] = $key;
- }
- }
- }
- // Add bundle information, if applicable.
- if ($bundle_key) {
- if ($key === $bundle_key) {
- // Set entity type this field contains bundle information for.
- $facet_info[$key]['field api bundles'][] = $index->item_type;
- }
- else {
- // Add "bundle" as possible dependency plugin.
- $facet_info[$key]['dependency plugins'][] = 'bundle';
- }
- }
- }
- }
- }
- return $facet_info;
- }
- /**
- * Implements hook_facetapi_adapters().
- */
- function search_api_facetapi_facetapi_adapters() {
- return array(
- 'search_api' => array(
- 'handler' => array(
- 'class' => 'SearchApiFacetapiAdapter',
- ),
- ),
- );
- }
- /**
- * Implements hook_facetapi_query_types().
- */
- function search_api_facetapi_facetapi_query_types() {
- return array(
- 'search_api_term' => array(
- 'handler' => array(
- 'class' => 'SearchApiFacetapiTerm',
- 'adapter' => 'search_api',
- ),
- ),
- 'search_api_date' => array(
- 'handler' => array(
- 'class' => 'SearchApiFacetapiDate',
- 'adapter' => 'search_api',
- ),
- ),
- );
- }
- /**
- * Implements hook_search_api_query_alter().
- *
- * Adds Facet API support to the query.
- */
- function search_api_facetapi_search_api_query_alter($query) {
- $index = $query->getIndex();
- if ($index->server()->supportsFeature('search_api_facets')) {
- // This is the main point of communication between the facet system and the
- // search back-end - it makes the query respond to active facets.
- $searcher = 'search_api@' . $index->machine_name;
- $adapter = facetapi_adapter_load($searcher);
- if ($adapter) {
- $adapter->addActiveFilters($query);
- }
- }
- }
- /**
- * Menu callback for the facet settings page.
- */
- function search_api_facetapi_settings($realm_name, SearchApiIndex $index) {
- if (!$index->enabled) {
- return array('#markup' => t('Since this index is at the moment disabled, no facets can be activated.'));
- }
- if (!$index->server()->supportsFeature('search_api_facets')) {
- return array('#markup' => t('This index uses a server that does not support facet functionality.'));
- }
- $searcher_name = 'search_api@' . $index->machine_name;
- module_load_include('inc', 'facetapi', 'facetapi.admin');
- return drupal_get_form('facetapi_realm_settings_form', $searcher_name, $realm_name);
- }
- /**
- * Map callback for all search_api facet fields.
- *
- * @param array $values
- * The values to map.
- * @param array $options
- * An associative array containing:
- * - field: Field information, as stored in the index, but with an additional
- * "key" property set to the field's internal name.
- * - index id: The machine name of the index for this facet.
- * - map callback: (optional) A callback that will be called at the beginning,
- * which allows initial mapping of filters. Only values not mapped by that
- * callback will be processed by this method.
- * - value callback: A callback used to map single values and the limits of
- * ranges. The signature is the same as for this function, but all values
- * will be single values.
- * - missing label: (optional) The label used for the "missing" facet.
- *
- * @return array
- * An array mapping raw filter values to their labels.
- */
- function search_api_facetapi_facet_map_callback(array $values, array $options = array()) {
- $map = array();
- // See if we have an additional map callback.
- if (isset($options['map callback']) && is_callable($options['map callback'])) {
- $map = call_user_func($options['map callback'], $values, $options);
- }
- // Then look at all unmapped values and save information for them.
- $mappable_values = array();
- $ranges = array();
- foreach ($values as $value) {
- $value = (string) $value;
- if (isset($map[$value])) {
- continue;
- }
- if ($value == '!') {
- // The "missing" filter is usually always the same, but we allow an easy
- // override via the "missing label" map option.
- $map['!'] = isset($options['missing label']) ? $options['missing label'] : '(' . t('none') . ')';
- continue;
- }
- $length = strlen($value);
- if ($length > 5 && $value[0] == '[' && $value[$length - 1] == ']' && ($pos = strpos($value, ' TO '))) {
- // This is a range filter.
- $lower = trim(substr($value, 1, $pos));
- $upper = trim(substr($value, $pos + 4, -1));
- if ($lower != '*') {
- $mappable_values[$lower] = TRUE;
- }
- if ($upper != '*') {
- $mappable_values[$upper] = TRUE;
- }
- $ranges[$value] = array(
- 'lower' => $lower,
- 'upper' => $upper,
- );
- }
- else {
- // A normal, single-value filter.
- $mappable_values[$value] = TRUE;
- }
- }
- if ($mappable_values) {
- $map += call_user_func($options['value callback'], array_keys($mappable_values), $options);
- }
- foreach ($ranges as $value => $range) {
- $lower = isset($map[$range['lower']]) ? $map[$range['lower']] : $range['lower'];
- $upper = isset($map[$range['upper']]) ? $map[$range['upper']] : $range['upper'];
- if ($lower == '*' && $upper == '*') {
- $map[$value] = t('any');
- }
- elseif ($lower == '*') {
- $map[$value] = "< $upper";
- }
- elseif ($upper == '*') {
- $map[$value] = "> $lower";
- }
- else {
- $map[$value] = "$lower – $upper";
- }
- }
- return $map;
- }
- /**
- * Creates a human-readable label for single facet filter values.
- */
- function _search_api_facetapi_facet_create_label(array $values, array $options) {
- $field = $options['field'];
- // For entities, we can simply use the entity labels.
- if (isset($field['entity_type'])) {
- $type = $field['entity_type'];
- $entities = entity_load($type, $values);
- $map = array();
- foreach ($entities as $id => $entity) {
- $label = entity_label($type, $entity);
- if ($label) {
- $map[$id] = $label;
- }
- }
- return $map;
- }
- // Then, we check whether there is an options list for the field.
- $index = search_api_index_load($options['index id']);
- $wrapper = $index->entityWrapper();
- foreach (explode(':', $field['key']) as $part) {
- if (!isset($wrapper->$part)) {
- $wrapper = NULL;
- break;
- }
- $wrapper = $wrapper->$part;
- while (($info = $wrapper->info()) && search_api_is_list_type($info['type'])) {
- $wrapper = $wrapper[0];
- }
- }
- if ($wrapper && ($options = $wrapper->optionsList('view'))) {
- return $options;
- }
- // As a "last resort" we try to create a label based on the field type.
- $map = array();
- foreach ($values as $value) {
- switch ($field['type']) {
- case 'boolean':
- $map[$value] = $value ? t('true') : t('false');
- break;
- case 'date':
- $v = is_numeric($value) ? $value : strtotime($value);
- $map[$value] = format_date($v, 'short');
- break;
- case 'duration':
- $map[$value] = format_interval($value);
- break;
- }
- }
- return $map;
- }
- /**
- * Implements hook_form_FORM_ID_alter().
- */
- function search_api_facetapi_form_search_api_admin_index_fields_alter(&$form, &$form_state) {
- $form['#submit'][] = 'search_api_facetapi_search_api_admin_index_fields_submit';
- }
- /**
- * Form submission handler for search_api_admin_index_fields().
- */
- function search_api_facetapi_search_api_admin_index_fields_submit($form, &$form_state) {
- // Clears this searcher's cached facet definitions.
- $cid = 'facetapi:facet_info:search_api@' . $form_state['index']->machine_name . ':';
- cache_clear_all($cid, 'cache', TRUE);
- }
|