You are here

apachesolr_multilingual.module in Apache Solr Multilingual 6.3

Multilingual search using Apache Solr.

@author Markus Kalkbrenner (mkalkbrenner) | bio.logis GmbH

File

apachesolr_multilingual.module
View source
<?php

/**
 * @file
 * Multilingual search using Apache Solr.
 *
 * @see apachesolr.module
 *
 * @author Markus Kalkbrenner (mkalkbrenner) | bio.logis GmbH
 *   @see http://drupal.org/user/124705
 */

/**
 * Language code used when mapping language-neutral content to all languages.
 * Defined by ISO639-2 for "Multilingual".
 */
define('APACHESOLR_MULTILINGUAL_MAP_ALL', 'mul');
define('APACHESOLR_MULTILINGUAL_LANGUAGE_NONE', '');
define('APACHESOLR_MULTILINGUAL_LANGUAGE_UND', 'und');

/**
 * Implements hook_nodeapi().
 */
function apachesolr_multilingual_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  switch ($op) {
    case 'delete':

      // Translations have to be modified
      apachesolr_multilingual_node_update($node);
      break;
    case 'insert':
    case 'update':

      // Insert or update are the same
      apachesolr_multilingual_node_update($node);
      break;
  }
}

/**
 * Implements hook_node_update().
 *
 * @return void
 */
function apachesolr_multilingual_node_update($node) {

  // If a node is translated using node translation aka content translation
  // we need to mark all translations as modified to update field
  // sm_translations, sm_missing_translations and CLIR.
  if (!empty($node->tnid)) {
    foreach (apachesolr_multilingual_load_all_environments() as $environment) {
      if (!empty($environment['index_bundles']['node']) && in_array($node->type, $environment['index_bundles']['node'])) {
        $translations = translation_node_get_translations($node->tnid);
        foreach ($translations as $node_stub) {
          if ($node_stub->nid != $node->nid) {
            apachesolr_mark_entity('node', $node_stub->nid);
          }
        }
        return;
      }
    }
  }
}

/**
 * Implements hook_apachesolr_index_documents_alter().
 */
function apachesolr_multilingual_apachesolr_index_documents_alter(&$documents, $entity, $entity_type, $env_id) {
  static $reentrance = TRUE;

  // The variable $reentrance forbids recursive calls if set to FALSE.
  if (!$reentrance) {
    return;
  }
  $environment = apachesolr_multilingual_environment_load($env_id);
  $settings = $environment['conf']['apachesolr_multilingual_index_settings'];
  if ($settings['apachesolr_multilingual_index']) {
    foreach ($documents as $document) {
      if (!isset($document->ss_language)) {

        // This document could not be handled by apachesolr_multilingual.
        continue;
      }
      $additional_documents_langcodes = array();
      $reset_entity_language_none = FALSE;
      $active_languages = apachesolr_multilingual_language_list();
      $active_langcodes = array_keys($active_languages);
      if (APACHESOLR_MULTILINGUAL_LANGUAGE_UND == $document->ss_language && APACHESOLR_MULTILINGUAL_LANGUAGE_NONE == $entity->language) {

        // language-neutral
        $document->ss_language = $settings['apachesolr_multilingual_map_language_neutral'];
        if (APACHESOLR_MULTILINGUAL_MAP_ALL == $document->ss_language) {

          // The language-neutral entity should be mapped to all languages.
          // Therefor we temporarily "transform" the entity in a language
          // specific one that has translations.
          $document->ss_language = $entity->language = $active_langcodes[0];
          array_shift($active_languages);
          $additional_documents_langcodes = array_combine(array_keys($active_languages), array_keys($active_languages));
          $reset_entity_language_none = TRUE;
        }
      }
      if (APACHESOLR_MULTILINGUAL_LANGUAGE_UND == $document->ss_language) {

        // Do not create special fields for language neutral ("und").
        continue;
      }
      switch ($entity_type) {
        case 'node':

          // Node Translation, aka Content Translation.
          $translations = array();
          if (!empty($document->is_tnid)) {
            $translations = translation_node_get_translations($document->is_tnid);
          }
          $document
            ->setField('sm_translations', array_unique(array_merge(array_keys($translations), array(
            $document->ss_language,
          ))));
          $document
            ->setField('sm_missing_translations', array_diff($active_langcodes, array_merge(array_keys($translations), array(
            $document->ss_language,
          ))));
          if (!empty($translations) && $settings['apachesolr_multilingual_clir']['apachesolr_multilingual_index_translations']) {

            // CLIR for Node Translation, aka Content Translation.
            // TODO distinguish between published and unpublished translations
            foreach ($translations as $node_stub) {
              if ($node_stub->nid != $document->entity_id) {
                if ($translation_node = node_load($node_stub->nid)) {
                  $reentrance = FALSE;

                  // apachesolr_convert_entity_to_documents() calls hook_apachesolr_index_documents_alter().
                  // The variable $reentrance avoids an endless recursion.
                  $tmp_documents = apachesolr_convert_entity_to_documents($translation_node, 'node', $env_id);
                  $reentrance = TRUE;

                  // Handle multiple documents is a future use case of apachesolr we don't care about now.
                  $translation_document = reset($tmp_documents);
                  apachesolr_multilingual_fire_callbacks($translation_document, $translation_node, 'node', 'document callback', $env_id);
                  apachesolr_multilingual_copy_common_to_i18n_fields($translation_document, $document);
                }
              }
            }
          }

          // In case of content translation this code is used when language
          // undefined content needs to be mapped to different languages.
          // The document id gets a language suffix to be unique.
          foreach ($additional_documents_langcodes as $langcode) {

            // Prevent endless recursion if language specific document has been created already
            // and $reentrance is set to TRUE.
            if (!array_key_exists($document->id, $documents)) {

              // Temporarily switch the language context for apachesolr_index_node_solr_document(),
              // which is not aware of entity_translation.
              $original_langcode = $entity->language;
              $entity->language = $langcode;
              $id = $document->id . '/' . $langcode;
              $additional_documents = array();
              $additional_documents[$id] = clone $document;
              $reentrance = FALSE;

              // apachesolr_convert_entity_to_documents() calls hook_apachesolr_index_documents_alter().
              // The variable $reentrance avoids an endless recursion.
              $tmp_documents = apachesolr_convert_entity_to_documents($entity, $entity_type, $env_id);
              $reentrance = TRUE;

              // Handle multiple documents is a future use case of apachesolr we don't care about now.
              $basic_fields = reset($tmp_documents);
              foreach ($basic_fields
                ->getFieldNames() as $field) {
                $additional_documents[$id]->{$field} = $basic_fields->{$field};
              }
              $additional_documents[$id]->id = $id;

              // Add document_id of original language, so that all translations can be deleted together,
              // when original document is removed. See apachesolr_index_delete_entity_from_index().
              $additional_documents[$id]->sm_parent_document_id = array(
                $document->id,
              );
              apachesolr_multilingual_fire_callbacks($additional_documents[$id], $entity, $entity_type, 'document callback', $env_id);

              // Recursive call to deal with 'apachesolr_multilingual_index_translations' aka CLIR and title field, see above and below
              apachesolr_multilingual_apachesolr_index_documents_alter($additional_documents, $entity, $entity_type, $env_id);
              $entity->language = $original_langcode;
              foreach ($additional_documents as $key => $additional_document) {
                $documents[$key] = $additional_document;
              }
            }
          }
          break;
        default:
          watchdog('Apache Solr', t('Apache Solr Multilingual does not fully support indexing entities of type %type.', array(
            '%type' => $entity_type,
          )), NULL, WATCHDOG_NOTICE);
      }

      // Same for Entity Translation and Node Translation, aka Content Translation.
      apachesolr_multilingual_copy_common_to_i18n_fields($document, $document);

      // Hook to allow modifications of the language specific index document.
      foreach (module_implements('apachesolr_multilingual_index_document_alter') as $module) {
        $function = $module . '_apachesolr_multilingual_index_document_alter';
        $function($document, $document->ss_language, $entity, $entity_type, $env_id);
      }
      if ($reset_entity_language_none) {
        $entity->language = APACHESOLR_MULTILINGUAL_LANGUAGE_NONE;
      }
    }
  }
}
function apachesolr_multilingual_copy_common_to_i18n_fields($src_document, $dst_document) {
  $fields = $src_document
    ->getFieldNames();
  $additionally_store_unstemmed = array(
    'ts' => 'tus',
    'tm' => 'tom',
  );

  // Use language-specific stemming and so on by copying all fields of type text
  // to a language-specific text field.
  foreach (array(
    'label',
    'teaser',
    'content',
    'path_alias',
  ) as $field_name) {
    if (in_array($field_name, $fields)) {
      $dst_document->{'i18n_' . $field_name . '_' . $src_document->ss_language} = $dst_document->{'i18n_tus_' . $src_document->ss_language . '_' . $field_name} = $src_document->{$field_name};
    }
  }
  $dst_document->{'i18n_path_' . $src_document->ss_language} = $src_document->{'path'};
  foreach ($fields as $field_name) {
    $prefixes = array_keys(apachesolr_multilingual_get_dynamic_text_field_prefixes_and_types());
    $prefixes[] = 'tags_';
    foreach ($prefixes as $prefix) {
      if (strpos($field_name, $prefix) === 0 && !empty($src_document->{$field_name})) {

        // search for existing language identifier at second position
        $tmp = explode('_', $field_name);
        if ($src_document->ss_language != $tmp[1]) {

          // Dynamic fields have to be prefixed with 'i18n_' to distinguish between
          // language-specific dynamic fields and standard dynamic fields.
          // The language suffix is not enough, because someone could define a
          // drupal field name like 'de_foo', which matches ts_de_* but not i18n_ts_*
          $new_field_name = 'i18n_' . $tmp[0] . '_' . $src_document->ss_language . drupal_substr($field_name, drupal_strlen($tmp[0]));
          $dst_document->{$new_field_name} = $src_document->{$field_name};
          if (array_key_exists($tmp[0], $additionally_store_unstemmed)) {
            $dst_document->{str_replace(array_keys($additionally_store_unstemmed), array_values($additionally_store_unstemmed), $new_field_name)} = $src_document->{$field_name};
          }
        }
      }
    }
  }
}
function apachesolr_multilingual_fire_callbacks($document, $entity, $entity_type, $callback_name, $env_id) {

  // See _apachesolr_index_process_entity_get_document().
  $bundle = $entity->type;

  //Get the callback array to add stuff to the document
  $document_callbacks = apachesolr_entity_get_callback($entity_type, $callback_name, $bundle);
  $tmp_documents = array();
  foreach ($document_callbacks as $document_callback) {

    // Call a type-specific callback to add stuff to the document.
    $tmp_documents = array_merge($tmp_documents, $document_callback($document, $entity, $entity_type, $env_id));

    // Generic use case for future reference. Callbacks can
    // allow you to send back multiple documents.
    // @see apachesolr_index_node_solr_document()
    // $tmp_documents is not in use yet.
  }
}

/**
 * Implements hook_form_apachesolr_search_bias_form_alter().
 *
 * @param $form
 * @param $state
 * @param $form_id
 * @return mixed
 */
function apachesolr_multilingual_form_apachesolr_search_bias_form_alter(&$form, &$form_state) {
  $environment = apachesolr_multilingual_environment_load($form['#env_id']);
  $fields = apachesolr_search_get_fields($environment);
  $unspecified_types = $types = array_unique(array_values(apachesolr_multilingual_get_dynamic_text_field_prefixes_and_types()));
  $defaults = array();
  foreach (array_keys(apachesolr_multilingual_language_list()) as $language_id) {
    foreach ($unspecified_types as $type) {
      if (strpos($type, 'text') === 0) {

        // matches text_und, too
        // Register language specific text types to add all fields based on this
        // these types to the bias form.
        $types[] = $type . '_' . $language_id;
      }
    }

    // get the current weights
    $defaults['i18n_content_' . $language_id] = '1.0';
    $defaults['i18n_ts_' . $language_id . '_comments'] = '0.5';
    $defaults['i18n_tos_' . $language_id . '_content_extra'] = '0.1';
    $defaults['i18n_label_' . $language_id] = '5.0';
    $defaults['i18n_tos_' . $language_id . '_name'] = '3.0';
    $defaults['i18n_taxonomy_names_' . $language_id] = '2.0';
    $defaults['i18n_tags_' . $language_id . '_h1'] = '5.0';
    $defaults['i18n_tags_' . $language_id . '_h2_h3'] = '3.0';
    $defaults['i18n_tags_' . $language_id . '_h4_h5_h6'] = '2.0';
    $defaults['i18n_tags_' . $language_id . '_inline'] = '1.0';
    $defaults['i18n_tags_' . $language_id . '_a'] = '0';
    $defaults['i18n_tus_' . $language_id . '_content'] = '0';
    $defaults['i18n_tus_' . $language_id . '_comments'] = '0';
    $defaults['i18n_tus_' . $language_id . '_label'] = '0';
    $defaults['i18n_tus_' . $language_id . '_teaser'] = '0';
    $defaults['i18n_tus_' . $language_id . '_path_alias'] = '0';
  }
  $qf = apachesolr_environment_variable_get($environment['env_id'], 'field_bias', $defaults);
  if (!$qf) {
    $qf = $defaults;
  }
  $incomplete = array();
  if ($fields) {
    foreach ($fields as $field_name => $field) {

      // Only indexed fields are searchable.
      if (in_array($field->type, $types) && $field->schema[0] == 'I') {
        $form['field_bias'][$field_name]['#access'] = TRUE;
        $form['field_bias'][$field_name]['#default_value'] = isset($qf[$field_name]) ? $qf[$field_name] : '0';
        $form['field_bias'][$field_name]['#description'] = in_array($field->type, $unspecified_types) ? t('Unspecified language: recommendation is set this bias to %omit.', array(
          '%omit' => t('Omit'),
        )) : '';
        if (!isset($qf[$field_name])) {
          $incomplete[] = $field_name;
        }
      }
    }

    // Make sure all the default fields are included, even if they have
    // no indexed content.
    $additional_vars_count = 0;
    foreach ($defaults as $field_name => $weight) {
      $form['field_bias'][$field_name] = array(
        '#type' => 'select',
        '#options' => $form['field_bias']['content']['#options'],
        '#title' => filter_xss(apachesolr_field_name_map($field_name)),
        '#default_value' => isset($qf[$field_name]) ? $qf[$field_name] : $defaults[$field_name],
      );
      if (!isset($qf[$field_name])) {
        $incomplete[] = $field_name;
      }
      $additional_vars_count++;
    }

    // Based on a default value of 1000 for max_input_vars this is a rough
    // estimation to warn the user about exceeding this setting.
    if ($additional_vars_count > ini_get('max_input_vars') - 400 || $additional_vars_count > ini_get('max_input_vars') * 0.6) {
      drupal_set_message(t('This settings form contains a lot of variables. If you are unable to save the settings try to increase the value of "max_input_vars" in your php.ini. If you use the suhosin extension you have to adjust the values of "suhosin.post.max_vars" and "suhosin.request.max_vars" as well.'), 'warning');
    }
    ksort($form['field_bias']);
    if (!empty($incomplete)) {
      foreach ($incomplete as $field_name) {

        // This field has never been configured before. Mark the field as
        // erroneous. This makes it easier to find the field even if we have a
        // lot of fields in this form.
        $form['field_bias'][$field_name]['#attributes']['class'] = 'error';

        // TODO use a theme function to format this message.
        $form['field_bias'][$field_name]['#description'] = (isset($form['field_bias'][$field_name]['#description']) ? $form['field_bias'][$field_name]['#description'] : '') . ' <span class="error">' . t('This field is new and needs to be configured.') . '</span>';
      }

      // The form contains at least one field that has never been configured
      // before. Display a corresponding message.
      drupal_set_message(t('This solr index contains new searchable fields. These fields are ignored until you configured and saved the !field_biases.', array(
        '!field_biases' => t('Field biases'),
      )), 'warning');
    }
  }
}

/**
 * Implements hook_apachesolr_field_name_map_alter().
 */
function apachesolr_multilingual_apachesolr_field_name_map_alter(&$map) {
  foreach (apachesolr_multilingual_language_list() as $language => $language_name) {

    // 'content' => t('The full, rendered content (e.g. the rendered node body)')
    // 'i18n_content_de' => 'German: ' . t('The full, rendered content (e.g. the rendered node body)')
    $map['i18n_content_' . $language] = $language_name . ': ' . $map['content'];

    // For language specific dynamic field i18n_ts_de_* we could not attach the
    // langcode at the end of the field name.
    $map['i18n_ts_' . $language . '_comments'] = $language_name . ': ' . $map['ts_comments'];
    $map['i18n_tos_' . $language . '_content_extra'] = $language_name . ': ' . $map['tos_content_extra'];
    $map['i18n_tos_' . $language . '_name_formatted'] = $language_name . ': ' . $map['tos_name_formatted'];
    $map['i18n_label_' . $language] = $language_name . ': ' . $map['label'];
    $map['i18n_teaser_' . $language] = $language_name . ': ' . $map['teaser'];
    $map['i18n_tos_' . $language . '_name'] = $language_name . ': ' . $map['tos_name'];
    $map['i18n_path_alias_' . $language] = $language_name . ': ' . $map['path_alias'];
    $map['i18n_tags_' . $language . '_h1'] = $language_name . ': ' . $map['tags_h1'];
    $map['i18n_tags_' . $language . '_h2_h3'] = $language_name . ': ' . $map['tags_h2_h3'];
    $map['i18n_tags_' . $language . '_h4_h5_h6'] = $language_name . ': ' . $map['tags_h4_h5_h6'];
    $map['i18n_tags_' . $language . '_inline'] = $language_name . ': ' . $map['tags_inline'];
    $map['i18n_tags_' . $language . '_a'] = $language_name . ': ' . $map['tags_a'];
    $map['i18n_tus_' . $language . '_content'] = $language_name . ': ' . $map['content'] . ' ' . t('(Unstemmed)');
    $map['i18n_tus_' . $language . '_comments'] = $language_name . ': ' . $map['ts_comments'] . ' ' . t('(Unstemmed)');
    $map['i18n_tus_' . $language . '_label'] = $language_name . ': ' . $map['label'] . ' ' . t('(Unstemmed)');
    $map['i18n_tus_' . $language . '_teaser'] = $language_name . ': ' . $map['teaser'] . ' ' . t('(Unstemmed)');
    $map['i18n_tus_' . $language . '_path_alias'] = $language_name . ': ' . $map['path_alias'] . ' ' . t('(Unstemmed)');
    if (module_exists('taxonomy')) {
      $map['i18n_taxonomy_names_' . $language] = $language_name . ': ' . $map['taxonomy_names'];
      foreach (taxonomy_get_vocabularies() as $vocab) {
        $map['i18n_tm_' . $language . '_vid_' . $vocab->vid . '_names'] = $language_name . ': ' . $map['tm_vid_' . $vocab->vid . '_names'];
      }
    }
    $prefixes = array_keys(apachesolr_multilingual_get_dynamic_text_field_prefixes_and_types());
    foreach (apachesolr_entity_fields('node') as $field_nm => $nodefields) {
      foreach ($nodefields as $field_info) {
        $name = apachesolr_index_key($field_info);
        list($prefix, ) = explode('_', $name);
        if (in_array($prefix, $prefixes)) {

          // FIXME are 32 chars the max allowed chars?
          $map['i18n_' . $prefix . '_' . $language . '_' . $field_nm] = $language_name . ': ' . $map[$name];
        }
      }
    }
  }
}
function apachesolr_multilingual_get_language_filters_by_query($query) {
  $filter_languages = array();
  $language_filters = $query
    ->getFilters('ss_language');
  if (!empty($language_filters)) {
    foreach ($language_filters as $language_filter) {
      $filter_languages[] = $language_filter['#value'];
    }
  }
  foreach ($query
    ->getFilterSubQueries() as $sub_query) {
    $filter_languages = array_merge($filter_languages, apachesolr_multilingual_get_language_filters_by_query($sub_query));
  }
  return array_unique($filter_languages);
}

/**
 * Implements hook_module_implements_alter().
 *
 * Move apachesolr_multilingual_apachesolr_query_alter() to the end of the
 * list because we have to wait if someone limits the query to a specific
 * language.
 */
function apachesolr_multilingual_module_implements_alter(&$implementations, $hook) {

  // FIXME BACKPORT hook_implements_alter() does not exist
  if (in_array($hook, array(
    'apachesolr_query_alter',
    'apachesolr_field_name_map_alter',
  ))) {
    $group = $implementations['apachesolr_multilingual'];
    unset($implementations['apachesolr_multilingual']);
    $implementations['apachesolr_multilingual'] = $group;

    // This confusing code turns
    // array('a' => '', 'apachesolr_multilingual' => '', 'b' => '')
    // into
    // array('a' => '', 'b' => '', 'apachesolr_multilingual' => '')
  }
}

/**
 * Implements hook_apachesolr_modify_query().
 */
function apachesolr_multilingual_apachesolr_query_alter($query) {
  global $language;
  $context = $query
    ->getContext();
  $environment = apachesolr_multilingual_environment_load($context['env_id']);
  $settings = $environment['conf']['apachesolr_multilingual_index_settings'];
  if ($settings['apachesolr_multilingual_index']) {
    $custom_settings = array();
    if ('apachesolr_search_mlt' == $context['search_type']) {

      // Add multilingual settings to mlt blocks like we do for search pages.
      // @see https://drupal.org/node/2056055
      $mlt_block_settings = apachesolr_search_mlt_block_load($context['block_id']);
      if ($mlt_block_settings && array_key_exists('apachesolr_multilingual_query_settings', $mlt_block_settings)) {
        $custom_settings = $mlt_block_settings['apachesolr_multilingual_query_settings'];
      }

      // Use mlt block is as page id later in this function.
      $context['page_id'] = $context['block_id'];
    }
    elseif (!empty($context['page_id'])) {

      // Load multilingual custom settings assigned to this search page.
      $search_page = apachesolr_search_page_load($context['page_id']);
      $custom_settings = $search_page['settings'];
    }
    else {

      // A custom query has been created by a third party module.
      // Assume default multilingual settings.
      // TODO This behavior should be somehow configurable.
    }

    // Add defaults for required settings to not yet saved multilingual custom settings.
    apachesolr_multilingual_search_page_settings_add_defaults($custom_settings);
    $filter_languages =& variable_static('apachesolr_multilingual_filter_languages_' . $context['page_id']);
    $filter_languages = apachesolr_multilingual_get_language_filters_by_query($query);
    $languages = apachesolr_multilingual_language_list();
    if (empty($filter_languages) && $custom_settings['apachesolr_multilingual_auto_language_filter'] && (!$custom_settings['apachesolr_multilingual_auto_language_filter_detachable'] || $custom_settings['apachesolr_multilingual_auto_language_filter_detachable'] && empty($_GET['detach-auto-language-filter']))) {
      if (!empty($language->language)) {
        $filter_languages[] = $language->language;
        if ($custom_settings['apachesolr_multilingual_show_language_undefined_results']) {
          $subquery = apachesolr_drupal_query('Multilingual Language Limit');
          $subquery
            ->addFilter('ss_language', $language->language);
          $subquery
            ->addFilter('ss_language', APACHESOLR_MULTILINGUAL_LANGUAGE_UND);
          $subquery->operator = 'OR';

          // that's the default, but this way it's readable
          $query
            ->addFilterSubQuery($subquery);
        }
        else {
          $query
            ->addFilter('ss_language', $language->language);
        }
      }
    }

    // When CLIR is activated we want to search all language specific fields
    if (!$settings['apachesolr_multilingual_clir']['apachesolr_multilingual_index_translations']) {

      // Per default the 'query fields' contain all language un-specific and
      // specific fields.
      // If the query filters by language we have to remove all unnecessary
      // language specific fields.
      // For the remaining language specific fields we have to apply the boosts as
      // apachesolr_search_add_boost_params() will do for the unspecific ones.
      $qf = $query
        ->getParam('qf');
      foreach ($qf as $index => $field) {
        if (strpos($field, 'i18n_') === 0) {
          if (!empty($filter_languages)) {
            $remove = TRUE;
            foreach ($filter_languages as $filter_language) {
              if (preg_match('@_' . $filter_language . '(\\^|_)@', $field)) {
                $remove = FALSE;
                break;
              }
            }
            if ($remove) {
              unset($qf[$index]);
              continue;
            }
          }

          // Because apachesolr_search_add_boost_params() does not recognize the
          // the i18n_* prefix we have to apply the same logic here to add the
          // extra boost to language specific normed fields.
          if (strpos($field, 'i18n_content') === 0 || strpos($field, 'i18n_ts_') === 0 || strpos($field, 'i18n_tm_') === 0) {
            list($field_name, $boost) = explode('^', $field);

            // Normed fields tend to have a lower score. Multiplying by 40 is
            // a rough attempt to bring the score in line with fields that are
            // not normed.
            $qf[$index] = $field_name . '^' . sprintf('%.1F', 40.0 * $boost);
          }
        }
      }
      $query
        ->replaceParam('qf', $qf);
    }

    // Add the teaser to the result fields, because apachesolr_search_run()
    // won't do it anymore if highlighting parameters are set here.
    $query
      ->addParam('fl', 'teaser');
    $hl_fl_removed = FALSE;
    foreach (array_keys($languages) as $language_id) {
      if (!empty($filter_languages) && !in_array($language_id, $filter_languages)) {
        continue;
      }
      if (!$hl_fl_removed) {

        // Remove all language un-specific fields from highlighting before
        // adding language specific ones.
        $query
          ->removeParam('hl.fl');
        $hl_fl_removed = TRUE;
      }

      // TODO avoid snippets in case of CLIR because these might come from machine translation which should be hidden.
      $query
        ->addParam('hl.fl', 'i18n_content_' . $language_id);
      $query
        ->addParam('f.i18n_content_' . $language_id . '.hl.alternateField', 'i18n_teaser_' . $language_id);
      $query
        ->addParam('hl.fl', 'i18n_ts_' . $language_id . '_comments');
    }
  }
}

/**
 * Implements hook_form_search_form_alter().
 *
 * @param $form
 * @param $form_state
 */
function apachesolr_multilingual_form_apachesolr_search_custom_page_search_form_alter(&$form, $form_state) {
  apachesolr_multilingual_search_page_settings_add_defaults($form['#search_page']['settings']);
  if ($form['#search_page']['settings']['apachesolr_multilingual_auto_language_filter']) {
    $form['basic']['detach_auto_language_filter'] = array(
      '#type' => $form['#search_page']['settings']['apachesolr_multilingual_auto_language_filter_detachable'] ? 'checkbox' : 'hidden',
      '#title' => t('Search all languages'),
      '#default_value' => (int) isset($_GET['detach-auto-language-filter']),
    );
    if (!isset($form['#submit']) || !is_array($form['#submit'])) {
      $form['#submit'] = array();
    }
    array_unshift($form['#submit'], 'apachesolr_multilingual_apachesolr_search_custom_page_search_form_submit');
  }
}

/**
 * @see apachesolr_multilingual_form_search_form_alter()
 */
function apachesolr_multilingual_apachesolr_search_custom_page_search_form_submit($form, &$form_state) {
  $fv = $form_state['values'];
  if (!empty($fv['get'])) {
    $get = json_decode($fv['get'], TRUE);
    if (!empty($fv['detach_auto_language_filter'])) {
      $get['detach-auto-language-filter'] = '1';
    }
    elseif (isset($get['detach-auto-language-filter'])) {
      unset($get['detach-auto-language-filter']);
    }
    $form_state['values']['get'] = json_encode($get);
  }
}

/**
 * Add multilingual settings to MLT blocks.
 *
 * @param $form
 * @param $form_state
 * @param $form_id
 */
function apachesolr_multilingual_form_block_admin_configure_alter(&$form, &$form_state, $form_id) {
  if ('apachesolr_search' == $form['module']['#value'] && 'sort' != $form['delta']['#value']) {
    $settings = array();
    $mlt_block_settings = apachesolr_search_mlt_block_load($form['delta']['#value']);
    if ($mlt_block_settings && array_key_exists('apachesolr_multilingual_query_settings', $mlt_block_settings)) {
      $settings = $mlt_block_settings['apachesolr_multilingual_query_settings'];
    }
    apachesolr_multilingual_search_page_settings_add_defaults($settings);
    unset($settings['apachesolr_multilingual_auto_language_filter_detachable']);
    apachesolr_multilingual_add_settings_form_elements($form, $settings);
    array_push($form['#validate'], 'apachesolr_multilingual_block_admin_configure_form_validate');

    // We have to modify apachesolr_search_mlt_blocks AFTER apachesolr_search.module!
    array_push($form['#submit'], 'apachesolr_multilingual_block_admin_configure_form_submit');
  }
}
function apachesolr_multilingual_block_admin_configure_form_validate($form, &$form_state) {
}
function apachesolr_multilingual_block_admin_configure_form_submit($form, &$form_state) {
  $blocks = variable_get('apachesolr_search_mlt_blocks', array());
  $blocks[$form_state['values']['delta']]['apachesolr_multilingual_query_settings'] = $form_state['values']['apachesolr_multilingual_query_settings'];
  variable_set('apachesolr_search_mlt_blocks', $blocks);
}
function apachesolr_multilingual_add_settings_form_elements(&$form, $settings) {
  $form['apachesolr_multilingual_query_settings'] = array(
    '#type' => 'fieldset',
    '#tree' => TRUE,
    '#title' => t('Multilingual Query Settings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['apachesolr_multilingual_query_settings']['apachesolr_multilingual_auto_language_filter'] = array(
    '#type' => 'checkbox',
    '#title' => t('Limit search to current language by default'),
    '#default_value' => $settings['apachesolr_multilingual_auto_language_filter'],
    '#description' => t('Show only search results that match the current language.'),
  );
  if (array_key_exists('apachesolr_multilingual_auto_language_filter_detachable', $settings)) {
    $form['apachesolr_multilingual_query_settings']['apachesolr_multilingual_auto_language_filter_detachable'] = array(
      '#type' => 'checkbox',
      '#title' => t('Limit search to current language could be disabled by user'),
      '#default_value' => $settings['apachesolr_multilingual_auto_language_filter_detachable'],
      '#description' => t('Displays a checkbox on search result page to search over all languages if "%limit_search" is active.', array(
        '%limit_search' => t('Limit search to current language by default'),
      )),
    );
  }
  $form['apachesolr_multilingual_query_settings']['apachesolr_multilingual_show_language_undefined_results'] = array(
    '#type' => 'checkbox',
    '#title' => t('Show language-neutral/undefined results by default'),
    '#default_value' => $settings['apachesolr_multilingual_show_language_undefined_results'],
    '#description' => t('Show language-neutral/undefined results on search result page if "%limit_search" is active.', array(
      '%limit_search' => t('Limit search to current language by default'),
    )),
  );
}

/**
 * Implements hook_form_alter().
 *
 * @param $form_state
 */
function apachesolr_multilingual_form_apachesolr_search_page_settings_form_alter(&$form, &$form_state) {
  $environment = apachesolr_multilingual_environment_load($form['search_page']['#value']['env_id']);
  if (array_key_exists('apachesolr_multilingual_index_settings', $environment['conf']) && $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_index']) {
    $search_page_settings = $form['search_page']['#value']['settings'];
    $tmp_actions = $form['actions'];
    unset($form['actions']);

    // Add multilingual defaults.
    apachesolr_multilingual_search_page_settings_add_defaults($search_page_settings);
    apachesolr_multilingual_add_settings_form_elements($form, $search_page_settings);

    // Move actions to bottom of the form.
    $form['actions'] = $tmp_actions;
    array_unshift($form['#validate'], 'apachesolr_multilingual_search_page_settings_form_validate');

    // We have to modify $form_state['values']['advanced'] BEFORE apachesolr_search.module!
    array_unshift($form['#submit'], 'apachesolr_multilingual_search_page_settings_form_submit');
    array_unshift($form['actions']['submit']['#submit'], 'apachesolr_multilingual_search_page_settings_form_submit');
    array_unshift($form['actions']['submit_edit']['#submit'], 'apachesolr_multilingual_search_page_settings_form_submit');
  }
}
function apachesolr_multilingual_search_page_settings_form_validate($form, &$form_state) {
}

/**
 * Processes apachesolr_search_page_settings_form form submissions.
 */
function apachesolr_multilingual_search_page_settings_form_submit($form, &$form_state) {
  $form_state['values']['advanced'] += $form_state['values']['apachesolr_multilingual_query_settings'];
}
function apachesolr_multilingual_search_page_settings_add_defaults(&$search_page_settings) {
  if (!$search_page_settings) {
    $search_page_settings = array();
  }

  // Add multilingual defaults.
  $search_page_settings += array(
    'apachesolr_multilingual_auto_language_filter' => 1,
    'apachesolr_multilingual_auto_language_filter_detachable' => 0,
    'apachesolr_multilingual_show_language_undefined_results' => 0,
  );
}

/**
 * Implements hook_form_alter().
 *
 * @param $form_state
 */
function apachesolr_multilingual_form_apachesolr_environment_edit_form_alter(&$form, &$form_state) {
  $active_languages = apachesolr_multilingual_language_list();
  $environment = apachesolr_multilingual_environment_load($form['env_id']['#value']);
  $form['conf']['apachesolr_multilingual_index_settings'] = array(
    '#type' => 'fieldset',
    '#title' => t('Multilingual Index Settings'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
  );
  $form['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_index'] = array(
    '#type' => 'checkbox',
    '#title' => t('This is a multilingual index'),
    '#default_value' => $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_index'],
    '#description' => t('The configuration of this index has been created by Apache Solr Multilingual Config Generator'),
  );
  $form['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_map_language_neutral'] = array(
    '#type' => 'radios',
    '#title' => t('Map language-neutral'),
    '#options' => array_merge(array(
      APACHESOLR_MULTILINGUAL_LANGUAGE_UND => t('No mapping'),
      APACHESOLR_MULTILINGUAL_MAP_ALL => t('All languages'),
    ), $active_languages),
    '#default_value' => $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_map_language_neutral'],
    '#description' => t('Select a language to treat content marked as language-neutral. Language-neutral will not occur as filter option any more.'),
  );
  $form['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir'] = array(
    '#type' => 'fieldset',
    '#title' => t('CLIR'),
    '#collapsible' => TRUE,
    '#collapsed' => TRUE,
    '#description' => filter_xss(t('<strong>Cross-language information retrieval</strong>. As a special feature Apache Solr Multilingual could be configured to provide a simple implementation of CLIR. That means that you can find content in any language it has been translated to, no matter which language was used to enter the search phrase. The standard use case for CLIR is to have only a few good translations, but machine translations for the other languages. See !wikipedia for details.', array(
      '!wikipedia' => l(t('CLIR on Wikipedia'), 'http://en.wikipedia.org/wiki/Cross-language_information_retrieval'),
    ))),
  );
  $form['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_translations'] = array(
    '#type' => 'checkbox',
    '#title' => t('Enable CLIR'),
    '#default_value' => $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_translations'],
    '#description' => t('All translations of a node will be added to the node itself.'),
  );
  $form['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_unpublished_translations'] = array(
    '#type' => 'checkbox',
    '#title' => t('Index unpublished translations'),
    '#default_value' => $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_unpublished_translations'],
    '#description' => t('Index unpublished translations, e.g. machine translations.'),
  );
  $form['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_term_translations'] = array(
    '#type' => 'checkbox',
    '#title' => t('Index taxonomy term translations'),
    '#default_value' => $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_term_translations'],
    '#description' => t('Index translations of the taxonomy terms. Requires module Taxonomy Translation which is part of !link.', array(
      '!link' => l(t('Internationalization'), 'http://drupal.org/project/i18n'),
    )),
    '#disabled' => !module_exists('i18ntaxonomy'),
  );
  array_unshift($form['actions']['save']['#submit'], 'apachesolr_multilingual_environment_edit_form_submit');
  array_unshift($form['actions']['save_edit']['#submit'], 'apachesolr_multilingual_environment_edit_form_submit');
  array_unshift($form['actions']['test']['#submit'], 'apachesolr_multilingual_environment_edit_form_submit');
}
function apachesolr_multilingual_environment_edit_form_submit($form, &$form_state) {
  $environment = apachesolr_multilingual_environment_load($form['env_id']['#value']);
  $reindex = FALSE;
  if ($form_state['values']['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_map_language_neutral'] != $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_map_language_neutral']) {
    $reindex = TRUE;
  }
  if ($form_state['values']['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_translations'] != $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_translations']) {
    $reindex = TRUE;
  }
  if ($form_state['values']['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_unpublished_translations'] != $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_unpublished_translations']) {
    $reindex = TRUE;
  }
  if ($form_state['values']['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_term_translations'] != $environment['conf']['apachesolr_multilingual_index_settings']['apachesolr_multilingual_clir']['apachesolr_multilingual_index_term_translations']) {
    $reindex = TRUE;
  }
  if ($reindex) {
    drupal_set_message(t('The changes requires you to !link_reindex your content.', array(
      '!link_reindex' => l(t('re-index'), 'admin/settings/apachesolr/settings/' . $form['env_id']['#default_value'] . '/index'),
    )), 'warning');
  }
}
function apachesolr_multilingual_environment_load($env_id) {
  $environment = apachesolr_environment_load($env_id);
  apachesolr_multilingual_environment_add_defaults($environment);
  return $environment;
}
function apachesolr_multilingual_load_all_environments() {
  $environments = apachesolr_load_all_environments();
  array_walk($environments, 'apachesolr_multilingual_environment_add_defaults');
  return $environments;
}
function apachesolr_multilingual_environment_add_defaults(&$environment) {
  if (!$environment) {
    $environment['conf'] = array();
  }
  $environment['conf'] += array(
    'apachesolr_multilingual_index_settings' => array(
      'apachesolr_multilingual_index' => 1,
      'apachesolr_multilingual_map_language_neutral' => APACHESOLR_MULTILINGUAL_LANGUAGE_UND,
      'apachesolr_multilingual_clir' => array(
        'apachesolr_multilingual_index_translations' => 0,
        'apachesolr_multilingual_index_unpublished_translations' => 0,
        'apachesolr_multilingual_index_term_translations' => 0,
      ),
    ),
  );
}

/**
 * Implements hook_multilingual_settings_changed().
 */
function apachesolr_multilingual_multilingual_settings_changed() {
  if (!module_exists('apachesolr_multilingual_confgen')) {
    drupal_set_message(t('Multilingual settings have been changed. Maybe you have to create a new set of configuration files, to update your solr configuration and to restart your solr server.', array(
      '!link_download' => l(t('download'), 'admin/settings/apachesolr/confgen'),
    )), 'warning');
  }
}

/**
 * Wrapper for locale_language_list() to ensure an alphabetical order of
 * languages. That's important for the spellcheck.
 */
function apachesolr_multilingual_language_list() {
  $languages = locale_language_list();
  ksort($languages, SORT_ASC & SORT_STRING);
  return $languages;
}

/**
 *
 * Modify the build array for any search output build by Apache Solr
 * This includes core and custom pages and makes it very easy to modify both
 * of them at once
 *
 * @param array $build
 * @param array $search_page
 */
function apachesolr_multilingual_apachesolr_search_page_alter(array &$build, array $search_page) {
  if (apachesolr_has_searched($search_page['env_id'])) {
    $environment = apachesolr_multilingual_environment_load($search_page['env_id']);
    $settings = $environment['conf']['apachesolr_multilingual_index_settings'];
    if ($settings['apachesolr_multilingual_index']) {
      $languages = apachesolr_multilingual_language_list();
      $filter_languages =& variable_static('apachesolr_multilingual_filter_languages_' . $search_page['page_id']);

      // Retrieve suggestion
      $suggestions = apachesolr_multilingual_get_search_suggestions($search_page['env_id'], $filter_languages);
      if (!empty($suggestions)) {
        $links = array();
        foreach ($suggestions as $language_id => $suggestion) {
          $links[] = (1 == count($filter_languages) ? '' : $languages[$language_id] . ': ') . l($suggestion, $search_page['search_path'] . '/' . $suggestion);
        }
        $build['suggestions'] = theme('apachesolr_search_suggestions', $links);
      }
    }
  }
}

/**
 * Retrieve all of the suggestions that were given after a certain search
 * Mostly copied from @see apachesolrsearch_get_search_suggestions();
 * @return array()
 */
function apachesolr_multilingual_get_search_suggestions($env_id, $filter_languages = array()) {
  $suggestions_output = array();
  if (apachesolr_has_searched($env_id)) {
    $query = apachesolr_current_query($env_id);
    $keyword = $query
      ->getParam('q');
    $searcher = $query
      ->getSearcher();
    $response = apachesolr_static_response_cache($searcher);
    $language_ids = array_keys(apachesolr_multilingual_language_list());

    // Get spellchecker suggestions into an array.
    foreach ($language_ids as $language_id) {
      if (!empty($filter_languages) && !in_array($language_id, $filter_languages)) {
        continue;
      }
      if (!empty($response->{'spellcheck_' . $language_id}->suggestions)) {
        $suggestions = get_object_vars($response->{'spellcheck_' . $language_id}->suggestions);
        if ($suggestions) {
          $replacements = array();

          // Get the original query and retrieve all words with suggestions.
          foreach ($suggestions as $word => $value) {
            $replacements[$word] = $value->suggestion[0];
          }

          // Replace the keyword with the suggested keyword.
          $suggested_keyword = strtr($keyword, $replacements);

          // Show only if suggestion is different than current query.
          if ($keyword != $suggested_keyword) {
            $suggestions_output[$language_id] = $suggested_keyword;
          }
        }
      }
    }
  }
  return $suggestions_output;
}

/**
 * Implements hook_apachesolr_field_mappings_alter()
 *
 * TODO needs review!
 */
function apachesolr_multilingual_apachesolr_field_mappings_alter(&$mappings, $entity_type) {
  $multilingual_mapping = array(
    'apachesolr_term_reference_indexing_callback' => 'apachesolr_multilingual_term_reference_indexing_callback',
  );
  foreach ($mappings as &$mapping) {
    if (isset($mapping['indexing_callback']) && is_array($mapping['indexing_callback'])) {
      foreach ($mapping['indexing_callback'] as $key => $value) {
        if (isset($multilingual_mapping[$value])) {
          $mapping['indexing_callback'][$key] = $multilingual_mapping[$value];
        }
      }
    }
  }
}

/**
 * Callback that converts term_reference field into an array
 *
 * TODO needs review!
 *
 * @param object $node
 * @param string $field_name
 * @param string $index_key
 * @param array $field_info
 * @return array $fields
 *   fields that will be indexed for this term reference
 */
function apachesolr_multilingual_term_reference_indexing_callback($node, $field_name, $index_key, array $field_info) {
  module_load_include('index.inc', 'apachesolr_multilingual');
  return apachesolr_multilingual_term_reference_indexing_callback_implementation($node, $field_name, $index_key, $field_info);
}
function apachesolr_multilingual_get_dynamic_text_field_prefixes_and_types() {
  return array(
    // prefix => type
    'ts_' => 'text',
    // single text value
    'tm_' => 'text',
    // multiple text values
    'tos_' => 'text',
    // single text-omitNorms value
    'tom_' => 'text',
    // multiple text-omitNorms values
    'tus_' => 'text_und',
    // single text-unstemmed value
    'tum_' => 'text_und',
    // multiple text-unstemmed values
    // In addition to the EdgeNGramFilterFactory this text field applies a
    // LowerCaseFilter. For some languages it makes sense to remove that
    // LowerCaseFilter.
    'tes_' => 'edge_n2_kw_text',
    // single text-edgeNgram value
    'tem_' => 'edge_n2_kw_text',
    // multiple text-edgeNgram values
    // A text field that only splits on whitespace for exact matching of words.
    // In addition it applies a LowerCaseFilter. For some languages it makes
    // sense to remove that LowerCaseFilter.
    'tws_' => 'text_ws',
    // single text-whiteSpace value
    'twm_' => 'text_ws',
  );
}

Functions

Namesort descending Description
apachesolr_multilingual_add_settings_form_elements
apachesolr_multilingual_apachesolr_field_mappings_alter Implements hook_apachesolr_field_mappings_alter()
apachesolr_multilingual_apachesolr_field_name_map_alter Implements hook_apachesolr_field_name_map_alter().
apachesolr_multilingual_apachesolr_index_documents_alter Implements hook_apachesolr_index_documents_alter().
apachesolr_multilingual_apachesolr_query_alter Implements hook_apachesolr_modify_query().
apachesolr_multilingual_apachesolr_search_custom_page_search_form_submit
apachesolr_multilingual_apachesolr_search_page_alter Modify the build array for any search output build by Apache Solr This includes core and custom pages and makes it very easy to modify both of them at once
apachesolr_multilingual_block_admin_configure_form_submit
apachesolr_multilingual_block_admin_configure_form_validate
apachesolr_multilingual_copy_common_to_i18n_fields
apachesolr_multilingual_environment_add_defaults
apachesolr_multilingual_environment_edit_form_submit
apachesolr_multilingual_environment_load
apachesolr_multilingual_fire_callbacks
apachesolr_multilingual_form_apachesolr_environment_edit_form_alter Implements hook_form_alter().
apachesolr_multilingual_form_apachesolr_search_bias_form_alter Implements hook_form_apachesolr_search_bias_form_alter().
apachesolr_multilingual_form_apachesolr_search_custom_page_search_form_alter Implements hook_form_search_form_alter().
apachesolr_multilingual_form_apachesolr_search_page_settings_form_alter Implements hook_form_alter().
apachesolr_multilingual_form_block_admin_configure_alter Add multilingual settings to MLT blocks.
apachesolr_multilingual_get_dynamic_text_field_prefixes_and_types
apachesolr_multilingual_get_language_filters_by_query
apachesolr_multilingual_get_search_suggestions Retrieve all of the suggestions that were given after a certain search Mostly copied from
apachesolr_multilingual_language_list Wrapper for locale_language_list() to ensure an alphabetical order of languages. That's important for the spellcheck.
apachesolr_multilingual_load_all_environments
apachesolr_multilingual_module_implements_alter Implements hook_module_implements_alter().
apachesolr_multilingual_multilingual_settings_changed Implements hook_multilingual_settings_changed().
apachesolr_multilingual_nodeapi Implements hook_nodeapi().
apachesolr_multilingual_node_update Implements hook_node_update().
apachesolr_multilingual_search_page_settings_add_defaults
apachesolr_multilingual_search_page_settings_form_submit Processes apachesolr_search_page_settings_form form submissions.
apachesolr_multilingual_search_page_settings_form_validate
apachesolr_multilingual_term_reference_indexing_callback Callback that converts term_reference field into an array

Constants

Namesort descending Description
APACHESOLR_MULTILINGUAL_LANGUAGE_NONE
APACHESOLR_MULTILINGUAL_LANGUAGE_UND
APACHESOLR_MULTILINGUAL_MAP_ALL Language code used when mapping language-neutral content to all languages. Defined by ISO639-2 for "Multilingual".