i18n_menu.module 32 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947
  1. <?php
  2. /**
  3. * @file
  4. * Internationalization (i18n) submodule: Menu translation.
  5. *
  6. * @author Jose A. Reyero, 2005
  7. *
  8. */
  9. /**
  10. * Implements hook_menu()
  11. */
  12. function i18n_menu_menu() {
  13. $items['admin/structure/menu/manage/translation'] = array(
  14. 'title' => 'Translation sets',
  15. 'page callback' => 'i18n_translation_set_list_manage',
  16. 'page arguments' => array('menu_link'),
  17. 'access arguments' => array('administer menu'),
  18. 'type' => MENU_LOCAL_TASK,
  19. 'weight' => 10,
  20. );
  21. $items['admin/structure/menu/manage/translation/add'] = array(
  22. 'title' => 'Add translation',
  23. 'page callback' => 'drupal_get_form',
  24. 'page arguments' => array('i18n_menu_translation_form'),
  25. 'access arguments' => array('administer menu'),
  26. 'type' => MENU_LOCAL_ACTION,
  27. 'file' => 'i18n_menu.admin.inc',
  28. );
  29. $items['admin/structure/menu/manage/translation/edit/%i18n_menu_translation'] = array(
  30. 'title' => 'Edit translation',
  31. 'page callback' => 'drupal_get_form',
  32. 'page arguments' => array('i18n_menu_translation_form', 6),
  33. 'access arguments' => array('administer menu'),
  34. 'type' => MENU_CALLBACK,
  35. 'file' => 'i18n_menu.admin.inc',
  36. );
  37. $items['admin/structure/menu/manage/translation/delete/%i18n_menu_translation'] = array(
  38. 'title' => 'Delete translation',
  39. 'page callback' => 'drupal_get_form',
  40. 'page arguments' => array('i18n_translation_set_delete_confirm', 6),
  41. 'access arguments' => array('administer menu'),
  42. 'type' => MENU_CALLBACK,
  43. );
  44. return $items;
  45. }
  46. /**
  47. * Implements hook_menu_alter()
  48. */
  49. function i18n_menu_menu_alter(&$items) {
  50. $items['admin/structure/menu/item/%menu_link'] = $items['admin/structure/menu/item/%menu_link/edit'];
  51. $items['admin/structure/menu/item/%menu_link']['type'] = MENU_CALLBACK;
  52. $items['admin/structure/menu/item/%menu_link/edit']['type'] = MENU_DEFAULT_LOCAL_TASK;
  53. $items['admin/structure/menu/manage/%menu']['title callback'] = 'i18n_menu_menu_overview_title';
  54. }
  55. /**
  56. * Preprocess theme_menu_admin_overview to translate menu name and description
  57. *
  58. * @param $variables
  59. */
  60. function i18n_menu_preprocess_menu_admin_overview(&$variables) {
  61. $variables['title'] = i18n_string(array('menu', 'menu', $variables['name'], 'title'), $variables['title'], array('sanitize' => FALSE));
  62. $variables['description'] = i18n_string(array('menu', 'menu', $variables['name'], 'description'), $variables['description'], array('sanitize' => FALSE));
  63. }
  64. /**
  65. * Title callback for the menu overview page and links.
  66. */
  67. function i18n_menu_menu_overview_title($menu) {
  68. return i18n_string(array('menu', 'menu', $menu['menu_name'], 'title'), $menu['title']);
  69. }
  70. /**
  71. * Implements hook_block_view().
  72. */
  73. function i18n_menu_block_view_alter(&$data, $block) {
  74. if (($block->module == 'menu' || $block->module == 'system') && (i18n_menu_mode($block->delta) & I18N_MODE_MULTIPLE)) {
  75. $menus = menu_get_menus();
  76. if (isset($menus[$block->delta])) {
  77. if (empty($block->title)) {
  78. $data['subject'] = i18n_string_plain(
  79. array('menu', 'menu', $block->delta, 'title'),
  80. $menus[$block->delta]
  81. );
  82. }
  83. // Add contextual links for this block.
  84. if (!empty($data['content'])) {
  85. $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($block->delta));
  86. }
  87. }
  88. }
  89. }
  90. /**
  91. * Implements hook_i18n_translate_path()
  92. */
  93. function i18n_menu_i18n_translate_path($path) {
  94. $item = i18n_menu_link_load($path, i18n_langcode());
  95. if ($item && ($set = i18n_translation_object('menu_link', $item))) {
  96. $links = array();
  97. foreach ($set->get_translations() as $lang => $link) {
  98. $links[$lang] = array(
  99. 'href' => $link['link_path'],
  100. 'title' => $link['link_title'],
  101. 'i18n_type' => 'menu_link',
  102. 'i18n_object' => $link,
  103. );
  104. }
  105. return $links;
  106. }
  107. }
  108. /**
  109. * Implements hook_menu_insert()
  110. */
  111. function i18n_menu_menu_insert($menu) {
  112. i18n_menu_menu_update($menu);
  113. }
  114. /**
  115. * Implements hook_menu_update()
  116. */
  117. function i18n_menu_menu_update($menu) {
  118. // Stores the fields of menu links which need an update.
  119. $update = array();
  120. if (!isset($menu['i18n_mode'])) {
  121. $menu['i18n_mode'] = I18N_MODE_NONE;
  122. }
  123. if (!($menu['i18n_mode'] & I18N_MODE_LANGUAGE)) {
  124. $menu['language'] = LANGUAGE_NONE;
  125. }
  126. db_update('menu_custom')
  127. ->fields(array('language' => $menu['language'], 'i18n_mode' => $menu['i18n_mode']))
  128. ->condition('menu_name', $menu['menu_name'])
  129. ->execute();
  130. if (!$menu['i18n_mode']) {
  131. $update['language'] = LANGUAGE_NONE;
  132. }
  133. elseif ($menu['i18n_mode'] & I18N_MODE_LANGUAGE) {
  134. $update['language'] = $menu['language'];
  135. }
  136. // Non translatable menu.
  137. if (!($menu['i18n_mode'] & I18N_MODE_TRANSLATE)) {
  138. $tsids = db_select('menu_links')
  139. ->fields('menu_links', array('i18n_tsid'))
  140. ->groupBy('i18n_tsid')
  141. ->condition('menu_name', $menu['menu_name'])
  142. ->condition('customized', 1)
  143. ->condition('i18n_tsid', 0, '<>')
  144. ->execute()
  145. ->fetchCol(0);
  146. if (!empty($tsids)) {
  147. foreach ($tsids as $tsid) {
  148. if ($translation_set = i18n_translation_set_load($tsid)) {
  149. $translation_set->delete();
  150. }
  151. }
  152. }
  153. $update['i18n_tsid'] = 0;
  154. }
  155. if (!empty($update)) {
  156. db_update('menu_links')
  157. ->fields($update)
  158. ->condition('menu_name', $menu['menu_name'])
  159. ->condition('customized', 1)
  160. ->execute();
  161. }
  162. // Update strings, always add translation if no language
  163. if (!i18n_object_langcode($menu)) {
  164. i18n_string_object_update('menu', $menu);
  165. }
  166. // Clear all menu caches.
  167. menu_cache_clear_all();
  168. }
  169. /**
  170. * Implements hook_menu_delete()
  171. */
  172. function i18n_menu_menu_delete($menu) {
  173. i18n_string_object_remove('menu', $menu);
  174. }
  175. /**
  176. * Implements hook_menu_link_alter().
  177. *
  178. * This function is invoked from menu_link_save() before default
  179. * menu link options (menu_name, module, etc.. have been set)
  180. */
  181. function i18n_menu_menu_link_alter(&$item) {
  182. // We just make sure every link has a valid language property.
  183. if (!i18n_object_langcode($item)) {
  184. $item['language'] = LANGUAGE_NONE;
  185. }
  186. }
  187. /**
  188. * Implements hook_menu_link_insert()
  189. */
  190. function i18n_menu_menu_link_insert($link) {
  191. i18n_menu_menu_link_update($link);
  192. }
  193. /**
  194. * Implements hook_menu_link_update().
  195. */
  196. function i18n_menu_menu_link_update($link) {
  197. // Stores the fields to update.
  198. $fields = array();
  199. $menu_mode = i18n_menu_mode($link['menu_name']);
  200. if ($menu_mode & I18N_MODE_TRANSLATE && isset($link['language'])) {
  201. // Multilingual menu links, translatable, it may be part of a
  202. // translation set.
  203. if (i18n_object_langcode($link)) {
  204. if (!empty($link['translation_set'])) {
  205. // Translation set comes as parameter, we may be creating a translation,
  206. // add link to the set.
  207. $translation_set = $link['translation_set'];
  208. $translation_set
  209. ->add_item($link)
  210. ->save(TRUE);
  211. }
  212. }
  213. elseif ($link['language'] === LANGUAGE_NONE && !empty($link['original_item']['i18n_tsid'])) {
  214. if ($translation_set = i18n_translation_set_load($link['original_item']['i18n_tsid'])) {
  215. $translation_set->remove_language($link['original_item']['language']);
  216. // If there are no links left in this translation set, delete the set.
  217. // Otherwise update the set.
  218. $translation_set->update_delete();
  219. }
  220. $fields['i18n_tsid'] = 0;
  221. }
  222. }
  223. // For multilingual menu items, always set a language and mark them for
  224. // 'alter' so they can be processed later by
  225. // hook_translated_link_menu_alter().
  226. if ($menu_mode) {
  227. if (!isset($link['language'])) {
  228. $link['language'] = LANGUAGE_NONE;
  229. }
  230. if (_i18n_menu_link_check_alter($link) && empty($link['options']['alter'])) {
  231. $fields['options'] = $link['options'];
  232. $fields['options']['alter'] = TRUE;
  233. }
  234. // We cannot unmark links for altering because we don't know what other
  235. // modules use it for.
  236. }
  237. // Update language field if the link has a language value.
  238. if (isset($link['language'])) {
  239. $fields['language'] = $link['language'];
  240. }
  241. if (!empty($fields)) {
  242. // If link options are to be updated, they need to be serialized.
  243. if (isset($fields['options'])) {
  244. $fields['options'] = serialize($fields['options']);
  245. }
  246. db_update('menu_links')
  247. ->fields($fields)
  248. ->condition('mlid', $link['mlid'])
  249. ->execute();
  250. }
  251. // Update translatable strings if any for customized links that belong to a
  252. // localizable menu.
  253. if (_i18n_menu_link_is_localizable($link)) {
  254. i18n_string_object_update('menu_link', $link);
  255. }
  256. else {
  257. i18n_string_object_remove('menu_link', $link);
  258. }
  259. }
  260. /**
  261. * Implements hook_menu_delete()
  262. */
  263. function i18n_menu_menu_link_delete($link) {
  264. // If a translation set exists for this link, remove this link from the set.
  265. if (!empty($link['i18n_tsid'])) {
  266. if ($translation_set = i18n_translation_set_load($link['i18n_tsid'])) {
  267. $translation_set->get_translations();
  268. $translation_set->remove_language($link['language']);
  269. // If there are no links left in this translation set, delete the set.
  270. // Otherwise update the set.
  271. $translation_set->update_delete();
  272. }
  273. }
  274. i18n_string_object_remove('menu_link', $link);
  275. }
  276. /**
  277. * Get menu mode or compare with given one
  278. */
  279. function i18n_menu_mode($name, $mode = NULL) {
  280. $menu = menu_load($name);
  281. if (!$menu || !isset($menu['i18n_mode'])) {
  282. return isset($mode) ? FALSE : I18N_MODE_NONE;
  283. }
  284. else {
  285. return isset($mode) ? $menu['i18n_mode'] & $mode : $menu['i18n_mode'];
  286. }
  287. }
  288. /**
  289. * Implements hook_translated_menu_link_alter().
  290. *
  291. * Translate localizable menu links on the fly.
  292. * Filter out items that have a different language from current interface.
  293. *
  294. * @see i18n_menu_menu_link_alter()
  295. */
  296. function i18n_menu_translated_menu_link_alter(&$item) {
  297. // Only process links to be displayed not processed before by i18n_menu.
  298. if (_i18n_menu_link_process($item)) {
  299. if (!_i18n_menu_link_is_visible($item)) {
  300. $item['hidden'] = TRUE;
  301. }
  302. elseif (_i18n_menu_link_is_localizable($item)) {
  303. // Item has undefined language, it is a candidate for localization.
  304. _i18n_menu_link_localize($item);
  305. }
  306. }
  307. }
  308. /**
  309. * Implements hook_help().
  310. */
  311. function i18n_menu_help($path, $arg) {
  312. switch ($path) {
  313. case 'admin/help#i18n_menu' :
  314. $output = '<p>' . t('This module adds support for multilingual menus. You can setup multilingual options for each menu:') . '</p>';
  315. $output .= '<ul>';
  316. $output .= '<li>' . t('Menus can be fully multilingual with translatable (or localized) menu items.') . '</li>';
  317. $output .= '<li>' . t('Menus can be configured to have a fixed language. All menu items share this language setting and the menu will be visible in that language only.') . '</li>';
  318. $output .= '<li>' . t('Menus can also be configured to have no translations.') . '</li>';
  319. $output .= '</ul>';
  320. $output .= '<p>' . t('The multilingual options of a menu must be configured before individual menu items can be translated. Go to the <a href="@menu-admin">Menus administration page</a> and follow the "edit menu" link to the menu in question.', array('@menu-admin' => url('admin/structure/menu') ) ) . '</p>';
  321. $output .= '<p>' . t('To search and translate strings, use the <a href="@translate-interface">translation interface</a> pages.', array('@translate-interface' => url('admin/config/regional/translate'))) . '</p>';
  322. return $output;
  323. case 'admin/config/regional/i18n':
  324. $output = '<p>' . t('Menus and menu items can be translated on the <a href="@configure_menus">Menu administration page</a>.', array('@configure_menus' => url('admin/structure/menu'))) . '</p>';
  325. return $output;
  326. }
  327. }
  328. /**
  329. * Implements hook_variable_info_alter()
  330. */
  331. function i18n_menu_variable_info_alter(&$variables, $options) {
  332. // Make menu variables translatable
  333. $variables['menu_main_links_source']['localize'] = TRUE;
  334. $variables['menu_secondary_links_source']['localize'] = TRUE;
  335. $variables['menu_parent_[node_type]']['localize'] = TRUE;
  336. $variables['menu_options_[node_type]']['localize'] = TRUE;
  337. }
  338. /**
  339. * Get localized menu tree.
  340. *
  341. * @param string $menu_name
  342. * The menu the translated tree has to be fetched from.
  343. * @param string $langcode
  344. * Optional language code to get the menu in, defaults to request language.
  345. * @param bool $reset
  346. * Whether to reset the internal i18n_menu_translated_tree cache.
  347. */
  348. function i18n_menu_translated_tree($menu_name, $langcode = NULL, $reset = FALSE) {
  349. $menu_output = &drupal_static(__FUNCTION__);
  350. $langcode = $langcode ? $langcode : i18n_language_interface()->language;
  351. if (!isset($menu_output[$langcode][$menu_name]) || $reset) {
  352. $tree = menu_tree_page_data($menu_name);
  353. $tree = i18n_menu_localize_tree($tree, $langcode);
  354. $menu_output[$langcode][$menu_name] = menu_tree_output($tree);
  355. }
  356. return $menu_output[$langcode][$menu_name];
  357. }
  358. /**
  359. * Localize menu tree.
  360. */
  361. function i18n_menu_localize_tree($tree, $langcode = NULL) {
  362. $langcode = $langcode ? $langcode : i18n_language_interface()->language;
  363. foreach ($tree as $index => &$item) {
  364. $link = $item['link'];
  365. // We only process links that are visible and not processed before.
  366. if (_i18n_menu_link_process($item['link'])) {
  367. if (!_i18n_menu_link_is_visible($item['link'], $langcode)) {
  368. // Remove links for other languages than current.
  369. // Links with language won't be localized.
  370. unset($tree[$index]);
  371. // @todo Research whether the above has any advantage over:
  372. // $item['hidden'] = TRUE;
  373. }
  374. else {
  375. if (_i18n_menu_link_is_localizable($item['link'])) {
  376. // Item has undefined language, it is a candidate for localization.
  377. _i18n_menu_link_localize($item['link'], $langcode);
  378. }
  379. // Localize subtree.
  380. if (!empty($item['below'])) {
  381. $item['below'] = i18n_menu_localize_tree($item['below'], $langcode);
  382. }
  383. }
  384. }
  385. }
  386. return $tree;
  387. }
  388. /**
  389. * Localize menu renderable array
  390. */
  391. function i18n_menu_localize_elements(&$elements) {
  392. foreach (element_children($elements) as $mlid) {
  393. $elements[$mlid]['#title'] = i18n_string(array('menu', 'item', $mlid, 'title'), $elements[$mlid]['#title']);
  394. if (!empty($tree[$mlid]['#localized_options']['attributes']['title'])) {
  395. $elements[$mlid]['#localized_options']['attributes']['title'] = i18n_string(array('menu', 'item', $mlid, 'description'), $tree[$mlid]['#localized_options']['attributes']['title']);
  396. }
  397. i18n_menu_localize_elements($elements[$mlid]);
  398. }
  399. }
  400. /**
  401. * Return an array of localized links for a navigation menu.
  402. *
  403. * Localized version of menu_navigation_links()
  404. */
  405. function i18n_menu_navigation_links($menu_name, $level = 0) {
  406. // Don't even bother querying the menu table if no menu is specified.
  407. if (empty($menu_name)) {
  408. return array();
  409. }
  410. // Get the menu hierarchy for the current page.
  411. $tree = menu_tree_page_data($menu_name, $level + 1);
  412. $tree = i18n_menu_localize_tree($tree);
  413. // Go down the active trail until the right level is reached.
  414. while ($level-- > 0 && $tree) {
  415. // Loop through the current level's items until we find one that is in trail.
  416. while ($item = array_shift($tree)) {
  417. if ($item['link']['in_active_trail']) {
  418. // If the item is in the active trail, we continue in the subtree.
  419. $tree = empty($item['below']) ? array() : $item['below'];
  420. break;
  421. }
  422. }
  423. }
  424. // Create a single level of links.
  425. $router_item = menu_get_item();
  426. $links = array();
  427. foreach ($tree as $item) {
  428. if (!$item['link']['hidden']) {
  429. $class = '';
  430. $l = $item['link']['localized_options'];
  431. $l['href'] = $item['link']['href'];
  432. $l['title'] = $item['link']['title'];
  433. if ($item['link']['in_active_trail']) {
  434. $class = ' active-trail';
  435. $l['attributes']['class'][] = 'active-trail';
  436. }
  437. // Normally, l() compares the href of every link with $_GET['q'] and sets
  438. // the active class accordingly. But local tasks do not appear in menu
  439. // trees, so if the current path is a local task, and this link is its
  440. // tab root, then we have to set the class manually.
  441. if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) {
  442. $l['attributes']['class'][] = 'active';
  443. }
  444. // Keyed with the unique mlid to generate classes in theme_links().
  445. $links['menu-' . $item['link']['mlid'] . $class] = $l;
  446. }
  447. }
  448. return $links;
  449. }
  450. /**
  451. * Get localized menu title
  452. */
  453. function _i18n_menu_link_title($link, $langcode = NULL) {
  454. $key = i18n_object_info('menu_link', 'key');
  455. return i18n_string_translate(array('menu', 'item', $link[$key], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
  456. }
  457. /**
  458. * Localize menu item title and description.
  459. *
  460. * This will be invoked always after _menu_item_localize()
  461. *
  462. * Link properties to manage:
  463. * - title, menu router title
  464. * - link_title, menu link title
  465. * - options.attributes.title, menu link description.
  466. * - localized_options.attributes.title,
  467. *
  468. * @see _menu_item_localize()
  469. * @see _menu_link_translate()
  470. */
  471. function _i18n_menu_link_localize(&$link, $langcode = NULL) {
  472. // Only translate title if it has no special callback.
  473. if (empty($link['title callback']) || $link['title callback'] === 't') {
  474. $link['title'] = _i18n_menu_link_title($link, $langcode);
  475. }
  476. if ($description = _i18n_menu_link_description($link, $langcode)) {
  477. $link['localized_options']['attributes']['title'] = $description;
  478. }
  479. }
  480. /**
  481. * Get localized menu description
  482. */
  483. function _i18n_menu_link_description($link, $langcode = NULL) {
  484. if (!empty($link['options']['attributes']['title'])) {
  485. $key = i18n_object_info('menu_link', 'key');
  486. return i18n_string_translate(array('menu', 'item', $link[$key], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode, 'sanitize' => FALSE));
  487. }
  488. else {
  489. return NULL;
  490. }
  491. }
  492. /**
  493. * Check whether this link is to be processed by i18n_menu and start processing.
  494. */
  495. function _i18n_menu_link_process(&$link) {
  496. // Only links that have a language property and haven't been processed before.
  497. // We also translate links marked as hidden because core breadcrumbs ignore
  498. // that flag and excluding them would basically interfere with core behaviour.
  499. // We also check that they belong to a menu with language options.
  500. if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && i18n_menu_mode($link['menu_name'])) {
  501. // Mark so it won't be processed twice.
  502. $link['i18n_menu'] = TRUE;
  503. // Skip if administering this menu or this menu item.
  504. if (arg(0) == 'admin' && arg(1) == 'structure' && arg(2) == 'menu') {
  505. if (arg(3) == 'manage' && $link['menu_name'] == arg(4)) {
  506. return FALSE;
  507. }
  508. elseif (arg(3) == 'item' && arg(4) == $link['mlid']) {
  509. return FALSE;
  510. }
  511. }
  512. // Skip if administering this menu item through the node edit form.
  513. elseif (arg(0) == 'node' && arg(2) == 'edit' && $link['link_path'] == arg(0) . '/' . arg(1)) {
  514. return FALSE;
  515. }
  516. return TRUE;
  517. }
  518. else {
  519. return FALSE;
  520. }
  521. }
  522. /**
  523. * Check whether this menu item should be marked for altering.
  524. *
  525. * Menu items that have a language or that have any localizable strings
  526. * will be marked to be run through hook_translated_menu_link_alter().
  527. *
  528. * @see i18n_menu_translated_menu_link_alter()
  529. */
  530. function _i18n_menu_link_check_alter($link) {
  531. return i18n_menu_mode($link['menu_name']) && (i18n_object_langcode($link) || _i18n_menu_link_is_localizable($link, TRUE));
  532. }
  533. /**
  534. * Check whether this link should be localized by i18n_menu.
  535. *
  536. * @param array $link
  537. * Menu link array.
  538. * @param bool $check_strings
  539. * Whether to check if the link has actually localizable strings. Since this
  540. * is a more expensive operation, it will be just checked when editing menu
  541. * items.
  542. *
  543. * @return boolean
  544. * Returns TRUE if link is localizable.
  545. */
  546. function _i18n_menu_link_is_localizable($link, $check_strings = FALSE) {
  547. return !empty($link['customized']) && !i18n_object_langcode($link) && i18n_menu_mode($link['menu_name'], I18N_MODE_LOCALIZE) &&
  548. (!$check_strings || _i18n_menu_link_localizable_properties($link));
  549. }
  550. /**
  551. * Check whether this menu link is visible for current/given language.
  552. */
  553. function _i18n_menu_link_is_visible($link, $langcode = NULL) {
  554. $langcode = $langcode ? $langcode : i18n_language_interface()->language;
  555. return $link['language'] == LANGUAGE_NONE || $link['language'] == $langcode;
  556. }
  557. /**
  558. * Get localizable properties for menu link checking against the router item.
  559. */
  560. function _i18n_menu_link_localizable_properties($link) {
  561. $props = array();
  562. $router = !empty($link['router_path']) ? _i18n_menu_get_router($link['router_path']) : NULL;
  563. if (!empty($link['link_title'])) {
  564. // If the title callback is 't' and the link title matches the router title
  565. // it will be localized by core, not by i18n_menu.
  566. if (!$router ||
  567. (empty($router['title_callback']) || ($router['title_callback'] != 't' || !empty($link['customized']))) ||
  568. (empty($router['title']) || $router['title'] != $link['link_title'])
  569. ) {
  570. $props[] = 'title';
  571. }
  572. }
  573. if (!empty($link['options']['attributes']['title'])) {
  574. // If the description matches the router description, it will be localized
  575. // by core.
  576. if (!$router || empty($router['description']) || ($router['description'] != $link['options']['attributes']['title']) || !empty($link['customized'])) {
  577. $props[] = 'description';
  578. }
  579. }
  580. return $props;
  581. }
  582. /**
  583. * Get the menu router for this router path.
  584. *
  585. * We need the untranslated title to compare, and this will be fast.
  586. * There's no api function to do this?
  587. *
  588. * @param string $path
  589. * The path to fetch from the router.
  590. */
  591. function _i18n_menu_get_router($path) {
  592. $cache = &drupal_static(__FUNCTION__, array());
  593. if (!array_key_exists($path, $cache)) {
  594. $cache[$path] = db_select('menu_router', 'mr')
  595. ->fields('mr', array('title', 'title_callback', 'description'))
  596. ->condition('path', $path)
  597. ->execute()
  598. ->fetchAssoc();
  599. }
  600. return $cache[$path];
  601. }
  602. /**
  603. * Implements hook_form_FORM_ID_alter().
  604. */
  605. function i18n_menu_form_menu_edit_menu_alter(&$form, &$form_state) {
  606. $menu = menu_load($form['old_name']['#value']);
  607. $i18n_mode = $menu && isset($menu['i18n_mode']) ? $menu['i18n_mode'] : I18N_MODE_NONE;
  608. $langcode = $menu && isset($menu['language']) ? $menu['language'] : LANGUAGE_NONE;
  609. $form += i18n_translation_mode_element('menu', $i18n_mode, $langcode);
  610. }
  611. /**
  612. * Implements hook_form_FORM_ID_alter().
  613. *
  614. * Add a language selector to the menu_edit_item form and register a submit
  615. * callback to process items.
  616. */
  617. function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
  618. $item = &$form['original_item']['#value'];
  619. $item['language'] = i18n_menu_item_get_language($item);
  620. // Check whether this item belongs to a node object and it is a supported type.
  621. $node_item = ($node = i18n_menu_item_get_node($item)) && i18n_menu_node_supported_type($node->type);
  622. if (!$node_item && i18n_menu_mode($item['menu_name'], I18N_MODE_TRANSLATE)) {
  623. //$form['i18n'] = array('#type' => 'fieldset');
  624. $form['i18n']['language'] = array(
  625. '#description' => t('This item belongs to a multilingual menu. You can set a language for it.'),
  626. ) + i18n_element_language_select($item);
  627. // If the term to be added will be a translation of a source term,
  628. // set the default value of the option list to the target language and
  629. // create a form element for storing the translation set of the source term.
  630. if (isset($_GET['translation']) && isset($_GET['target']) && ($source_item = menu_link_load($_GET['translation']))) {
  631. if (!empty($source_item['i18n_tsid'])) {
  632. $translation_set = i18n_translation_set_load($source_item['i18n_tsid']);
  633. }
  634. else {
  635. // Create object and stick the source information in the translation set.
  636. $translation_set = i18n_translation_set_build('menu_link')
  637. ->add_item($source_item);
  638. }
  639. $form['link_path']['#default_value'] = $source_item['link_path'];
  640. // Maybe we should disable the 'link_path' and 'parent' form elements?
  641. // $form['link_path']['#disabled'] = TRUE;
  642. // $form['parent']['#disabled'] = TRUE;
  643. $form['i18n']['language']['#default_value'] = $_GET['target'];
  644. $form['i18n']['language']['#disabled'] = TRUE;
  645. drupal_set_title(t('%language translation of menu item %title', array('%language' => locale_language_name($_GET['target']), '%title' => $source_item['link_title'])), PASS_THROUGH);
  646. }
  647. elseif (!empty($item['i18n_tsid'])) {
  648. $translation_set = i18n_translation_set_load($item['i18n_tsid']);
  649. }
  650. // Add the translation set to the form so we know the new menu item
  651. // needs to be added to that set.
  652. if (!empty($translation_set)) {
  653. $form['translation_set'] = array(
  654. '#type' => 'value',
  655. '#value' => $translation_set,
  656. );
  657. // If the current term is part of a translation set,
  658. // remove all other languages of the option list.
  659. if ($translations = $translation_set->get_translations()) {
  660. unset($form['i18n']['language']['#options'][LANGUAGE_NONE]);
  661. foreach ($translations as $langcode => $translation) {
  662. if ($translation['mlid'] !== $item['mlid']) {
  663. unset($form['i18n']['language']['#options'][$langcode]);
  664. }
  665. }
  666. }
  667. }
  668. }
  669. else {
  670. $form['language'] = array(
  671. '#type' => 'value',
  672. '#value' => $item['language'],
  673. );
  674. }
  675. if ($node_item && i18n_langcode($item['language'])) {
  676. $form['i18n']['message'] = array(
  677. '#type' => 'item',
  678. '#title' => t('Language'),
  679. '#markup' => i18n_language_name($item['language']),
  680. '#description' => t('This menu item belongs to a node, so it will have the same language as the node and cannot be localized.'),
  681. );
  682. }
  683. array_unshift($form['#validate'], 'i18n_menu_menu_item_prepare_normal_path');
  684. }
  685. /**
  686. * Implements hook_form_FORM_ID_alter().
  687. * FORM_ID = menu-overview-form.
  688. * Add a "translate" link in operations column for each menu item.
  689. */
  690. function i18n_menu_form_menu_overview_form_alter(&$form, &$form_state) {
  691. if (i18n_menu_mode($form['#menu']['menu_name'], I18N_MODE_MULTIPLE)) {
  692. foreach (element_children($form) as $element) {
  693. if (substr($element, 0, 5) == 'mlid:') {
  694. $item = $form[$element]["#item"];
  695. $mlid = $form[$element]['#item']['mlid'];
  696. if (i18n_get_object('menu', $mlid)->get_translate_access()) {
  697. $form[$element]['operations']['translate'] = array(
  698. '#type' => 'link',
  699. '#title' => t('translate'),
  700. '#href' => "admin/structure/menu/item/{$mlid}/translate",
  701. );
  702. $form[$element]['title']['#markup'] = l(_i18n_menu_link_title($item), $item['href'], $item['localized_options']);
  703. }
  704. }
  705. }
  706. }
  707. }
  708. /**
  709. * Normal path should be checked with menu item's language to avoid
  710. * troubles when a node and it's translation has the same url alias.
  711. */
  712. function i18n_menu_menu_item_prepare_normal_path($form, &$form_state) {
  713. $item = &$form_state['values'];
  714. $item['link_path'] = i18n_prepare_normal_path($item['link_path'], $item['language']);
  715. }
  716. /**
  717. * Get language for menu item
  718. */
  719. function i18n_menu_item_get_language($item) {
  720. if (isset($item['language'])) {
  721. return $item['language'];
  722. }
  723. else {
  724. $menu = menu_load($item['menu_name']);
  725. if (!isset($menu['i18n_mode'])) {
  726. return LANGUAGE_NONE;
  727. }
  728. switch ($menu['i18n_mode']) {
  729. case I18N_MODE_LANGUAGE:
  730. return $menu['language'];
  731. case I18N_MODE_NONE:
  732. case I18N_MODE_LOCALIZE:
  733. return LANGUAGE_NONE;
  734. default:
  735. if (!empty($item['mlid'])) {
  736. return db_select('menu_links', 'm')
  737. ->fields('m', array('language'))
  738. ->condition('mlid', $item['mlid'])
  739. ->execute()
  740. ->fetchField();
  741. }
  742. else {
  743. return LANGUAGE_NONE;
  744. }
  745. }
  746. }
  747. }
  748. /**
  749. * Implements hook_form_node_form_alter().
  750. *
  751. * Add language to menu settings of the node form, as well as setting defaults
  752. * to match the translated item's menu settings.
  753. */
  754. function i18n_menu_form_node_form_alter(&$form, &$form_state, $form_id) {
  755. if (isset($form['menu'])) {
  756. $node = $form['#node'];
  757. $link = $node->menu;
  758. if (!empty($link['mlid'])) {
  759. // Preserve the menu item language whatever it is.
  760. $form['menu']['link']['language'] = array('#type' => 'value', '#value' => $link['language']);
  761. }
  762. elseif (i18n_menu_node_supported_type($node->type)) {
  763. // Set menu language to node language but only if it is a supported node type.
  764. $form['menu']['link']['language'] = array('#type' => 'value', '#value' => $node->language);
  765. }
  766. else {
  767. $form['menu']['link']['language'] = array('#type' => 'value', '#value' => LANGUAGE_NONE);
  768. }
  769. // Customized must be set to 1 to save language.
  770. $form['menu']['link']['customized'] = array('#type' => 'value', '#value' => 1);
  771. }
  772. }
  773. /**
  774. * Check whether a node type has multilingual support (but not entity translation).
  775. */
  776. function i18n_menu_node_supported_type($type) {
  777. $supported = &drupal_static(__FUNCTION__);
  778. if (!isset($supported[$type])) {
  779. $mode = variable_get('language_content_type_' . $type, 0);
  780. $supported[$type] = $mode == 1 || $mode == 2; // 2 == TRANSLATION_ENABLED
  781. }
  782. return $supported[$type];
  783. }
  784. /**
  785. * Get the node object for a menu item.
  786. */
  787. function i18n_menu_item_get_node($item) {
  788. return isset($item['router_path']) && $item['router_path'] == 'node/%' ? node_load(arg(1, $item['link_path'])) : NULL;
  789. }
  790. /**
  791. * Implements hook_node_presave()
  792. *
  793. * Set menu link language to node language
  794. */
  795. function i18n_menu_node_presave($node) {
  796. if (!empty($node->menu) && isset($node->language) && i18n_menu_node_supported_type($node->type)) {
  797. $node->menu['language'] = i18n_object_langcode($node, LANGUAGE_NONE);
  798. // Store node type with menu item so we can quickly access it later.
  799. $node->menu['options']['node_type'] = $node->type;
  800. }
  801. }
  802. /**
  803. * Implements hook_node_prepare_translation().
  804. */
  805. function i18n_menu_node_prepare_translation($node) {
  806. if (empty($node->menu['mlid']) && !empty($node->translation_source)) {
  807. $tnode = $node->translation_source;
  808. // Prepare the tnode so the menu item will be available.
  809. node_object_prepare($tnode);
  810. $node->menu['link_title'] = $tnode->menu['link_title'];
  811. $node->menu['weight'] = $tnode->menu['weight'];
  812. }
  813. }
  814. /**
  815. * Process menu and menu item add/edit form submissions.
  816. *
  817. * @todo See where this fits
  818. */
  819. /*
  820. function i18n_menu_edit_item_form_submit($form, &$form_state) {
  821. $mid = menu_edit_item_save($form_state['values']);
  822. db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", array($form_state['values']['language'], $mid));
  823. return 'admin/build/menu';
  824. }
  825. */
  826. /**
  827. * Load translation set. Menu loading callback.
  828. */
  829. function i18n_menu_translation_load($tsid) {
  830. return i18n_translation_set_load($tsid, 'menu_link');
  831. }
  832. /**
  833. * Load menu item by path, language
  834. */
  835. function i18n_menu_link_load($path, $langcode) {
  836. $query = db_select('menu_links', 'ml');
  837. $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
  838. $query->fields('ml');
  839. // Weight should be taken from {menu_links}, not {menu_router}.
  840. $query->addField('ml', 'weight', 'link_weight');
  841. $query->fields('m');
  842. $query->condition('ml.link_path', $path);
  843. $query->condition('ml.language', $langcode);
  844. if ($item = $query->execute()->fetchAssoc()) {
  845. $item['weight'] = $item['link_weight'];
  846. _menu_link_translate($item);
  847. return $item;
  848. }
  849. }
  850. /**
  851. * Implements hook_query_TAG_alter() for features_menu_links.
  852. * Add needed fields to properly serialize localization information.
  853. */
  854. function i18n_menu_query_features_menu_link_alter($query) {
  855. $query->fields('menu_links', array('language', 'customized'));
  856. }
  857. /**
  858. * Implements hook_query_TAG_alter()
  859. *
  860. * Using tag 'preferred_menu_links' added in menu_link_get_preferred().
  861. * See http://drupal.org/node/1854134
  862. */
  863. function i18n_menu_query_preferred_menu_links_alter(QueryAlterableInterface $query) {
  864. global $language;
  865. // Get queried tables.
  866. $tables = $query->getTables();
  867. foreach ($tables as $alias => $table) {
  868. if ($table['table'] == 'menu_links') {
  869. // Add language filter, ensuring that we don't have any collision when
  870. // determining the active menu trail when there are multiple menu items
  871. // with same link path but different languages.
  872. if ($language) {
  873. $query->condition('language', array($language->language, LANGUAGE_NONE), 'IN');
  874. }
  875. break;
  876. }
  877. }
  878. }