You are here

i18n_book_navigation.module in Book translation 7.2

Same filename and directory in other branches
  1. 6.2 i18n_book_navigation.module
  2. 6 i18n_book_navigation.module

Defines the Book translation module.

File

i18n_book_navigation.module
View source
<?php

/**
 * @file
 * Defines the Book translation module.
 */

/**
 * Implements hook_theme().
 *
 * The i18n_book_navigation uses the book-navigation.tpl.php file for theming
 * the "footer" book navigation in book pages. It will use it to display
 * this navigation on translated nodes, even if they're not part of a book
 * outline. We must implement our own theme however, because the default
 * book_navigation will call menu_tree_all_data() before i18n_book_navigation,
 * which will store an empty tree statically, preventing us from using it and
 * translating the links.
 *
 * Note that, because of the way Drupal template auto-discovery works, a theme
 * cannot override the i18n_book_navigation rendering using the
 * book-navigation.tpl.php template. Drupal bases the template discovery on the
 * theme key, NOT the template property. So, if a theme wishes to override the
 * default template, it must use i18n-book-navigation.tpl.php instead.
 */
function i18n_book_navigation_theme($existing, $type, $theme, $path) {
  return array(
    'i18n_book_navigation' => array(
      'variables' => array(
        'book_link' => NULL,
      ),
      'path' => drupal_get_path('module', 'book'),
      'template' => 'book-navigation',
    ),
  );
}

/**
 * Implements hook_node_view().
 */
function i18n_book_navigation_node_view($node, $view_mode = 'full') {
  if ($view_mode == 'full') {

    // Check that we're not dealing with the original node itself or with a
    // non-translated node.
    if (isset($node->tnid) && $node->nid != $node->tnid) {
      if ($node->tnid != 0) {

        // Get the original node book information.
        $tnode = (object) array(
          'nid' => $node->tnid,
        );
        book_node_load(array(
          $node->tnid => $tnode,
        ), array());
      }
      else {

        // In the case of field translation tnid is zero.
        // We work with nid instead.
        $tnode = (object) array(
          'nid' => $node->nid,
        );
        book_node_load(array(
          $node->nid => $tnode,
        ), array());
      }

      // If the original node is part of a book outline.
      if (!empty($tnode->book['bid'])) {

        // Set the breadcrumb.
        i18n_book_navigation_set_breadcrumb($node, i18n_book_navigation($tnode));

        // Add the book navigation.
        $node->content['book_navigation'] = array(
          '#markup' => theme('i18n_book_navigation', $tnode->book),
          '#weight' => 100,
        );
      }
    }
  }
}

/**
 * Implements hook_block_info().
 */
function i18n_book_navigation_block_info() {
  $block = array();
  $block['i18n_book_navigation']['info'] = t("i18n Book navigation");
  $block['i18n_book_navigation']['cache'] = DRUPAL_CACHE_PER_PAGE | DRUPAL_CACHE_PER_ROLE;
  return $block;
}

/**
 * Implements hook_block_configure().
 *
 * Uses the same settings as the book module.
 * @see book_block_configure()
 */
function i18n_book_navigation_block_configure($delta = '') {
  return book_block_configure($delta);
}

/**
 * Implements hook_block_save().
 *
 * Uses the same settings as the book module.
 * @see book_block_save()
 */
function i18n_book_navigation_block_save($delta = '', $edit = array()) {
  return book_block_save($delta, $edit);
}

/**
 * Implements hook_block_view().
 *
 * Port from the book module block. This function contains much of the same
 * logic as the original book_block_view(), except for the additions of the
 * translation logic.
 * @see book_block_view()
 */
function i18n_book_navigation_block_view($delta = '') {
  $tnode = menu_get_object();

  // Not on a node page.
  if (empty($tnode)) {
    return array();
  }
  $block = array();

  // Get the node in the original language.
  $node = i18n_book_navigation_get_original_node($tnode);
  if (!empty($node->book)) {
    $current_bid = $node->book['bid'];
  }
  else {
    $current_bid = 0;
  }
  if (variable_get('book_block_mode', 'all pages') == 'all pages') {
    $book_menus = array();
    foreach (i18n_book_navigation_get_books() as $bid => $book) {
      $tree = array();
      if ($bid == $current_bid) {
        $tree = i18n_book_navigation($node);
      }
      else {
        $book['in_active_trail'] = FALSE;

        // Check whether user can access the book link.
        $book_node = node_load($book['nid']);
        $book['access'] = node_access('view', $book_node);
        $tree[0]['link'] = $book;
        $tree[0]['below'] = FALSE;
        $tree = i18n_book_navigation_translate_tree($tree);
      }

      // Render.
      $book_menus[$bid] = menu_tree_output($tree);
    }
    $block['subject'] = t("i18n Book navigation");
    $book_menus['#theme'] = 'book_all_books_block';
    $block['content'] = $book_menus;
  }
  elseif ($current_bid) {

    // Disable the i18n node selection mode.
    $prev = i18n_select(FALSE);

    // Only display this block when the user is browsing a book.
    $title = db_select('node', 'n')
      ->fields('n', array(
      'title',
    ))
      ->condition('n.nid', $node->book['bid'])
      ->addTag('node_access')
      ->execute()
      ->fetchField();

    // Revert the i18n node selection mode.
    i18n_select($prev);

    // Only show the block if the user has view access for the top-level node.
    if ($title) {
      $tree = i18n_book_navigation($node);
      if (count($tree)) {

        // There should only be one element at the top level.
        $data = array_shift($tree);
        $block['subject'] = theme('book_title_link', array(
          'link' => $data['link'],
        ));
        $block['content'] = $data['below'] ? menu_tree_output($data['below']) : '';
      }
    }
  }
  return $block;
}

/**
 * Set the breadcrumb.
 *
 * This is a port from menu_get_active_breadcrumb(). It will set the breadcrumb
 * based on the current, translated tree.
 * @see menu_get_active_breadcrumb()
 *
 * @param object $node
 *   The node currently viewed (as in menu_get_item())
 * @param array $tree
 *   The translated tree
 */
function i18n_book_navigation_set_breadcrumb($node, $tree) {
  $trail = array();
  $breadcrumb = array(
    l(t("Home"), '<front>'),
  );
  $href = 'node/' . $node->nid;
  list($key, $curr) = each($tree);
  while ($curr) {

    // Terminate the loop when we find the current path in the active trail.
    if ($curr['link']['href'] == $href) {
      $trail[] = $curr['link'];
      $curr = FALSE;
    }
    else {

      // Add the link if it's in the active trail, then move to the link below.
      if ($curr['link']['in_active_trail']) {
        $trail[] = $curr['link'];
        $tree = $curr['below'] ? $curr['below'] : array();
      }
      list($key, $curr) = each($tree);
    }
  }
  foreach ($trail as $element) {
    $breadcrumb[] = l($element['title'], $element['href'], $element['localized_options']);
  }

  // Remove the last element.
  array_pop($breadcrumb);
  drupal_set_breadcrumb($breadcrumb);
}

/**
 * Get the translated menu tree for the original node.
 *
 * @param object $node
 *   The node in the original language, that is part of the book outline.
 * @param array $config
 *   (optional) Only used in context of the Menu Block module implementation.
 *   This array contains the information and settings passed to the
 *   hook_menu_block_tree_alter() hooks. Defaults to null.
 *
 * @return array
 *   The translated menu tree, or an empty array
 */
function i18n_book_navigation($node, $config = NULL) {
  $cache =& drupal_static(__FUNCTION__, array());
  if (!empty($node->book)) {

    // Disable the i18n node selection mode.
    $prev = i18n_select(FALSE);
  }
  else {

    // Nothing we can do.
    return array();
  }

  // If we have any configuration, we skip the cache.
  if (!empty($cache[$node->book['bid']]) && !isset($config)) {
    return $cache[$node->book['bid']];
  }

  // Get the original menu tree.
  if (!empty($config['expanded'])) {
    $tree = menu_tree_all_data($node->book['menu_name']);
  }
  else {
    $tree = menu_tree_all_data($node->book['menu_name'], $node->book);
  }

  // Translate the tree.
  $tree = i18n_book_navigation_translate_tree($tree);

  // Cleanup the "empty" leaves.
  $tree = i18n_book_navigation_cleanup($tree);
  if (!empty($node->book)) {

    // Revert the i18n node selection mode.
    i18n_select($prev);
  }
  $cache[$node->book['bid']] = $tree;
  return $tree;
}

/**
 * Get all the available books.
 *
 * Port from book_get_books(). We need to deactivate the i18n module in order to
 * retrieve all books, regardless of the language settings.
 * @see book_get_books()
 */
function i18n_book_navigation_get_books() {

  // Disable the i18n node selection mode.
  $prev = i18n_select(FALSE);
  $books = book_get_books();

  // Revert the i18n node selection mode.
  i18n_select($prev);
  return $books;
}

/**
 * Translate the menu tree.
 *
 * Walks down the tree and translates every item. Will respect the translation
 * settings from the i18n module.
 *
 * @param array $tree
 *   The menu tree
 * @param string $lan
 *   (optional) The language to translate the tree in. Defaults to the
 *   current language. Defaults to null.
 *
 * @return array
 *   The translated menu tree
 */
function i18n_book_navigation_translate_tree($tree, $lan = NULL) {
  if (!$lan) {
    $lan = i18n_langcode();
  }
  foreach ($tree as $key => &$item) {
    if ($node = i18n_book_navigation_get_translated_node($item['link']['link_path'], $lan)) {
      $item['link']['title'] = $item['link']['link_title'] = $node->title;
      $item['link']['href'] = $item['link']['link_path'] = 'node/' . $node->nid;
    }
    else {
      $item = array();
    }
    if (!empty($item['below'])) {
      $item['below'] = i18n_book_navigation_translate_tree($item['below'], $lan);
    }
  }
  return $tree;
}

/**
 * Clean up the menu tree.
 *
 * Depending on the translation mode, some leaves my be displayed as "empty".
 * Walks down the tree and unsets all "empty" items.
 *
 * @param array $tree
 *   The menu tree.
 *
 * @return array
 *   The cleaned up menu tree.
 */
function i18n_book_navigation_cleanup($tree) {
  $new_tree = array();
  foreach ($tree as $item) {
    $temp = array();
    if (!empty($item['link'])) {
      $temp['link'] = $item['link'];
      if (!empty($item['below'])) {
        $temp['below'] = i18n_book_navigation_cleanup($item['below']);
      }
      else {
        $temp['below'] = NULL;
      }
      $new_tree[] = $temp;
    }
  }
  return $new_tree;
}

/**
 * Implements hook_preprocess_i18n_book_navigation().
 *
 * Very similar to the book_navigation, the i18n_book_navigation uses the same
 * template file and template variables. These variables will be translated and
 * the urls adapted.
 * @see template_preprocess_book_navigation()
 */
function i18n_book_navigation_preprocess_i18n_book_navigation(&$variables) {

  // Get the current node.
  // Comment reply page.
  if (arg(0) == 'comment' && arg(1) == 'reply' && is_numeric(arg(2))) {

    // The current node is in arg 2...
    $tnode = menu_get_object('node', 2);
  }
  else {
    $tnode = menu_get_object();
  }

  // Get the translation.
  $node = i18n_book_navigation_get_original_node($tnode);

  // We MUST call the menu_tree_all_data() function BEFORE the book module does.
  // This is ok for performance, as the result is stored in a static cache, so
  // the tree will not get build all over again later on.
  i18n_book_navigation($node);

  // Provide the original book link.
  $variables['book_link'] = $node->book;

  // Call the book module preprocess function.
  template_preprocess_book_navigation($variables);

  // Translate title and book url.
  $query = db_select('node_revision', 'v');
  $query
    ->leftJoin('node', 'n', 'n.vid = v.vid');
  $data = $query
    ->fields('v', array(
    'nid',
    'title',
  ))
    ->condition('n.tnid', $variables['book_id'])
    ->condition('n.language', i18n_langcode())
    ->execute()
    ->fetchAssoc();
  $variables['book_title'] = check_plain($data['title']);
  $variables['book_url'] = 'node/' . $data['nid'];

  // Translate the sub tree.
  if (strlen($variables['tree'])) {
    $variables['tree'] = i18n_book_navigation_children($node->book);
  }

  // Translate the book links.
  if ($variables['has_links']) {

    // Translate the "prev" link.
    if (!empty($variables['prev_url'])) {
      if ($link = i18n_book_navigation_prev($variables['book_link'])) {
        $href = url($link['href']);

        // This *should* override the default link, as it comes afterwards.
        drupal_add_html_head_link(array(
          'rel' => 'prev',
          'href' => $href,
        ));
        $variables['prev_url'] = $href;
        $variables['prev_title'] = check_plain($link['title']);
      }
      else {

        // Unset the variables.
        unset($variables['prev_url'], $variables['prev_title']);
      }
    }

    // Translate the "up" link.
    if (!empty($variables['parent_url'])) {
      if ($link = i18n_book_navigation_link_load($variables['book_link']['plid'])) {
        $href = url($link['href']);

        // This *should* override the default link, as it comes afterwards.
        drupal_add_html_head_link(array(
          'rel' => 'up',
          'href' => $href,
        ));
        $variables['parent_url'] = $href;
        $variables['parent_title'] = check_plain($link['title']);
      }
      else {
        unset($variables['parent_url'], $variables['parent_title']);
      }
    }

    // Translate the "next" link.
    if (!empty($variables['next_url'])) {
      if ($link = i18n_book_navigation_next($variables['book_link'])) {
        $href = url($link['href']);

        // This *should* override the default link, as it comes afterwards.
        drupal_add_html_head_link(array(
          'rel' => 'next',
          'href' => $href,
        ));
        $variables['next_url'] = $href;
        $variables['next_title'] = check_plain($link['title']);
      }
      else {
        unset($variables['next_url'], $variables['next_title']);
      }
    }
    $variables['has_links'] = FALSE;

    // Link variables to filter for values and set state of the flag variable.
    $links = array(
      'prev_url',
      'prev_title',
      'parent_url',
      'parent_title',
      'next_url',
      'next_title',
    );
    foreach ($links as $link) {
      if (isset($variables[$link])) {

        // Flag when there is a value.
        $variables['has_links'] = TRUE;
      }
      else {

        // Set empty to prevent notices.
        $variables[$link] = '';
      }
    }
  }
}

/**
 * Load the direct child elements.
 *
 * Port from book_children(). Will perform a similar task, but will also
 * translate the links.
 * @see book_children()
 *
 * @param array $book_link
 *   The link data
 *
 * @return string
 *   The child elements as HTML. If no child elements were found, the string
 *   will be empty
 */
function i18n_book_navigation_children($book_link) {
  $flat = book_get_flat_menu($book_link);
  $children = array();
  if ($book_link['has_children']) {

    // Walk through the array until we find the current page.
    do {
      $link = array_shift($flat);
    } while ($link && $link['mlid'] != $book_link['mlid']);

    // Continue though the array and collect the links whose parent is this
    // page.
    while (($link = array_shift($flat)) && $link['plid'] == $book_link['mlid']) {
      $data['link'] = $link;
      $data['below'] = '';
      $children[] = $data;
    }
  }

  // Translate the tree.
  $children = i18n_book_navigation_translate_tree($children);

  // Clean it up.
  $children = i18n_book_navigation_cleanup($children);
  $tree = menu_tree_output($children);

  // Render it.
  return $children ? drupal_render($tree) : '';
}

/**
 * Load the previous link in the book.
 *
 * Port from book_prev(). Will translate the link after loading it.
 * @see book_prev()
 *
 * @param array $book_link
 *   The link data.
 *
 * @return array|false
 *   The loaded link or false if no link was found.
 */
function i18n_book_navigation_prev($book_link) {
  $tree = array(
    array(
      'link' => book_prev($book_link),
      'below' => FALSE,
    ),
  );
  $tree = i18n_book_navigation_translate_tree($tree);
  return isset($tree[0]['link']) ? $tree[0]['link'] : FALSE;
}

/**
 * Load the next link in the book.
 *
 * Port from book_next(). Will translate the link after loading it.
 * @see book_nex()
 *
 * @param array $book_link
 *   The link data.
 *
 * @return array|false
 *   The loaded link or false if no link was found.
 */
function i18n_book_navigation_next($book_link) {
  $tree = array(
    array(
      'link' => book_next($book_link),
      'below' => FALSE,
    ),
  );
  $tree = i18n_book_navigation_translate_tree($tree);
  return isset($tree[0]['link']) ? $tree[0]['link'] : FALSE;
}

/**
 * Load a link.
 *
 * Port from book_link_load(). Will translate the link after loading it.
 * @see book_link_load()
 *
 * @param int $mlid
 *   The menu link id
 * @param string $lan
 *   (optional) The desired language. Defaults to the current language.
 *
 * @return array|false
 *   The loaded link or false if no link was found.
 */
function i18n_book_navigation_link_load($mlid, $lan = NULL) {
  $cache =& drupal_static(__FUNCTION__, array());
  if (!$lan) {
    $lan = i18n_langcode();
  }
  if (!is_numeric($mlid)) {
    return FALSE;
  }

  // Do we have a cached version ?
  if (!isset($cache["{$mlid}-{$lan}"])) {
    $tree = array(
      array(
        'link' => book_link_load($mlid),
        'below' => FALSE,
      ),
    );
    $tree = i18n_book_navigation_translate_tree($tree, $lan);
    $cache["{$mlid}-{$lan}"] = isset($tree[0]['link']) ? $tree[0]['link'] : FALSE;
  }
  return $cache["{$mlid}-{$lan}"];
}

/**
 * Get the translated node based on the link path.
 *
 * The returned value depends on the availability of a translation and the i18n
 * settings.
 *
 * @param string $link_path
 *   The link path of the original node
 * @param string $lan
 *   (optional) The desired language. Defaults to the current language.
 *
 * @return object|false
 *   The translated node, or the default node, or false
 */
function i18n_book_navigation_get_translated_node($link_path, $lan = NULL) {
  $cache =& drupal_static(__FUNCTION__, array());
  if (!$lan) {
    $lan = i18n_langcode();
  }
  if (!isset($cache["{$link_path}-{$lan}"])) {
    $i18n_setting = variable_get('i18n_select_nodes', FALSE);
    $tnid = (int) substr($link_path, strlen('node/'));
    if (module_exists('title') && db_table_exists('field_data_title_field')) {

      // Check if we can find a translated title.
      $query = db_select('field_data_title_field', 'fdtf');
      $query
        ->addField('fdtf', 'entity_id', 'nid');
      $query
        ->addField('fdtf', 'title_field_value', 'title');
      $data = $query
        ->condition('fdtf.entity_type', 'node')
        ->condition('fdtf.entity_id', $tnid)
        ->condition('fdtf.language', $lan)
        ->condition('fdtf.delta', 0)
        ->execute()
        ->fetchObject();
      if (isset($data->nid) && !node_access('view', node_load($data->nid))) {
        return FALSE;
      }
    }
    if (empty($data)) {
      $tlanguage = db_select('node', 'n')
        ->fields('n', array(
        'language',
      ))
        ->condition('n.nid', $tnid)
        ->addTag('node_access')
        ->execute()
        ->fetchField();
      $nid = db_select('node', 'n')
        ->fields('n', array(
        'nid',
      ))
        ->condition('n.tnid', $tnid)
        ->condition('n.language', $lan)
        ->execute()
        ->fetchField();

      // We found a translation.
      if ($nid) {
        $data = db_select('node', 'n')
          ->fields('n', array(
          'nid',
          'title',
        ))
          ->condition('n.nid', $nid)
          ->addTag('node_access')
          ->execute()
          ->fetchObject();
      }
      else {

        // Only current language OR no language conditions.
        if ($i18n_setting && $tlanguage == $lan || !$i18n_setting) {
          $data = db_select('node', 'n')
            ->fields('n', array(
            'nid',
            'title',
          ))
            ->condition('n.nid', $tnid)
            ->addTag('node_access')
            ->execute()
            ->fetchObject();
        }
        else {
          $data = db_select('node', 'n')
            ->fields('n', array(
            'nid',
            'title',
          ))
            ->condition('n.nid', $tnid)
            ->condition(db_or()
            ->condition('language', 'und')
            ->condition('language', '')
            ->isNull('language'))
            ->addTag('node_access')
            ->execute()
            ->fetchObject();
        }
      }
    }
    $cache["{$link_path}-{$lan}"] = !empty($data) ? $data : FALSE;
  }
  return $cache["{$link_path}-{$lan}"];
}

/**
 * Get the original node based on the current, translated version.
 *
 * @param object $node
 *   The translated node.
 *
 * @return object
 *   The original node, or an empty object if no translation was found.
 */
function i18n_book_navigation_get_original_node($node) {
  if (!empty($node->nid)) {
    if ($node->tnid == $node->nid || empty($node->tnid)) {
      return $node;
    }
    else {
      return node_load($node->tnid);
    }
  }
  else {
    return new stdClass();
  }
}

/**
 * Implements hook_token_info().
 *
 * Provide tokens similar to the book ones, except these will also be applicable
 * to translated nodes, returning the translated book path.
 * However, depending on the i18n settings, this could have unexpected results
 * and should be used wisely (when translating the book, always start from the
 * top, as to not have "empty" positions in the book path).
 */
function i18n_book_navigation_token_info($type = 'all') {
  $info['tokens']['node']['i18n-book'] = array(
    'name' => t('Translated Book'),
    'description' => t('The book page associated with the node.'),
    'type' => 'menu-link',
  );
  return $info;
}

/**
 * Implements hook_tokens().
 *
 * Port of book_tokens() and part of token_tokens() from the token module. Will
 * translate all the book tokens, and correctly implement the parents-join
 * tokens (menu-link tokens).
 * @see book_tokens()
 * @see token_tokens()
 */
function i18n_book_navigation_tokens($type, $tokens, $data = array(), $options = array()) {
  $replacements = array();
  $sanitize = !empty($options['sanitize']);
  if (isset($options['language'])) {
    $lan = $options['language']->language;
  }
  else {
    $lan = NULL;
  }

  // Node tokens.
  if ($type == 'node' && !empty($data['node'])) {
    $node = i18n_book_navigation_get_original_node($data['node']);
    if (!empty($node->book['mlid'])) {
      $link = i18n_book_navigation_link_load($node->book['mlid'], $lan);
      foreach ($tokens as $name => $original) {
        switch ($name) {
          case 'i18n-book':
            if ($node->nid == $node->book['bid']) {
              $title = $link['title'];
            }
            else {
              $book = node_load($node->book['bid']);
              $root_link = i18n_book_navigation_link_load($book->book['mlid'], $lan);
              $title = $root_link['title'];
            }
            $replacements[$original] = $sanitize ? check_plain($title) : $title;
            break;
        }
      }

      // Chained token relationships.
      if ($book_tokens = token_find_with_prefix($tokens, 'i18n-book')) {
        $replacements += token_generate('i18n-book-menu-link', $book_tokens, array(
          'menu-link' => $link,
        ), $options);
      }
    }
  }
  elseif ($type == 'i18n-book-menu-link' && !empty($data['menu-link'])) {
    $url_options = array(
      'absolute' => TRUE,
    );
    if (isset($options['language'])) {
      $url_options['language'] = $options['language'];
    }
    $link = $data['menu-link'];
    foreach ($tokens as $name => $original) {
      switch ($name) {
        case 'mlid':
          $replacements[$original] = $link['mlid'];
          break;
        case 'title':
          $replacements[$original] = $sanitize ? check_plain($link['title']) : $link['title'];
          break;
        case 'url':
          $replacements[$original] = url($link['href'], $url_options);
          break;
        case 'parent':
          if (!empty($link['plid']) && ($parent = i18n_book_navigation_link_load($link['plid'], $lan))) {
            $replacements[$original] = $sanitize ? check_plain($parent['title']) : $parent['title'];
          }
          break;
        case 'parents':
          if ($parents = i18n_book_navigation_link_load_all_parents($link['mlid'], $lan)) {
            $replacements[$original] = token_render_array($parents, $options);
          }
          break;
        case 'root':
          if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && ($root = i18n_book_navigation_link_load($link['p1'], $lan))) {
            $replacements[$original] = $sanitize ? check_plain($root['title']) : $root['title'];
          }
          break;
      }
    }

    // Chained token relationships.
    if (!empty($link['plid']) && ($source_tokens = token_find_with_prefix($tokens, 'parent')) && ($parent = i18n_book_navigation_link_load($link['plid'], $lan))) {
      $replacements += token_generate('menu-link', $source_tokens, array(
        'menu-link' => $parent,
      ), $options);
    }

    // [menu-link:parents:*] chained tokens.
    if ($parents_tokens = token_find_with_prefix($tokens, 'parents')) {
      if ($parents = i18n_book_navigation_link_load_all_parents($link['mlid'], $lan)) {
        $replacements += token_generate('array', $parents_tokens, array(
          'array' => $parents,
        ), $options);
      }
    }
    if (!empty($link['p1']) && $link['p1'] != $link['mlid'] && ($root_tokens = token_find_with_prefix($tokens, 'root')) && ($root = i18n_book_navigation_link_load($link['p1'], $lan))) {
      $replacements += token_generate('menu-link', $root_tokens, array(
        'menu-link' => $root,
      ), $options);
    }
    if ($url_tokens = token_find_with_prefix($tokens, 'url')) {
      $replacements += token_generate('url', $url_tokens, array(
        'path' => $link['href'],
      ), $options);
    }
  }
  return $replacements;
}

/**
 * Port of token_menu_link_load_all_parents().
 *
 * Loads all parent links, but translates them on the fly.
 * @see token_menu_link_load_all_parents()
 */
function i18n_book_navigation_link_load_all_parents($mlid, $lan = NULL) {
  $cache =& drupal_static(__FUNCTION__, array());
  if (!is_numeric($mlid)) {
    return array();
  }
  if (!$lan) {
    $lan = i18n_langcode();
  }
  if (!isset($cache["{$mlid}-{$lan}"])) {
    $cache["{$mlid}-{$lan}"] = array();
    $plid = db_select('menu_links', 'ml')
      ->fields('ml', array(
      'plid',
    ))
      ->condition('ml.mlid', $mlid)
      ->execute()
      ->fetchField();
    while ($plid && ($parent = i18n_book_navigation_link_load($plid))) {
      $cache["{$mlid}-{$lan}"] = array(
        $plid => $parent['title'],
      ) + $cache["{$mlid}-{$lan}"];
      $plid = $parent['plid'];
    }
  }
  return $cache["{$mlid}-{$lan}"];
}

/**
 * Implements hook_presave_translation().
 *
 * New translated nodes should not be saved with book information as it
 * inteferes with this module.
 *
 * Patch by chaps2.
 */
function i18n_book_navigation_presave_translation(&$node, $nid, $vid, $code) {

  // Check that node is a new node cloned from a source node.
  if (!isset($node->nid) && isset($node->tnid) && isset($node->book)) {
    unset($node->book);
    variable_set('icl_content_books_to_update', array());
  }
}

/**
 * Implements hook_menu_block_tree_alter().
 */
function i18n_book_navigation_menu_block_tree_alter(&$tree, &$config) {
  $tnode = menu_get_object();

  // Get the node in the original language.
  $node = i18n_book_navigation_get_original_node($tnode);
  if (isset($node->language) && $node->language != i18n_langcode() && !empty($node->book) && $config['menu_name'] == $node->book['menu_name']) {
    $tree = i18n_book_navigation($node, $config);
  }
}

/**
 * Implements hook_ctools_plugin_directory().
 *
 * Original patch by joel_osc.
 */
function i18n_book_navigation_ctools_plugin_directory($owner, $plugin_type) {
  if ($owner == 'ctools' && $plugin_type == 'content_types') {
    return 'plugins/content_types';
  }
}

/**
 * Implements hook_ctools_render_alter().
 *
 * Used to invoke i18n_book_navigation_node_view() in case the node_view page is
 * overridden by the Page Manager module. We need this in order to set the
 * correct breadcrumb.
 *
 * Patch by Zekvyrin.
 */
function i18n_book_navigation_ctools_render_alter(&$info, &$page, &$context) {
  $task = $context['task'];
  if ($task['task type'] == 'page' && $task['name'] == 'node_view') {
    $data = array_values($context['contexts']);
    i18n_book_navigation_node_view($data[0]->data, 'full');
  }
}

/**
 * Implements hook_test_group_finished().
 *
 * Our tests have certain conditions, like not running if certain modules are
 * not available. For example, the module does not depend on Menu Block, but
 * does provide logic for it. So, if the Menu Block module is available, some
 * more tests are run to test the implementation. Otherwise, the tests are
 * ignored. It is desirable, however, to notify the user that some tests were
 * not run. That is where this comes into play. If any messages were set inside
 * the i18n_book_navigation_test_messages system variable, they will be output
 * here at the end.
 *
 * The reason we use this "hack" is that messages set using drupal_set_message()
 * during test runs are not kept till the end, as they are stored in session.
 *
 * @see I18nBookNavigationTestCase::setMessage()
 */
function i18n_book_navigation_test_group_finished() {
  $messages = variable_get('i18n_book_navigation_test_messages', array());
  if (!empty($messages)) {
    foreach ($messages as $message) {
      drupal_set_message($message['message'], $message['type']);
    }
  }
  variable_del('i18n_book_navigation_test_messages');
}

Functions

Namesort descending Description
i18n_book_navigation Get the translated menu tree for the original node.
i18n_book_navigation_block_configure Implements hook_block_configure().
i18n_book_navigation_block_info Implements hook_block_info().
i18n_book_navigation_block_save Implements hook_block_save().
i18n_book_navigation_block_view Implements hook_block_view().
i18n_book_navigation_children Load the direct child elements.
i18n_book_navigation_cleanup Clean up the menu tree.
i18n_book_navigation_ctools_plugin_directory Implements hook_ctools_plugin_directory().
i18n_book_navigation_ctools_render_alter Implements hook_ctools_render_alter().
i18n_book_navigation_get_books Get all the available books.
i18n_book_navigation_get_original_node Get the original node based on the current, translated version.
i18n_book_navigation_get_translated_node Get the translated node based on the link path.
i18n_book_navigation_link_load Load a link.
i18n_book_navigation_link_load_all_parents Port of token_menu_link_load_all_parents().
i18n_book_navigation_menu_block_tree_alter Implements hook_menu_block_tree_alter().
i18n_book_navigation_next Load the next link in the book.
i18n_book_navigation_node_view Implements hook_node_view().
i18n_book_navigation_preprocess_i18n_book_navigation Implements hook_preprocess_i18n_book_navigation().
i18n_book_navigation_presave_translation Implements hook_presave_translation().
i18n_book_navigation_prev Load the previous link in the book.
i18n_book_navigation_set_breadcrumb Set the breadcrumb.
i18n_book_navigation_test_group_finished Implements hook_test_group_finished().
i18n_book_navigation_theme Implements hook_theme().
i18n_book_navigation_tokens Implements hook_tokens().
i18n_book_navigation_token_info Implements hook_token_info().
i18n_book_navigation_translate_tree Translate the menu tree.