You are here

language_hierarchy.module in Language Hierarchy 7

Same filename and directory in other branches
  1. 8 language_hierarchy.module
  2. 2.x language_hierarchy.module

File

language_hierarchy.module
View source
<?php

/**
 * Implements hook_language_init().
 */
function language_hierarchy_language_init() {
  global $conf;
  foreach (language_list() as $langcode => $language) {
    if (!empty($language->parent)) {
      $conf["locale_custom_strings_{$langcode}"] = new LocaleHierarchy($langcode);
    }
  }
}

/**
 * Implements hook_permission().
 *
 * @return array
 */
function language_hierarchy_permission() {
  return array(
    'always fallback to default' => array(
      'title' => t('Always fallback to default language'),
      'description' => t('Always include default language in fallback chain.'),
    ),
  );
}

/**
 * Implements hook_menu_alter().
 *
 * Use our form instead of the default one from locale module.
 */
function language_hierarchy_menu_alter(&$items) {

  // Change the item values by reference.
  $items['admin/config/regional/language']['page arguments'] = array(
    'language_hierarchy_form',
  );
  $items['admin/config/regional/language']['file'] = 'language_hierarchy_form.inc';
  $items['admin/config/regional/language']['file path'] = drupal_get_path('module', 'language_hierarchy');
}

/**
 * Implements hook_theme().
 *
 * We need run our forms through custom theme functions in order to build the
 * table structure which is required by tabledrag.js.  Before we can use our
 * custom theme functions, we need to implement hook_theme in order to register
 * them with Drupal.
 *
 * We are defining our theme hooks with the same name as the form generation
 * function so that Drupal automatically calls our theming function when the
 * form is displayed.
 */
function language_hierarchy_theme() {
  return array(
    // Theme function for the 'simple' example.
    'language_hierarchy_form' => array(
      'render element' => 'form',
      'file' => 'language_hierarchy_form.inc',
    ),
  );
}

/**
 * Implements hook_views_api().
 */
function language_hierarchy_views_api() {
  return array(
    'api' => 3,
    'path' => drupal_get_path('module', 'language_hierarchy') . '/views',
  );
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * We need to alter the language delete form to stop users from deleting parent languages.
 */
function language_hierarchy_form_locale_languages_delete_form_alter(&$form, &$form_state) {
  $language_code = isset($form['langcode']['#value']) ? $form['langcode']['#value'] : '';
  $descendants = language_hierarchy_get_descendants($language_code);

  // If the language is a parent, then we can't delete it.
  if (isset($descendants->children) && sizeof($descendants->children) > 0) {
    $message = t('The @languagename language has @childlanguages child languages. These must be moved or deleted before you can delete this language.', array(
      '@languagename' => $descendants->name,
      '@childlanguages' => sizeof($descendants->children),
    ));
    drupal_set_message($message, 'warning');
    drupal_goto('admin/config/regional/language');
  }
  else {
    $form['#submit'][] = 'language_hierarchy_delete_form_submit_callback';
  }
}

/**
 *  An additional submission callback for the delete language form.
 */
function language_hierarchy_delete_form_submit_callback($form, &$form_state) {
  $language_code = $form['langcode']['#value'];

  // Delete variable for custom strings.
  variable_del("locale_custom_strings_{$language_code}");
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alter the predefined language add form to include language parent functionality.
 */
function language_hierarchy_form_locale_languages_predefined_form_alter(&$form, &$form_state) {

  // Add language parent select list into the predefined add form fieldset.
  $form['language list']['parent_language_list'] = _language_hierarchy_add_language_parent_select_list();

  // Include a submit callback to update the parent language.
  $form['#submit'][] = 'language_hierarchy_language_add_form_submit_callback';
}

/**
 * Implements hook_form_FORM_ID_alter().
 *
 * Alter the custom language add form to include language parent functionality.
 */
function language_hierarchy_form_locale_languages_custom_form_alter(&$form, &$form_state) {

  // Add language parent select list into the predefined add form fieldset.
  $form['custom language']['parent_language_list'] = _language_hierarchy_add_language_parent_select_list();

  // Include a submit callback to update the parent language.
  $form['#submit'][] = 'language_hierarchy_language_add_form_submit_callback';
}

/**
 * A common function to return language parent select list for use in language add forms.
 *
 * @return array
 *   Form API select list.
 */
function _language_hierarchy_add_language_parent_select_list() {
  $languages = language_hierarchy_language_list();
  $options = array(
    'none' => t('No parent language'),
  );

  // Creation options for parent language select list.
  foreach ($languages as $language_code => $language) {
    $options[$language_code] = t('@language_name (@language_code)', array(
      '@language_name' => $language->name,
      '@language_code' => $language->language,
    ));
  }
  $select_list = array(
    '#type' => 'select',
    '#title' => t('Parent language'),
    '#options' => $options,
    '#default_value' => 'none',
    '#description' => t('Select the parent language'),
  );
  return $select_list;
}

/**
 *  An additional submission callback for the add predefined language form.
 */
function language_hierarchy_language_add_form_submit_callback($form, &$form_state) {
  $langcode = $form_state['values']['langcode'];
  if (isset($form_state['values']['parent_language_list']) && $form_state['values']['parent_language_list'] != 'none') {

    // Reset static cache language so the list is fetched from database
    drupal_static_reset('language_hierarchy_language_list');
    drupal_static_reset('language_hierarchy_get_children');
    drupal_static_reset('language_hierarchy_get_root_languages');

    // Update the parent information.
    db_update('languages')
      ->fields(array(
      'parent' => $form_state['values']['parent_language_list'],
    ))
      ->condition('language', $form_state['values']['langcode'], '=')
      ->execute();

    // If language has parent language then set variable for custom strings.
    variable_set("locale_custom_strings_{$langcode}", new LocaleHierarchy($langcode));

    // Normalize weight, so languages are returned in correct order for flat lists.
    _language_hierarchy_normalize_weight();
  }
}

/**
 * Implements hook_form_alter().
 */
function language_hierarchy_form_alter(&$form, &$form_state, $form_id) {
  if (in_array($form_id, array(
    'locale_translate_edit_form',
    'i18n_string_locale_translate_edit_form',
  ))) {
    $lid = $form['lid']['#value'];
    $source = db_query('SELECT source, context, textgroup, location FROM {locales_source} WHERE lid = :lid', array(
      ':lid' => $lid,
    ))
      ->fetchObject();
    $languages = language_hierarchy_language_list();
    if (!$source || count($languages) == 1) {
      return;
    }

    // Approximate the number of rows to use in the default textarea.
    $rows = min(ceil(str_word_count($source->source) / 12), 10);
    foreach ($languages as $langcode => $language) {
      if (isset($form['translations'][$langcode])) {
        $form['translations'][$langcode]['#prefix'] = '<div style="margin-left: ' . $language->depth * 2 . 'em">';
        $form['translations'][$langcode]['#suffix'] = '</div>';
      }
      else {
        $form['translations'][$langcode] = array(
          '#type' => 'textarea',
          '#rows' => $rows,
          '#title' => $language->name . ' (' . $form['original']['#title'] . ')',
          '#disabled' => TRUE,
          // Reverse wordwrap().
          '#default_value' => $source->source,
        );
      }
      $form['translations'][$langcode]['#weight'] = $language->weight;
    }
  }
}

/**
 * Implements hook_i18n_string_info_alter().
 */
function language_hierarchy_i18n_string_info_alter(&$object_info) {
  foreach ($object_info as $type => &$info) {
    if (!isset($info['class']) || $info['class'] == 'i18n_string_textgroup_default') {
      $info['class'] = 'language_hierarchy_i18n_string_textgroup_default';
    }
    elseif ($info['class'] == 'i18n_string_textgroup_cached') {
      $info['class'] = 'language_hierarchy_i18n_string_textgroup_cached';
    }
  }
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function language_hierarchy_form_i18n_string_translate_page_overview_form_alter(&$form, &$form_state) {
  $strings = $form_state['build_info']['args'][1];
  $source_langcode = variable_get_value('i18n_string_source_language');
  $languages = language_list();
  foreach ($form['languages']['#rows'] as $langcode => &$row) {
    if ($langcode != $source_langcode) {
      foreach ($strings as $string_object) {
        if ($string_object
          ->get_translation($langcode)) {
          $fallback_langcode = _language_hierarchy_get_i18n_string_translation_fallback_langcode($string_object, $langcode);
          if ($fallback_langcode !== NULL) {
            $row['status'] = t('fallback from @language', array(
              '@language' => $languages[$fallback_langcode]->name,
            ));
          }
          break;
        }
      }
    }
  }
  unset($row);
}

/**
 * Implements hook_form_FORM_ID_alter().
 */
function language_hierarchy_form_i18n_string_translate_page_form_alter(&$form, &$form_state) {
  $languages = language_list();
  foreach ($form['string_groups']['#value'] as $group_key => $strings) {
    foreach ($strings as $string_object) {
      $fallback_langcode = _language_hierarchy_get_i18n_string_translation_fallback_langcode($string_object, $form['langcode']['#value']);
      if ($fallback_langcode !== NULL) {
        $name = $string_object
          ->get_name();
        $form['strings'][$group_key][$name]['#description'] .= t('The @language fallback is currently used for this string. If you save this string, a new one will be created and the fallback not used anymore.', array(
          '@language' => $languages[$fallback_langcode]->name,
        ));
      }
    }
  }
}

/**
 * Returns fallback language code if fallback is used.
 *
 * @param i18n_string_object $string_object
 * @param string $langcode
 *
 * @return string|null
 */
function _language_hierarchy_get_i18n_string_translation_fallback_langcode($string_object, $langcode) {
  $class = i18n_string_group_info($string_object->textgroup, 'class', 'i18n_string_textgroup_default');
  $group_object = new $class($string_object->textgroup);
  if ($group_object instanceof language_hierarchy_i18n_string_textgroup_default) {
    $show_language =& drupal_static('language_hierarchy_i18n_show_language');
    $show_language = TRUE;
    $translation = $group_object
      ->load_translation($string_object, $langcode);
    drupal_static_reset('language_hierarchy_i18n_show_language');
    if ($translation && isset($translation->language) && $translation->language != $langcode) {
      return $translation->language;
    }
  }
  return NULL;
}

/**
 * Returns parent language of the provided language.
 *
 * @param $langcode
 *   Code of language you want to retrieve parent of.
 *
 * @return mixed
 *   Parent language, NULL otherwise
 */
function language_hierarchy_get_parent($langcode) {
  $languages = language_list();
  if (isset($languages[$langcode]) && !empty($languages[$langcode]->parent) && isset($languages[$languages[$langcode]->parent])) {
    return $languages[$languages[$langcode]->parent];
  }
  else {
    return NULL;
  }
}

/**
 * Return children of the language provided
 *
 * @param $langcode
 *
 * @return array()
 *   Array with children languages
 */
function language_hierarchy_get_children($langcode) {
  $children =& drupal_static(__FUNCTION__);
  if (!isset($children[$langcode])) {
    $children[$langcode] = db_select('languages', 'l')
      ->fields('l')
      ->condition('parent', $langcode)
      ->orderBy('weight', 'ASC')
      ->orderBy('name', 'ASC')
      ->execute()
      ->fetchAllAssoc('language');
  }
  return $children[$langcode];
}

/**
 * Returns top level languages.
 *
 * @return array
 *   List of top level languages, keyed with language code
 */
function language_hierarchy_get_root_languages() {
  $top_level_languages =& drupal_static(__FUNCTION__);
  if (!isset($top_level_languages)) {
    $top_level_languages = db_select('languages', 'l')
      ->fields('l')
      ->condition('parent', '')
      ->orderBy('weight', 'ASC')
      ->orderBy('name', 'ASC')
      ->execute()
      ->fetchAllAssoc('language');
  }
  return $top_level_languages;
}

/**
 * Returns ancestors language code of the provided language.
 *
 * @param $langcode
 *   Code of language you want to retrieve parent of.
 *
 * @param $enabled_languages_only Boolean
 *   Determines whatever only the enabled language ancestors will be shown.
 *
 * @return mixed
 *   Ordered array with all ancestors, most specific on the top.
 */
function language_hierarchy_get_ancestors($langcode, $enabled_languages_only = FALSE) {
  $ancestors = array();
  while ($ancestor = language_hierarchy_get_parent($langcode)) {

    // Check conditions on whatever to add the language ancestor to the results.
    if ($enabled_languages_only == FALSE || $ancestor->enabled == TRUE) {
      $ancestors[$ancestor->language] = $ancestor;
    }
    $langcode = $ancestor->language;
  }
  return $ancestors;
}

/**
 * Returns tree of descendants for the provided language.
 *
 * @param $langcode
 *   Code of language you want to retrieve descendants of.
 *
 * @return mixed
 *   Requested language object with 'children' property containing the tree of descendants
 */
function language_hierarchy_get_descendants($langcode = NULL, &$language = NULL) {
  $languages = language_list();
  if (!empty($languages[$langcode])) {
    $language = $languages[$langcode];
    $children = language_hierarchy_get_children($langcode);
    foreach ($children as $child_code => $child) {
      $language->children[$child_code] = language_hierarchy_get_descendants($child_code, $child);
    }
  }
  return $language;
}

/**
 * Returns flat list of languages with additional depth information, ordered in manner that is then usable by tablesort
 *
 * @return array()
 *   Language list
 */
function language_hierarchy_language_list() {
  $languages =& drupal_static(__FUNCTION__);
  if (empty($languages)) {
    $root_languages = language_hierarchy_get_root_languages();
    foreach ($root_languages as $langcode => $language) {
      $language->depth = count(language_hierarchy_get_ancestors($langcode));
      $languages[$langcode] = $language;
      _language_hierarchy_language_list_descendants($langcode, $languages);
    }
  }
  return $languages;
}

/**
 * Sets a parent of specified language
 *
 * @param $language
 *   Code of the language you want to set parent for
 *
 * @param $parent_langcode
 *   Code of the language to set parent to
 */
function language_hierarchy_set_parent($langcode, $parent_langcode) {
  $languages = language_list();

  // Check if the language requested as new parent exists and is not one of the descendants of $langcode
  if (empty($languages[$parent_langcode])) {
    return FALSE;
  }
  if (!empty($languages[$langcode])) {
    db_update('languages')
      ->fields(array(
      'parent' => $parent_langcode,
    ))
      ->condition('language', $langcode)
      ->execute();
    return TRUE;
  }
  else {
    return FALSE;
  }
}

/**
 * Implements hook_language_fallback_candidates_alter().
 *
 * Alter the language fallbacks so that languages only fallback to their ancestors in the hierarchy tree.
 * Root items do not fall back to other root items, or English.
 * This allows constructions of languages where Spanish languages can fallback to a common Spanish ancestor content
 * while bypassing English content.
 */
function language_hierarchy_language_fallback_candidates_alter(array &$fallback_candidates) {

  // Get the ancestors of the currently request language. Use these as the fallback language candidates
  $fallback_languages = array_keys(language_hierarchy_get_ancestors($GLOBALS['language']->language, TRUE));

  // If user has special permissions he can always see default language as an ancestor
  $default_language = language_default();
  if (!in_array($default_language->language, $fallback_languages) && user_access('always fallback to default')) {
    $fallback_languages[] = $default_language->language;
  }

  // Language neutral should always be the last item in the list.
  $fallback_languages[] = LANGUAGE_NONE;

  // Overwrite the list of fallback languages with our own ordered, filtered set.
  $fallback_candidates = $fallback_languages;
}

/**
 * Recursion helper function that populates language_list in hierarchical order
 *
 * @param $langcode
 *   Code of language to request descendants of
 *
 * @param null $languages
 *   List to populate
 */
function _language_hierarchy_language_list_descendants($langcode, &$languages = NULL) {
  $children = language_hierarchy_get_children($langcode);
  foreach ($children as $child_code => $child) {
    $child->depth = count(language_hierarchy_get_ancestors($child_code));
    $languages[$child_code] = $child;
    _language_hierarchy_language_list_descendants($child_code, $languages);
  }
}

/**
 * Normalizes weight of the languages so 'flat' functions like
 * language_list still return the languages in correct order.
 */
function _language_hierarchy_normalize_weight() {
  $languages = language_hierarchy_language_list();
  $normalized_weight = -10;
  foreach ($languages as $langcode => $language) {
    db_update('languages')
      ->fields(array(
      'weight' => $normalized_weight,
    ))
      ->condition('language', $langcode, '=')
      ->execute();
    $normalized_weight++;
  }
}

/**
 * Attach libraries to replace local tasks links with hierarchical links.
 */
function language_hierarchy_attach_language_selector(&$element) {
  $languages = language_hierarchy_get_root_languages();
  foreach ($languages as $langcode => $language) {
    $languages[$langcode] = language_hierarchy_get_descendants($langcode, $language);
  }

  // Expose language hierarchy as JS variable, so JS code can pick it up.
  $element['#attached']['js'][] = array(
    'type' => 'setting',
    'data' => array(
      'languageHierarchy' => language_hierarchy_language_list(),
    ),
  );
  $path = drupal_get_path('module', 'language_hierarchy');
  $element['#attached']['js'][] = $path . '/language_hierarchy.js';
  $element['#attached']['css'][] = $path . '/language_hierarchy.css';
}

/**
 * Preprocess select to add in aria attributes.
 */
function language_hierarchy_preprocess_select(&$variables) {
  if (language_hierarchy_is_language_selector($variables['element'])) {
    language_hierarchy_indent_options_in_select($variables['element']);
  }
}

/**
 * Check if given form element is used to chose language.
 *
 * @param $element
 *   Form API element array.
 *
 * @return bool
 *   TRUE if element is used to select language, FALSE otherwise.
 */
function language_hierarchy_is_language_selector($element) {
  $language_list = language_list();
  $common = array_intersect(array_keys($language_list), array_keys($element['#options']));
  return count($common) > 0;
}

/**
 * Add indentation to options in select element to visualize language hierarchy.
 */
function language_hierarchy_indent_options_in_select(&$element) {
  $languages = language_hierarchy_language_list();
  foreach ($languages as $langcode => $language) {
    if (isset($element['#options'][$langcode])) {
      $indent = str_repeat('--', $language->depth);
      $indent = $indent ? $indent . ' ' : '';
      $element['#options'][$langcode] = $indent . $element['#options'][$langcode];
    }
  }
}

Functions

Namesort descending Description
language_hierarchy_attach_language_selector Attach libraries to replace local tasks links with hierarchical links.
language_hierarchy_delete_form_submit_callback An additional submission callback for the delete language form.
language_hierarchy_form_alter Implements hook_form_alter().
language_hierarchy_form_i18n_string_translate_page_form_alter Implements hook_form_FORM_ID_alter().
language_hierarchy_form_i18n_string_translate_page_overview_form_alter Implements hook_form_FORM_ID_alter().
language_hierarchy_form_locale_languages_custom_form_alter Implements hook_form_FORM_ID_alter().
language_hierarchy_form_locale_languages_delete_form_alter Implements hook_form_FORM_ID_alter().
language_hierarchy_form_locale_languages_predefined_form_alter Implements hook_form_FORM_ID_alter().
language_hierarchy_get_ancestors Returns ancestors language code of the provided language.
language_hierarchy_get_children Return children of the language provided
language_hierarchy_get_descendants Returns tree of descendants for the provided language.
language_hierarchy_get_parent Returns parent language of the provided language.
language_hierarchy_get_root_languages Returns top level languages.
language_hierarchy_i18n_string_info_alter Implements hook_i18n_string_info_alter().
language_hierarchy_indent_options_in_select Add indentation to options in select element to visualize language hierarchy.
language_hierarchy_is_language_selector Check if given form element is used to chose language.
language_hierarchy_language_add_form_submit_callback An additional submission callback for the add predefined language form.
language_hierarchy_language_fallback_candidates_alter Implements hook_language_fallback_candidates_alter().
language_hierarchy_language_init Implements hook_language_init().
language_hierarchy_language_list Returns flat list of languages with additional depth information, ordered in manner that is then usable by tablesort
language_hierarchy_menu_alter Implements hook_menu_alter().
language_hierarchy_permission Implements hook_permission().
language_hierarchy_preprocess_select Preprocess select to add in aria attributes.
language_hierarchy_set_parent Sets a parent of specified language
language_hierarchy_theme Implements hook_theme().
language_hierarchy_views_api Implements hook_views_api().
_language_hierarchy_add_language_parent_select_list A common function to return language parent select list for use in language add forms.
_language_hierarchy_get_i18n_string_translation_fallback_langcode Returns fallback language code if fallback is used.
_language_hierarchy_language_list_descendants Recursion helper function that populates language_list in hierarchical order
_language_hierarchy_normalize_weight Normalizes weight of the languages so 'flat' functions like language_list still return the languages in correct order.