i18n_book_navigation.module in Book translation 6.2
Same filename and directory in other branches
Defines the i18n book navigation module. Contains all necessary data and hooks
File
i18n_book_navigation.moduleView source
<?php
/**
* @file
* Defines the i18n book navigation module. Contains all necessary data and
* hooks
*/
/**
* Implementation of 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.
*/
function i18n_book_navigation_theme($existing, $type, $theme, $path) {
return array(
'i18n_book_navigation' => array(
'arguments' => array(
'book_link' => NULL,
),
'path' => drupal_get_path('module', 'book'),
'template' => 'book-navigation',
),
);
}
/**
* Sets 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);
}
/**
* Implementation of hook_nodeapi()
*/
function i18n_book_navigation_nodeapi(&$node, $op, $teaser, $page) {
switch ($op) {
case 'view':
// Check that we're not dealing with the original node itself or with a
// non-translated node
if ($node->nid != $node->tnid && $node->tnid != 0 && !$teaser) {
// Get the original node book information
$tnode = (object) array(
'nid' => $node->tnid,
);
$book_info = book_nodeapi($tnode, 'load', $teaser, $page);
// If the original node is part of a book outline
if (!empty($book_info['book']['bid'])) {
$node->content['book_navigation'] = array(
'#value' => theme('i18n_book_navigation', $book_info['book']),
'#weight' => 100,
);
}
}
break;
}
}
/**
* Implementation of hook_block()
*/
function i18n_book_navigation_block($op = 'list', $delta = 0, $edit = array()) {
switch ($op) {
case 'view':
return _i18n_book_navigation_block(menu_get_object());
case 'list':
$block = array(
array(
'info' => t('i18n Book Navigation'),
'cache' => BLOCK_CACHE_PER_PAGE | BLOCK_CACHE_PER_ROLE,
),
);
return $block;
// Configure and save is identical to book_block()
case 'configure':
case 'save':
return book_block($op, $delta, $edit);
}
}
/**
* Creates the block navigation.
* Assembles the navigation for the "current" book outline and translates
* the links into the desired language.
*
* @see book_block() for more information
*
* @param stdClass $tnode
* The current node object.
*
* @return array
* The block array
*/
function _i18n_book_navigation_block($tnode) {
$block = array();
// Get the node in the original language
$node = i18n_book_navigation_get_original_node($tnode);
// If it's the same as the current language, use the default block to save time
if ($node->language == i18n_get_lang()) {
$block = book_block('view');
return $block;
}
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;
$tree[0]['link'] = $book;
$tree[0]['below'] = FALSE;
}
if ($bid == $current_bid) {
// Set the breadcrumb
i18n_book_navigation_set_breadcrumb($tnode, $tree);
}
// Render
$book_menus[$bid] = menu_tree_output($tree);
}
$block['subject'] = t("i18n Book navigation");
$block['content'] = theme('book_all_books_block', $book_menus);
}
elseif ($current_bid) {
// Only display this block when the user is browsing a book.
$title = db_result(db_query(db_rewrite_sql('SELECT n.title FROM {node} n WHERE n.nid = %d'), array(
$node->book['bid'],
)));
// 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', $data['link']);
$block['content'] = $data['below'] ? menu_tree_output($data['below']) : '';
}
}
}
return $block;
}
/**
* Gets the translated menu tree for the original node.
*
* @param stdClass $node
* The node in the original language, that is part of the book outline
*
* @return array
* The translated menu tree, or an empty array
*/
function i18n_book_navigation($node) {
if (!empty($node->book)) {
// Set the i18n node selection mode in the correct language
i18n_selection_mode('node', $node->language);
}
else {
// Nothing we can do
return array();
}
$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_selection_mode('node', i18n_get_lang());
}
return $tree;
}
/**
* Gets all the available books.
* Port from book_get_books(). Needs to deactivate the i18n module in order to
* retreive all books, regardless of the language settings.
* @see book_get_books()
*/
function i18n_book_navigation_get_books() {
module_disable(array(
'i18n',
));
$books = book_get_books();
module_enable(array(
'i18n',
));
return $books;
}
/**
* Translates the 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 = NULL
* (optional) The language to translate the tree in. Defaults to the
* current language
*
* @return array
* The translated menu tree
*/
function i18n_book_navigation_translate_tree($tree, $lan = NULL) {
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 ($item['below']) {
$item['below'] = i18n_book_navigation_translate_tree($item['below'], $lan);
}
}
return $tree;
}
/**
* Cleans up the 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 (isset($item['link'])) {
$temp['link'] = $item['link'];
if ($item['below']) {
$temp['below'] = i18n_book_navigation_cleanup($item['below']);
}
$new_tree[] = $temp;
}
}
return $new_tree;
}
/**
* Preprocesses the 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()
*
* @param array &$variables
* The template variables
*/
function template_preprocess_i18n_book_navigation(&$variables) {
// Get the current node
$tnode = menu_get_object();
// Get the translation
$node = i18n_book_navigation_get_original_node($tnode);
// Set the i18n node selection mode in the correct language
i18n_selection_mode('node', $node->language);
// 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
menu_tree_all_data($node->book['menu_name'], $node->book);
// Revert the i18n node selection mode
i18n_selection_mode('node', i18n_get_lang());
// 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
$data = db_fetch_array(db_query("SELECT v.title, n.nid FROM {node_revisions} v\n LEFT JOIN {node} n ON n.nid = v.nid AND n.vid = v.vid\n WHERE n.tnid = %d AND n.language = '%s' LIMIT 1", array(
$variables['book__id'],
i18n_get_lang(),
)));
$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 ($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_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 ($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_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 ($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_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']);
}
}
// Reset the "has_links" flag if needed
$links = array(
'prev_url',
'prev_title',
'parent_url',
'parent_title',
'next_url',
'next_title',
);
foreach ($links as $link) {
if (isset($variables[$link])) {
$variables['has_links'] = TRUE;
}
else {
$variables[$link] = '';
}
}
}
}
/**
* Loads 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);
// Render it
return $children ? menu_tree_output($children) : '';
}
/**
* Loads 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;
}
/**
* Loads 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;
}
/**
* Loads 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
*
* @return array|false
* The loaded link or false if no link was found
*/
function i18n_book_navigation_link_load($mlid) {
$tree = array(
array(
'link' => book_link_load($mlid),
'below' => FALSE,
),
);
$tree = i18n_book_navigation_translate_tree($tree);
return isset($tree[0]['link']) ? $tree[0]['link'] : FALSE;
}
/**
* Gets 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 = NULL
* (optional) The desired language. Defaults to the current language
*
* @return object|false
* The tranlsated node, or the default node, or false
*/
function i18n_book_navigation_get_translated_node($link_path, $lan = NULL) {
if (!$lan) {
$lan = i18n_get_lang();
}
$i18n_setting = variable_get('i18n_selection_mode', 'simple');
$tnid = (int) substr($link_path, strlen('node/'));
$tlanguage = db_result(db_query("SELECT language FROM {node} WHERE nid = %d", array(
$tnid,
)));
$nid = db_result(db_query("SELECT nid FROM {node} WHERE tnid = %d AND language = '%s'", array(
$tnid,
$lan,
)));
if ($nid) {
if ($i18n_setting == 'simple' || $i18n_setting == 'mixed' || $i18n_setting == 'strict') {
// Current language and language neutral
// OR
// Mixed current language (if available) or default language (if not) and language neutral
// OR
// Only current language
$data = db_fetch_object(db_query("SELECT nid, title FROM {node} WHERE nid = %d", array(
$nid,
)));
}
elseif ($i18n_setting == 'default' || $i18n_setting == 'off') {
// Only default language and language neutral
// OR
// All content. No language conditions apply
$data = db_fetch_object(db_query("SELECT nid, title FROM {node} WHERE nid = %d", array(
$tnid,
)));
}
else {
$data = FALSE;
}
}
else {
if ($i18n_setting == 'simple') {
// Current language and language neutral
if ($tlanguage == i18n_get_lang()) {
$data = db_fetch_object(db_query("SELECT nid, title FROM {node} WHERE nid = %d", array(
$tnid,
)));
}
else {
$data = db_fetch_object(db_query("SELECT nid, title FROM {node} WHERE nid = %d AND (language = '' OR language IS NULL)", array(
$tnid,
)));
}
}
elseif ($i18n_setting == 'mixed' || $i18n_setting == 'default' || $i18n_setting == 'off') {
// Mixed current language (if available) or default language (if not) and language neutral
// OR
// Only default language and language neutral
// OR
// All content. No language conditions apply
$data = db_fetch_object(db_query("SELECT nid, title FROM {node} WHERE nid = %d", array(
$tnid,
)));
}
elseif ($i18n_setting == 'strict') {
// Only current language
if ($tlanguage == $lan) {
$data = db_fetch_object(db_query("SELECT nid, title FROM {node} WHERE nid = %d", array(
$tnid,
)));
}
else {
$data = FALSE;
}
}
else {
$data = FALSE;
}
}
return $data;
}
/**
* Gets 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 ($node->nid) {
if ($node->tnid == $node->nid || $node->tnid == 0) {
return $node;
}
else {
return node_load($node->tnid);
}
}
else {
return new stdClass();
}
}
/**
* Implementation of hook_token_list()
*
* Provide tokens similar to the bookpath ones, except these will also be aplliable
* 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_list($type = 'all') {
$list = array();
if ($type == 'node' || $type == 'all') {
$list['book']['i18n-book'] = t("The title of the node's book parent, translated.");
$list['book']['i18n-book-raw'] = t("The unfiltered title of the node's book parent, translated.");
$list['book']['i18n-bookpath'] = t("The titles of all parents in the node's book hierarchy, translated.");
$list['book']['i18n-bookpath-raw'] = t("The unfiltered titles of all parents in the node's book hierarchy, translated.");
}
return $list;
}
/**
* Implementation of hook_token_values()
*
* Port of book_token_values() from the token module. Will translate all the tokens.
* @see book_token_values().
*/
function i18n_book_navigation_token_values($type, $object = NULL, $options = array()) {
$tokens = array();
if ($type == 'node') {
$tokens['i18n-bookpath'] = '';
$tokens['i18n-bookpath-raw'] = '';
// Get the translation
$node = i18n_book_navigation_get_original_node($object);
if (!empty($node->book['menu_name'])) {
// Set the i18n node selection mode in the correct language
i18n_selection_mode('node', $node->language);
$trail_raw = i18n_book_navigation_menu_titles($node->book, $node->nid, $object->language);
// Revert the i18n node selection mode
i18n_selection_mode('node', i18n_get_lang());
$book_raw = $trail_raw[0];
$book = check_plain($book_raw);
// For book paths, we don't include the current node's title in the trail
array_pop($trail_raw);
$trail = array();
foreach ($trail_raw as $title) {
$trail[] = check_plain($title);
}
$tokens['i18n-book'] = $book;
$tokens['i18n-book-raw'] = $book_raw;
$tokens['i18n-bookpath'] = !empty($options['pathauto']) ? $trail : implode('/', $trail);
$tokens['i18n-bookpath-raw'] = !empty($options['pathauto']) ? $trail_raw : implode('/', $trail_raw);
}
}
return $tokens;
}
/**
* Gets the menu titles for the book path.
* Port of _menu_titles of the token module. Will translate the tree before fetching
* the titles.
* @see _menu_title()
*
* @param array $menu_link
* The current menu link data
* @param int $nid
* The node nid
* @param string $language
* The language of the current node
*
* @return array
* The titles from the book path
*/
function i18n_book_navigation_menu_titles($menu_link, $nid, $language) {
$tree = menu_tree_all_data($menu_link['menu_name'], $menu_link);
// Translate the tree
$tree = i18n_book_navigation_translate_tree($tree, $language);
// Clean it up
$tree = i18n_book_navigation_cleanup($tree);
// Get mlid of all nodes in path - top-most parent to leaf node.
$parents = array();
for ($i = 1; $i < MENU_MAX_DEPTH; $i++) {
if ($menu_link["p{$i}"]) {
$parents[] = $menu_link["p{$i}"];
}
}
// Build the titles in this hierarchy.
$titles = array();
$current = array_shift($tree);
while ($current) {
if (in_array($current['link']['mlid'], $parents)) {
$titles[] = $current['link']['title'];
if ($current['link']['href'] == "node/" . $nid) {
break;
}
// Go deeper in tree hierarchy.
$tree = $current['below'];
}
// Go to next sibling at same level in tree hierarchy.
$current = $tree ? array_shift($tree) : NULL;
}
return $titles;
}
/**
* Implementation of hook_presave_translation() from the Translation Management module.
* 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());
}
}
/**
* Implementation of hook_menu_block_tree_alter() from the Menu Block module.
* A menu block configured for book navigations can now also be translated.
*/
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 ($node->language != i18n_get_lang() && !empty($node->book) && $config['menu_name'] == $node->book['menu_name']) {
$tree = i18n_book_navigation($node);
i18n_book_navigation_set_breadcrumb($tnode, $tree);
}
}
Functions
Name![]() |
Description |
---|---|
i18n_book_navigation | Gets the translated menu tree for the original node. |
i18n_book_navigation_block | Implementation of hook_block() |
i18n_book_navigation_children | Loads the direct child elements. Port from book_children(). Will perform a similar task, but will also translate the links. |
i18n_book_navigation_cleanup | Cleans up the tree. Depending on the translation mode, some leaves my be displayed as "empty". Walks down the tree and unsets all "empty" items. |
i18n_book_navigation_get_books | Gets all the available books. Port from book_get_books(). Needs to deactivate the i18n module in order to retreive all books, regardless of the language settings. |
i18n_book_navigation_get_original_node | Gets the original node based on the current, translated version. |
i18n_book_navigation_get_translated_node | Gets the translated node based on the link path. The returned value depends on the availability of a translation and the i18n settings. |
i18n_book_navigation_link_load | Loads a link. Port from book_link_load(). Will translate the link after loading it. |
i18n_book_navigation_menu_block_tree_alter | Implementation of hook_menu_block_tree_alter() from the Menu Block module. A menu block configured for book navigations can now also be translated. |
i18n_book_navigation_menu_titles | Gets the menu titles for the book path. Port of _menu_titles of the token module. Will translate the tree before fetching the titles. |
i18n_book_navigation_next | Loads the next link in the book. Port from book_next(). Will translate the link after loading it. |
i18n_book_navigation_nodeapi | Implementation of hook_nodeapi() |
i18n_book_navigation_presave_translation | Implementation of hook_presave_translation() from the Translation Management module. New translated nodes should not be saved with book information as it inteferes with this module. |
i18n_book_navigation_prev | Loads the previous link in the book. Port from book_prev(). Will translate the link after loading it. |
i18n_book_navigation_set_breadcrumb | Sets the breadcrumb. This is a port from menu_get_active_breadcrumb(). It will set the breadcrumb based on the current, translated tree. |
i18n_book_navigation_theme | Implementation of hook_theme() |
i18n_book_navigation_token_list | Implementation of hook_token_list() |
i18n_book_navigation_token_values | Implementation of hook_token_values() |
i18n_book_navigation_translate_tree | Translates the tree. Walks down the tree and translates every item. Will respect the translation settings from the i18n module. |
template_preprocess_i18n_book_navigation | Preprocesses the 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. |
_i18n_book_navigation_block | Creates the block navigation. Assembles the navigation for the "current" book outline and translates the links into the desired language. |