entity_translation_i18n_menu.module 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408
  1. <?php
  2. /**
  3. * @file
  4. * The menu specific translation functions and hook implementations.
  5. */
  6. /**
  7. * Implements hook_node_prepare().
  8. *
  9. * Translates the menu item shown on node edit forms if the node language does
  10. * not equal the language of the menu item. This means either loading the
  11. * respective menu item from the translation set or localizing the item.
  12. */
  13. function entity_translation_i18n_menu_node_prepare($node) {
  14. $langcode = entity_language('node', $node);
  15. if (!empty($langcode) && !empty($node->menu['language']) && $node->menu['language'] != $langcode && entity_translation_i18n_menu_item($node->menu)) {
  16. $handler = entity_translation_get_handler('node', $node);
  17. $source_langcode = $handler->getSourceLanguage();
  18. // If we are creating a translation we need to use the source language.
  19. entity_translation_i18n_menu_node_menu_item_translate($node, $source_langcode ? $source_langcode : $langcode);
  20. }
  21. }
  22. /**
  23. * Implements hook_module_implements_alter().
  24. */
  25. function entity_translation_i18n_menu_module_implements_alter(&$implementations, $hook) {
  26. switch ($hook) {
  27. case 'node_prepare':
  28. case 'node_presave':
  29. // Move some of our hook implementations to end of list. Required so that
  30. // the 'menu' key is populated when our implementation gets called. This
  31. // also prevents our changes from being overridden.
  32. $group = $implementations['entity_translation_i18n_menu'];
  33. unset($implementations['entity_translation_i18n_menu']);
  34. $implementations['entity_translation_i18n_menu'] = $group;
  35. break;
  36. }
  37. }
  38. /**
  39. * Implements hook_node_presave().
  40. */
  41. function entity_translation_i18n_menu_node_presave($node) {
  42. if (!entity_translation_enabled('node')) {
  43. return;
  44. }
  45. $handler = entity_translation_get_handler('node', $node);
  46. $translations = $handler->getTranslations();
  47. $source_langcode = $handler->getSourceLanguage();
  48. $tset = !empty($node->menu['tset']);
  49. // If no translation is available the menu data is always supposed to be
  50. // entered in the source string language. This way we avoid having unneeded
  51. // string translations hanging around.
  52. if (empty($source_langcode) && count($translations->data) < 2 && !$tset) {
  53. return;
  54. }
  55. // When creating a new translation, leave the source menu item intact and
  56. // create a new one.
  57. $langcode = entity_language('node', $node);
  58. if (!empty($node->menu) && $tset && !empty($source_langcode)) {
  59. $node->source_menu = menu_link_load($node->menu['mlid']);
  60. unset($node->menu['mlid']);
  61. }
  62. // Store the entity language for later reference when saving a translation.
  63. // If we are editing a translation in the string source language, we can skip
  64. // item processing since the proper values are already in place. Instead when
  65. // creating the translation we need to process the link item before saving it.
  66. if (!empty($node->menu) && !empty($langcode) && ($source_langcode || $langcode != i18n_string_source_language())) {
  67. $node->menu['entity_language'] = $langcode;
  68. $node->menu['entity_translation_handler'] = $handler;
  69. }
  70. // If we have a translation set here we should prepare it for storage,
  71. // otherwise we need to ensure the menu item has no language so it can be
  72. // localized.
  73. if ($tset) {
  74. entity_translation_i18n_menu_item_tset_prepare($node, $langcode);
  75. }
  76. else {
  77. $node->menu['language'] = LANGUAGE_NONE;
  78. }
  79. }
  80. /**
  81. * Implements hook_form_FORM_ID_alter().
  82. */
  83. function entity_translation_i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
  84. $form['#validate'][] = 'entity_translation_i18n_menu_form_menu_edit_item_validate';
  85. }
  86. /**
  87. * Implements hook_menu_link_alter().
  88. */
  89. function entity_translation_i18n_menu_menu_link_alter(&$link) {
  90. if (!empty($link['mlid']) && !empty($link['entity_language']) && $link['language'] == LANGUAGE_NONE && entity_translation_i18n_menu_item($link)) {
  91. $sources = array();
  92. foreach (array('title' => 'link_title', 'description' => 'description') as $key => $link_field) {
  93. $name = array('menu', 'item', $link['mlid'], $key);
  94. $source = i18n_string_get_source($name);
  95. // The source might not exist yet.
  96. $sources[$key] = is_object($source) ? $source->get_string() : $link[$link_field];
  97. }
  98. // If the link values to be saved are translated, we need to revert the
  99. // localized menu link back to the original. This way they can be saved
  100. // without accidentially storing a translation string as a source string.
  101. // The translated values are put in a separate key for later reference.
  102. if ($link['entity_language'] != i18n_string_source_language()) {
  103. $link['entity_translation_strings'] = array(
  104. 'title' => $link['link_title'],
  105. 'description' => $link['description'],
  106. );
  107. $link['link_title'] = $sources['title'];
  108. $link['options']['attributes']['title'] = $sources['description'];
  109. }
  110. // If the link values are in the string source language, we need to save
  111. // the previous source values as translations. As a matter of fact this can
  112. // happen only when initially creating a menu item in a language different
  113. // from the source string one.
  114. else {
  115. $link['entity_translation_strings'] = array(
  116. 'title' => $sources['title'],
  117. 'description' => $sources['description'],
  118. );
  119. $link['entity_language'] = $link['entity_translation_handler']->getLanguage();
  120. }
  121. }
  122. }
  123. /**
  124. * Implements hook_menu_link_update().
  125. */
  126. function entity_translation_i18n_menu_menu_link_update($link) {
  127. // Make sure localizations are saved properly.
  128. if (entity_translation_i18n_menu_item($link) && !empty($link['entity_translation_strings'])) {
  129. $string_langcode = isset($link['entity_language']) ? $link['entity_language'] : i18n_string_source_language();
  130. $name = implode(':', array('menu', 'item', $link['mlid']));
  131. foreach ($link['entity_translation_strings'] as $key => $translation) {
  132. i18n_string_translation_update($name . ':' . $key, $translation, $string_langcode);
  133. }
  134. }
  135. }
  136. /**
  137. * Menu specific alterations for the entity form.
  138. *
  139. * Adds to the regular menu item widget a checkbox to choose whether the current
  140. * menu item should be localized or part of a translation set.
  141. */
  142. function entity_translation_i18n_menu_form(&$form, &$form_state) {
  143. $info = entity_translation_edit_form_info($form, $form_state);
  144. if ($info && $info['entity type'] == 'node') {
  145. $node = $info['entity'];
  146. $source_menu = isset($node->source_menu) ? $node->source_menu : $node->menu;
  147. // Check that the menu item of the source node is translatable.
  148. if (isset($form['menu']) && !empty($source_menu) && i18n_menu_mode($source_menu['menu_name'], I18N_MODE_MULTIPLE)) {
  149. $default = isset($source_menu['language']) && $source_menu['language'] != LANGUAGE_NONE;
  150. $languages = language_list();
  151. $handler = entity_translation_entity_form_get_handler($form, $form_state);
  152. $langcode = $handler->getActiveLanguage();
  153. $language_name = isset($languages[$langcode]) ? t($languages[$langcode]->name) : t('current');
  154. $form['menu']['#multilingual'] = TRUE;
  155. $form['menu']['link']['tset'] = array(
  156. '#type' => 'checkbox',
  157. '#title' => t('Menu link enabled only for the %language language', array('%language' => $language_name)),
  158. '#prefix' => '<label>' . t('Menu translation') . '</label>',
  159. '#default_value' => $default,
  160. '#description' => t('Create a different menu link for each translation. Every link will have its own parent and weight, otherwise only title and description will be translated.'),
  161. '#weight' => 10,
  162. );
  163. if (!empty($default)) {
  164. $translation_set = i18n_menu_translation_load($source_menu['i18n_tsid']);
  165. $translations = $translation_set ? $translation_set->get_translations() : FALSE;
  166. if (!empty($translations) && (count($translations) > 1 || !isset($translations[$langcode]))) {
  167. $form['menu']['link']['tset']['#disabled'] = TRUE;
  168. }
  169. }
  170. }
  171. }
  172. }
  173. /**
  174. * Validation handler for the menu item edit form.
  175. */
  176. function entity_translation_i18n_menu_form_menu_edit_item_validate($form, &$form_state) {
  177. $item = $form_state['values'];
  178. // Localizable menu items should not be created when a translation set for the
  179. // same path already exists (exluding special paths starting by <).
  180. if ($item['language'] == LANGUAGE_NONE && strpos($item['link_path'], '<') !== 0) {
  181. $count = db_select('menu_links', 'ml')
  182. ->condition('ml.link_path', $item['link_path'])
  183. ->condition('ml.i18n_tsid', 0, '<>')
  184. ->countQuery()
  185. ->execute()
  186. ->fetchField();
  187. if (!empty($count)) {
  188. form_set_error('language', t('There are already one or more items with a language assigned for the given path. Remove them or assign a language to this item too.'));
  189. }
  190. }
  191. }
  192. /**
  193. * Checks whether a given menu item is translatable through entity translation.
  194. *
  195. * @param array $item
  196. * A menu item.
  197. *
  198. * @todo
  199. * Find more generic way of determining whether ET is enabled for a link; add
  200. * support for other entities, e.g. taxonomy_term (?).
  201. */
  202. function entity_translation_i18n_menu_item($item) {
  203. $cache = &drupal_static(__FUNCTION__, array());
  204. if (!isset($cache[$item['link_path']])) {
  205. // First check that the item belongs to a menu which has translation
  206. // enabled.
  207. if (!i18n_menu_mode($item['menu_name'], I18N_MODE_MULTIPLE)) {
  208. $cache[$item['link_path']] = FALSE;
  209. }
  210. // Check if the respective node type has entity translation enabled.
  211. if (preg_match('!^node/(\d+)(/.+|)$!', $item['link_path'], $matches)) {
  212. if (!entity_translation_enabled('node')) {
  213. $cache[$item['link_path']] = FALSE;
  214. }
  215. else {
  216. $type = db_select('node', 'n')
  217. ->condition('nid', $matches[1])
  218. ->fields('n', array('type'))
  219. ->execute()->fetchField();
  220. $cache[$item['link_path']] = entity_translation_node_supported_type($type);
  221. }
  222. }
  223. else {
  224. $cache[$item['link_path']] = FALSE;
  225. }
  226. }
  227. return $cache[$item['link_path']];
  228. }
  229. /**
  230. * Replace the menu item on the given node with a localized version.
  231. *
  232. * If the menu item is replaced by a different menu item from the translation
  233. * set, the original item is stored in $node->source_menu.
  234. *
  235. * @param $node
  236. * A node object, with a menu item ($node->menu).
  237. * @param $langcode
  238. * The language into which the menu item should be translated.
  239. */
  240. function entity_translation_i18n_menu_node_menu_item_translate($node, $langcode) {
  241. // Localization.
  242. if ($node->menu['language'] == LANGUAGE_NONE) {
  243. _i18n_menu_link_localize($node->menu, $langcode);
  244. // Update properties 'link_title' and 'options.attributes.title' which are
  245. // used for the node menu form; i18n_menu_link_localize only localizes
  246. // rendered properties 'title' and 'localized_options.attributes.title'.
  247. $node->menu['link_title'] = $node->menu['title'];
  248. $node->menu['options']['attributes']['title'] = isset($node->menu['localized_options']['attributes']['title']) ? $node->menu['localized_options']['attributes']['title'] : '';
  249. }
  250. // Translation sets.
  251. else {
  252. $menu = NULL;
  253. if (!empty($node->menu['i18n_tsid']) && $translation_set = i18n_menu_translation_load($node->menu['i18n_tsid'])) {
  254. // Load menu item from translation set.
  255. $menu = $translation_set->get_item($langcode);
  256. // Set parent_depth_limit (required on node forms).
  257. if (!empty($menu) && !isset($menu['parent_depth_limit'])) {
  258. $menu['parent_depth_limit'] = _menu_parent_depth_limit($menu);
  259. }
  260. // Make sure the menu item is not set to hidden; i18n_menu automatically
  261. // hides any menu items not matching the current interface language.
  262. if (!empty($menu)) {
  263. $menu['hidden'] = FALSE;
  264. }
  265. }
  266. // Replace the menu item with the translated version, or null if there is
  267. // no translated item. Store the original one in $node->source_menu.
  268. $node->source_menu = $node->menu;
  269. $node->menu = $menu;
  270. }
  271. }
  272. /**
  273. * Prepares the menu item attached to given entity for saving.
  274. *
  275. * - Ensures that different menu items attached to the entity and its
  276. * translations are stored within the same translation set.
  277. * - Sets missing default values, and cleans out null values.
  278. * - Sets the language of the menu item to given target language.
  279. *
  280. * @param $entity
  281. * Node object.
  282. * @param $langcode
  283. * Target language.
  284. */
  285. function entity_translation_i18n_menu_item_tset_prepare($entity, $langcode) {
  286. // Load or create a translation set.
  287. if (!empty($entity->source_menu)) {
  288. if (!empty($entity->source_menu['i18n_tsid'])) {
  289. $translation_set = i18n_translation_set_load($entity->source_menu['i18n_tsid']);
  290. }
  291. else {
  292. // Make sure that the source menu item does have a language assigned.
  293. if ($entity->source_menu['language'] == LANGUAGE_NONE) {
  294. $entity->source_menu['language'] = $entity->menu['entity_translation_handler']->getSourceLanguage();
  295. menu_link_save($entity->source_menu);
  296. }
  297. // Create new translation set.
  298. $translation_set = i18n_translation_set_build('menu_link')
  299. ->add_item($entity->source_menu);
  300. }
  301. $entity->menu['translation_set'] = $translation_set;
  302. }
  303. // Extract menu_name and pid from parent property.
  304. if (!empty($entity->menu['parent'])) {
  305. list($entity->menu['menu_name'], $entity->menu['plid']) = explode(':', $entity->menu['parent']);
  306. }
  307. // Remove null values.
  308. $entity->menu = array_filter($entity->menu);
  309. $entity->menu['language'] = $langcode;
  310. $entity->menu += array(
  311. 'description' => '',
  312. 'customized' => 1,
  313. );
  314. }
  315. /**
  316. * Implements hook_entity_translation_upgrade().
  317. */
  318. function entity_translation_i18n_menu_entity_translation_upgrade($node, $translation) {
  319. menu_node_prepare($node);
  320. menu_node_prepare($translation);
  321. if (!empty($node->menu['mlid']) && !empty($translation->menu['mlid'])) {
  322. $link = $node->menu;
  323. $link['link_title'] = $translation->menu['link_title'];
  324. $link['description'] = $translation->menu['description'];
  325. $link['entity_language'] = $translation->language;
  326. $link['language'] = LANGUAGE_NONE;
  327. menu_link_save($link, $node->menu);
  328. }
  329. }
  330. /**
  331. * Implements hook_entity_translation_delete().
  332. */
  333. function entity_translation_i18n_menu_entity_translation_delete($entity_type, $entity, $langcode) {
  334. // Make sure that we are working with an entity of type node.
  335. if ($entity_type != 'node') {
  336. return;
  337. }
  338. list($entity_id, , ) = entity_extract_ids($entity_type, $entity);
  339. // Clean-up all menu module links that point to this node.
  340. $result = db_select('menu_links', 'ml')
  341. ->fields('ml', array('mlid', 'language'))
  342. ->condition('link_path', 'node/' . $entity_id)
  343. ->condition('module', 'menu')
  344. ->execute()
  345. ->fetchAllAssoc('mlid');
  346. foreach ($result as $link) {
  347. // Delete all menu links matching the deleted language.
  348. if ($link->language == $langcode) {
  349. menu_link_delete($link->mlid);
  350. }
  351. // Delete string translations for all language-neutral menu items.
  352. if ($link->language == LANGUAGE_NONE) {
  353. $name = array('menu', 'item', $link->mlid);
  354. foreach (array('title', 'description') as $key) {
  355. $name[] = $key;
  356. $source = i18n_string_get_source($name);
  357. if(!empty($source->lid)) {
  358. db_delete('locales_target')
  359. ->condition('lid', $source->lid)
  360. ->condition('language', $langcode)
  361. ->execute();
  362. }
  363. }
  364. }
  365. }
  366. }