Projet

Général

Profil

Paste
Télécharger (34,2 ko) Statistiques
| Branche: | Révision:

root / drupal7 / sites / all / modules / i18n / i18n_menu / i18n_menu.module @ 76df55b7

1
<?php
2

    
3
/**
4
 * @file
5
 * Internationalization (i18n) submodule: Menu translation.
6
 *
7
 * @author Jose A. Reyero, 2005
8
 *
9
 */
10

    
11
/**
12
 * Implements hook_menu()
13
 */
14
function i18n_menu_menu() {
15
  $items['admin/structure/menu/manage/translation'] = array(
16
    'title' => 'Translation sets',
17
    'page callback' => 'i18n_translation_set_list_manage',
18
    'page arguments' => array('menu_link'),
19
    'access arguments' => array('administer menu'),
20
    'type' => MENU_LOCAL_TASK,
21
    'weight' => 10,
22
  );
23
  $items['admin/structure/menu/manage/translation/add'] = array(
24
    'title' => 'Add translation',
25
    'page callback' => 'drupal_get_form',
26
    'page arguments' => array('i18n_menu_translation_form'),
27
    'access arguments' => array('administer menu'),
28
    'type' => MENU_LOCAL_ACTION,
29
    'file' => 'i18n_menu.admin.inc',
30
  );
31
  $items['admin/structure/menu/manage/translation/edit/%i18n_menu_translation'] = array(
32
    'title' => 'Edit translation',
33
    'page callback' => 'drupal_get_form',
34
    'page arguments' => array('i18n_menu_translation_form', 6),
35
    'access arguments' => array('administer menu'),
36
    'type' => MENU_CALLBACK,
37
    'file' => 'i18n_menu.admin.inc',
38
  );
39
  $items['admin/structure/menu/manage/translation/delete/%i18n_menu_translation'] = array(
40
    'title' => 'Delete translation',
41
    'page callback' => 'drupal_get_form',
42
    'page arguments' => array('i18n_translation_set_delete_confirm', 6),
43
    'access arguments' => array('administer menu'),
44
    'type' => MENU_CALLBACK,
45
  );
46
  return $items;
47
}
48

    
49
/**
50
 * Implements hook_menu_alter()
51
 */
52
function i18n_menu_menu_alter(&$items) {
53
  $items['admin/structure/menu/item/%menu_link'] = $items['admin/structure/menu/item/%menu_link/edit'];
54
  $items['admin/structure/menu/item/%menu_link']['type'] = MENU_CALLBACK;
55
  $items['admin/structure/menu/item/%menu_link/edit']['type'] = MENU_DEFAULT_LOCAL_TASK;
56
}
57

    
58
/**
59
 * Implements hook_block_view().
60
 */
61
function i18n_menu_block_view_alter(&$data, $block) {
62
  if (($block->module == 'menu' || $block->module == 'system') && (i18n_menu_mode($block->delta) & I18N_MODE_MULTIPLE)) {
63
    $menus = menu_get_menus();
64
    if (isset($menus[$block->delta])) {
65
      if (empty($block->title)) {
66
        $data['subject'] = i18n_string_plain(
67
          array('menu', 'menu', $block->delta, 'title'),
68
          $menus[$block->delta]
69
        );
70
      }
71
      // Add contextual links for this block.
72
      if (!empty($data['content'])) {
73
        $data['content']['#contextual_links']['menu'] = array('admin/structure/menu/manage', array($block->delta));
74
      }
75
    }
76
  }
77
}
78

    
79
/**
80
 * Implements hook_i18n_translate_path()
81
 */
82
function i18n_menu_i18n_translate_path($path) {
83
  $item = i18n_menu_link_load($path, i18n_langcode());
84
  if ($item && ($set = i18n_translation_object('menu_link', $item))) {
85
    $links = array();
86
    foreach ($set->get_translations() as $lang => $link) {
87
      $links[$lang] = array(
88
        'href' => $link['link_path'],
89
        'title' => $link['link_title'],
90
        'i18n_type' => 'menu_link',
91
        'i18n_object' => $link,
92
      );
93
    }
94
    return $links;
95
  }
96
}
97

    
98
/**
99
 * Implements hook_menu_insert()
100
 */
101
function i18n_menu_menu_insert($menu) {
102
  i18n_menu_menu_update($menu);
103
}
104

    
105
/**
106
 * Implements hook_menu_update()
107
 */
108
function i18n_menu_menu_update($menu) {
109
  // Stores the fields of menu links which need an update.
110
  $update = array();
111

    
112
  if (!isset($menu['i18n_mode'])) {
113
    $menu['i18n_mode'] = I18N_MODE_NONE;
114
  }
115
  if (!($menu['i18n_mode'] & I18N_MODE_LANGUAGE)) {
116
    $menu['language'] = LANGUAGE_NONE;
117
  }
118
  db_update('menu_custom')
119
    ->fields(array('language' => $menu['language'], 'i18n_mode' => $menu['i18n_mode']))
120
    ->condition('menu_name', $menu['menu_name'])
121
    ->execute();
122
  if (!$menu['i18n_mode']) {
123
    $update['language'] = LANGUAGE_NONE;
124
  }
125
  elseif ($menu['i18n_mode'] & I18N_MODE_LANGUAGE) {
126
    $update['language'] = $menu['language'];
127
  }
128

    
129
  // Non translatable menu.
130
  if (!($menu['i18n_mode'] & I18N_MODE_TRANSLATE)) {
131
    $tsids = db_select('menu_links')
132
      ->fields('menu_links', array('i18n_tsid'))
133
      ->groupBy('i18n_tsid')
134
      ->condition('menu_name', $menu['menu_name'])
135
      ->condition('customized', 1)
136
      ->condition('i18n_tsid', 0, '<>')
137
      ->execute()
138
      ->fetchCol(0);
139
    if (!empty($tsids)) {
140
      foreach ($tsids as $tsid) {
141
        if ($translation_set = i18n_translation_set_load($tsid)) {
142
          $translation_set->delete();
143
        }
144
      }
145
    }
146
    $update['i18n_tsid'] = 0;
147
  }
148

    
149
  if (!empty($update)) {
150
    db_update('menu_links')
151
      ->fields($update)
152
      ->condition('menu_name', $menu['menu_name'])
153
      ->condition('customized', 1)
154
      ->execute();
155
  }
156

    
157
  // Update strings, always add translation if no language
158
  if (!i18n_object_langcode($menu)) {
159
    i18n_string_object_update('menu', $menu);
160
  }
161

    
162
  // Clear all menu caches.
163
  menu_cache_clear_all();
164
}
165

    
166
/**
167
 * Implements hook_menu_delete()
168
 */
169
function i18n_menu_menu_delete($menu) {
170
  i18n_string_object_remove('menu', $menu);
171
}
172

    
173
/**
174
 * Implements hook_menu_link_alter().
175
 *
176
 * This function is invoked from menu_link_save() before default
177
 * menu link options (menu_name, module, etc.. have been set)
178
 */
179
function i18n_menu_menu_link_alter(&$item) {
180
  // We just make sure every link has a valid language property.
181
  if (!i18n_object_langcode($item)) {
182
    $item['language'] = LANGUAGE_NONE;
183
  }
184
}
185

    
186
/**
187
 * Implements hook_menu_link_insert()
188
 */
189
function i18n_menu_menu_link_insert($link) {
190
  i18n_menu_menu_link_update($link);
191
}
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

    
201
  if ($menu_mode & I18N_MODE_TRANSLATE && isset($link['language'])) {
202
    // Multilingual menu links, translatable, it may be part of a
203
    // translation set.
204
    if (i18n_object_langcode($link)) {
205
      if (!empty($link['translation_set'])) {
206
        // Translation set comes as parameter, we may be creating a translation,
207
        // add link to the set.
208
        $translation_set = $link['translation_set'];
209
        $translation_set
210
          ->add_item($link)
211
          ->save(TRUE);
212
      }
213
    }
214
    elseif ($link['language'] === LANGUAGE_NONE && !empty($link['original_item']['i18n_tsid'])) {
215
      if ($translation_set = i18n_translation_set_load($link['original_item']['i18n_tsid'])) {
216
        $translation_set->remove_language($link['original_item']['language']);
217
        // If there are no links left in this translation set, delete the set.
218
        // Otherwise update the set.
219
        $translation_set->update_delete();
220
      }
221
      $fields['i18n_tsid'] = 0;
222
    }
223
  }
224
  // For multilingual menu items, always set a language and mark them for
225
  // 'alter' so they can be processed later by
226
  // hook_translated_link_menu_alter().
227
  if ($menu_mode) {
228
    if (!isset($link['language'])) {
229
      $link['language'] = LANGUAGE_NONE;
230
    }
231
    if (_i18n_menu_link_check_alter($link) && empty($link['options']['alter'])) {
232
      $fields['options'] = $link['options'];
233
      $fields['options']['alter'] = TRUE;
234
    }
235
    // We cannot unmark links for altering because we don't know what other
236
    // modules use it for.
237
  }
238
  // Update language field if the link has a language value.
239
  if (isset($link['language'])) {
240
    $fields['language'] = $link['language'];
241
  }
242

    
243
  if (!empty($fields)) {
244
    // If link options are to be updated, they need to be serialized.
245
    if (isset($fields['options'])) {
246
      $fields['options'] = serialize($fields['options']);
247
    }
248
    db_update('menu_links')
249
      ->fields($fields)
250
      ->condition('mlid', $link['mlid'])
251
      ->execute();
252
  }
253
  // Update translatable strings if any for customized links that belong to a
254
  // localizable menu.
255
  if (_i18n_menu_link_is_localizable($link)) {
256
    i18n_string_object_update('menu_link', $link);
257
  }
258
  else {
259
    i18n_string_object_remove('menu_link', $link);
260
  }
261
}
262

    
263
/**
264
 * Implements hook_menu_delete()
265
 */
266
function i18n_menu_menu_link_delete($link) {
267
  // If a translation set exists for this link, remove this link from the set.
268
  if (!empty($link['i18n_tsid'])) {
269
    if ($translation_set = i18n_translation_set_load($link['i18n_tsid'])) {
270
      $translation_set->get_translations();
271

    
272
      $translation_set->remove_language($link['language']);
273

    
274
      // If there are no links left in this translation set, delete the set.
275
      // Otherwise update the set.
276
      $translation_set->update_delete();
277
    }
278
  }
279

    
280
  i18n_string_object_remove('menu_link', $link);
281
}
282

    
283
/**
284
 * Get menu mode or compare with given one
285
 */
286
function i18n_menu_mode($name, $mode = NULL) {
287
  $menu = menu_load($name);
288
  if (!$menu || !isset($menu['i18n_mode'])) {
289
    return isset($mode) ? FALSE : I18N_MODE_NONE;
290
  }
291
  else {
292
    return isset($mode) ? $menu['i18n_mode'] & $mode : $menu['i18n_mode'];
293
  }
294
}
295

    
296
/**
297
 * Implements hook_translated_menu_link_alter().
298
 *
299
 * Translate localizable menu links on the fly.
300
 * Filter out items that have a different language from current interface.
301
 *
302
 * @see i18n_menu_menu_link_alter()
303
 */
304
function i18n_menu_translated_menu_link_alter(&$item) {
305
  // Only process links to be displayed not processed before by i18n_menu.
306
  if (_i18n_menu_link_process($item)) {
307
    if (!_i18n_menu_link_is_visible($item)) {
308
      $item['hidden'] = TRUE;
309
    }
310
    elseif (_i18n_menu_link_is_localizable($item)) {
311
      // Item has undefined language, it is a candidate for localization.
312
      _i18n_menu_link_localize($item);
313
    }
314
  }
315
}
316

    
317
/**
318
 * Implements hook_help().
319
 */
320
function i18n_menu_help($path, $arg) {
321
  switch ($path) {
322
    case 'admin/help#i18n_menu' :
323
      $output = '<p>' . t('This module adds support for multilingual menus. You can setup multilingual options for each menu:') . '</p>';
324
      $output .= '<ul>';
325
      $output .= '<li>' . t('Menus can be fully multilingual with translatable (or localized) menu items.') . '</li>';
326
      $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>';
327
      $output .= '<li>' . t('Menus can also be configured to have no translations.') . '</li>';
328
      $output .= '</ul>';
329
      $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>';
330
      $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>';
331
      return $output;
332

    
333
    case 'admin/config/regional/i18n':
334
      $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>';
335
      return $output;
336
  }
337
}
338

    
339
/**
340
 * Implements hook_variable_info_alter()
341
 */
342
function i18n_menu_variable_info_alter(&$variables, $options) {
343
  // Make menu variables translatable
344
  $variables['menu_main_links_source']['localize'] = TRUE;
345
  $variables['menu_secondary_links_source']['localize'] = TRUE;
346
  $variables['menu_parent_[node_type]']['localize'] = TRUE;
347
  $variables['menu_options_[node_type]']['localize'] = TRUE;
348
}
349

    
350
/**
351
 * Get localized menu tree.
352
 *
353
 * @param string $menu_name
354
 *   The menu the translated tree has to be fetched from.
355
 * @param string $langcode
356
 *   Optional language code to get the menu in, defaults to request language.
357
 * @param bool $reset
358
 *   Whether to reset the internal i18n_menu_translated_tree cache.
359
 */
360
function i18n_menu_translated_tree($menu_name, $langcode = NULL, $reset = FALSE) {
361
  $menu_output = &drupal_static(__FUNCTION__);
362
  $langcode = $langcode ? $langcode : i18n_language_interface()->language;
363
  if (!isset($menu_output[$langcode][$menu_name]) || $reset) {
364
    $tree = menu_tree_page_data($menu_name);
365
    $tree = i18n_menu_localize_tree($tree, $langcode);
366
    $menu_output[$langcode][$menu_name] = menu_tree_output($tree);
367
  }
368
  return $menu_output[$langcode][$menu_name];
369
}
370

    
371
/**
372
 * Localize menu tree.
373
 */
374
function i18n_menu_localize_tree($tree, $langcode = NULL) {
375
  $langcode = $langcode ? $langcode : i18n_language_interface()->language;
376
  foreach ($tree as $index => &$item) {
377
    $link = $item['link'];
378
    // We only process links that are visible and not processed before.
379
    if (_i18n_menu_link_process($item['link'])) {
380
      if (!_i18n_menu_link_is_visible($item['link'], $langcode)) {
381
        // Remove links for other languages than current.
382
        // Links with language wont be localized.
383
        unset($tree[$index]);
384
        // @todo Research whether the above has any advantage over:
385
        // $item['hidden'] = TRUE;
386
      }
387
      else {
388
        if (_i18n_menu_link_is_localizable($item['link'])) {
389
          // Item has undefined language, it is a candidate for localization.
390
          _i18n_menu_link_localize($item['link'], $langcode);
391
        }
392
        // Localize subtree.
393
        if (!empty($item['below'])) {
394
          $item['below'] = i18n_menu_localize_tree($item['below'], $langcode);
395
        }
396
      }
397
    }
398
  }
399
  return $tree;
400
}
401

    
402
/**
403
 * Localize menu renderable array
404
 */
405
function i18n_menu_localize_elements(&$elements) {
406
  foreach (element_children($elements) as $mlid) {
407
    $elements[$mlid]['#title'] = i18n_string(array('menu', 'item', $mlid, 'title'), $elements[$mlid]['#title']);
408
    if (!empty($tree[$mlid]['#localized_options']['attributes']['title'])) {
409
      $elements[$mlid]['#localized_options']['attributes']['title'] = i18n_string(array('menu', 'item', $mlid, 'description'), $tree[$mlid]['#localized_options']['attributes']['title']);
410
    }
411
    i18n_menu_localize_elements($elements[$mlid]);
412
  }
413
}
414

    
415
/**
416
 * Return an array of localized links for a navigation menu.
417
 *
418
 * Localized version of menu_navigation_links()
419
 */
420
function i18n_menu_navigation_links($menu_name, $level = 0) {
421
  // Don't even bother querying the menu table if no menu is specified.
422
  if (empty($menu_name)) {
423
    return array();
424
  }
425

    
426
  // Get the menu hierarchy for the current page.
427
  $tree = menu_tree_page_data($menu_name, $level + 1);
428
  $tree = i18n_menu_localize_tree($tree);
429

    
430
  // Go down the active trail until the right level is reached.
431
  while ($level-- > 0 && $tree) {
432
    // Loop through the current level's items until we find one that is in trail.
433
    while ($item = array_shift($tree)) {
434
      if ($item['link']['in_active_trail']) {
435
        // If the item is in the active trail, we continue in the subtree.
436
        $tree = empty($item['below']) ? array() : $item['below'];
437
        break;
438
      }
439
    }
440
  }
441

    
442
  // Create a single level of links.
443
  $router_item = menu_get_item();
444
  $links = array();
445
  foreach ($tree as $item) {
446
    if (!$item['link']['hidden']) {
447
      $class = '';
448
      $l = $item['link']['localized_options'];
449
      $l['href'] = $item['link']['href'];
450
      $l['title'] = $item['link']['title'];
451
      if ($item['link']['in_active_trail']) {
452
        $class = ' active-trail';
453
        $l['attributes']['class'][] = 'active-trail';
454
      }
455
      // Normally, l() compares the href of every link with $_GET['q'] and sets
456
      // the active class accordingly. But local tasks do not appear in menu
457
      // trees, so if the current path is a local task, and this link is its
458
      // tab root, then we have to set the class manually.
459
      if ($item['link']['href'] == $router_item['tab_root_href'] && $item['link']['href'] != $_GET['q']) {
460
        $l['attributes']['class'][] = 'active';
461
      }
462
      // Keyed with the unique mlid to generate classes in theme_links().
463
      $links['menu-' . $item['link']['mlid'] . $class] = $l;
464
    }
465
  }
466
  return $links;
467
}
468

    
469
/**
470
 * Get localized menu title
471
 */
472
function _i18n_menu_link_title($link, $langcode = NULL) {
473
  $key = i18n_object_info('menu_link', 'key');
474
  return i18n_string_translate(array('menu', 'item', $link[$key], 'title'), $link['link_title'], array('langcode' => $langcode, 'sanitize' => FALSE));
475
}
476

    
477
/**
478
 * Localize menu item title and description.
479
 *
480
 * This will be invoked always after _menu_item_localize()
481
 *
482
 * Link properties to manage:
483
 * - title, menu router title
484
 * - link_title, menu link title
485
 * - options.attributes.title, menu link description.
486
 * - localized_options.attributes.title,
487
 *
488
 * @see _menu_item_localize()
489
 * @see _menu_link_translate()
490
 */
491
function _i18n_menu_link_localize(&$link, $langcode = NULL) {
492
  // Only translate title if it has no special callback.
493
  if (empty($link['title callback']) || $link['title callback'] === 't') {
494
    $link['title'] = _i18n_menu_link_title($link, $langcode);
495
  }
496
  if ($description = _i18n_menu_link_description($link, $langcode)) {
497
    $link['localized_options']['attributes']['title'] = $description;
498
  }
499
}
500

    
501
/**
502
 * Get localized menu description
503
 */
504
function _i18n_menu_link_description($link, $langcode = NULL) {
505
  if (!empty($link['options']['attributes']['title'])) {
506
    $key = i18n_object_info('menu_link', 'key');
507
    return i18n_string_translate(array('menu', 'item', $link[$key], 'description'), $link['options']['attributes']['title'], array('langcode' => $langcode));
508
  }
509
  else {
510
    return NULL;
511
  }
512
}
513

    
514
/**
515
 * Check whether this link is to be processed by i18n_menu and start processing.
516
 */
517
function _i18n_menu_link_process(&$link) {
518
  // Only visible links that have a language property and haven't been processed
519
  // before. We also check that they belong to a menu with language options.
520
  if (empty($link['i18n_menu']) && !empty($link['language']) && !empty($link['access']) && empty($link['hidden']) && i18n_menu_mode($link['menu_name'])) {
521
    // Mark so it won't be processed twice.
522
    $link['i18n_menu'] = TRUE;
523
    // Skip if administering this menu or this menu item.
524
    if (arg(0) == 'admin' && arg(1) == 'structure' && arg(2) == 'menu') {
525
      if (arg(3) == 'manage' && $link['menu_name'] == arg(4)) {
526
        return FALSE;
527
      }
528
      elseif (arg(3) == 'item' && arg(4) == $link['mlid']) {
529
        return FALSE;
530
      }
531
    }
532
    // Skip if administering this menu item through the node edit form.
533
    elseif (arg(0) == 'node' && arg(2) == 'edit' && $link['link_path'] == arg(0) . '/' . arg(1)) {
534
      return FALSE;
535
    }
536
    return TRUE;
537
  }
538
  else {
539
    return FALSE;
540
  }
541
}
542

    
543
/**
544
 * Check whether this menu item should be marked for altering.
545
 *
546
 * Menu items that have a language or that have any localizable strings
547
 * will be marked to be run through hook_translated_menu_link_alter().
548
 *
549
 * @see i18n_menu_translated_menu_link_alter()
550
 */
551
function _i18n_menu_link_check_alter($link) {
552
  return i18n_menu_mode($link['menu_name']) && (i18n_object_langcode($link) || _i18n_menu_link_is_localizable($link, TRUE));
553
}
554

    
555
/**
556
 * Check whether this link should be localized by i18n_menu.
557
 *
558
 * @param array $link
559
 *   Menu link array.
560
 * @param bool $check_strings
561
 *   Whether to check if the link has actually localizable strings. Since this
562
 *   is a more expensive operation, it will be just checked when editing menu
563
 *   items.
564
 *
565
 * @return boolean
566
 *   Returns TRUE if link is localizable.
567
 */
568
function _i18n_menu_link_is_localizable($link, $check_strings = FALSE) {
569
  return !empty($link['customized']) && !i18n_object_langcode($link) && i18n_menu_mode($link['menu_name'], I18N_MODE_LOCALIZE) &&
570
  (!$check_strings || _i18n_menu_link_localizable_properties($link));
571
}
572

    
573
/**
574
 * Check whether this menu link is visible for current/given language.
575
 */
576
function _i18n_menu_link_is_visible($link, $langcode = NULL) {
577
  $langcode = $langcode ? $langcode : i18n_language_interface()->language;
578
  return $link['language'] == LANGUAGE_NONE || $link['language'] == $langcode;
579
}
580

    
581
/**
582
 * Get localizable properties for menu link checking agains the router item.
583
 */
584
function _i18n_menu_link_localizable_properties($link) {
585
  $props = array();
586
  $router = !empty($link['router_path']) ? _i18n_menu_get_router($link['router_path']) : NULL;
587
  if (!empty($link['link_title'])) {
588
    // If the title callback is 't' and the link title matches the router title
589
    // it will be localized by core, not by i18n_menu.
590
    if (!$router ||
591
        (empty($router['title_callback']) || ($router['title_callback'] != 't' || !empty($link['customized']))) ||
592
        (empty($router['title']) || $router['title'] != $link['link_title'])
593
    ) {
594
      $props[] = 'title';
595
    }
596
  }
597
  if (!empty($link['options']['attributes']['title'])) {
598
    // If the description matches the router description, it will be localized
599
    // by core.
600
    if (!$router || empty($router['description']) || ($router['description'] != $link['options']['attributes']['title']) || !empty($link['customized'])) {
601
      $props[] = 'description';
602
    }
603
  }
604
  return $props;
605
}
606

    
607
/**
608
 * Get the menu router for this router path.
609
 *
610
 * We need the untranslated title to compare, and this will be fast.
611
 * There's no api function to do this?
612
 *
613
 * @param string $path
614
 *   The path to fetch from the router.
615
 */
616
function _i18n_menu_get_router($path) {
617
  $cache = &drupal_static(__FUNCTION__, array());
618
  if (!array_key_exists($path, $cache)) {
619
    $cache[$path] = db_select('menu_router', 'mr')
620
      ->fields('mr', array('title', 'title_callback', 'description'))
621
      ->condition('path', $path)
622
      ->execute()
623
      ->fetchAssoc();
624
  }
625
  return $cache[$path];
626
}
627

    
628
/**
629
 * Implements hook_form_FORM_ID_alter().
630
 */
631
function i18n_menu_form_menu_edit_menu_alter(&$form, &$form_state) {
632
  $menu = menu_load($form['old_name']['#value']);
633
  $i18n_mode = $menu && isset($menu['i18n_mode']) ? $menu['i18n_mode'] : I18N_MODE_NONE;
634
  $langcode = $menu && isset($menu['language']) ? $menu['language'] : LANGUAGE_NONE;
635

    
636
  $form += i18n_translation_mode_element('menu', $i18n_mode, $langcode);
637
}
638

    
639
/**
640
 * Implements hook_form_FORM_ID_alter().
641
 *
642
 * Add a language selector to the menu_edit_item form and register a submit
643
 * callback to process items.
644
 */
645
function i18n_menu_form_menu_edit_item_alter(&$form, &$form_state) {
646
  $item = &$form['original_item']['#value'];
647
  $item['language'] = i18n_menu_item_get_language($item);
648
  // Check whether this item belongs to a node object and it is a supported type.
649
  $node_item = ($node = i18n_menu_item_get_node($item)) && i18n_menu_node_supported_type($node->type);
650
  if (!$node_item && i18n_menu_mode($item['menu_name'], I18N_MODE_TRANSLATE)) {
651
    //$form['i18n'] = array('#type' => 'fieldset');
652
    $form['i18n']['language'] = array(
653
      '#description' => t('This item belongs to a multilingual menu. You can set a language for it.'),
654
    ) + i18n_element_language_select($item);
655

    
656
    // If the term to be added will be a translation of a source term,
657
    // set the default value of the option list to the target language and
658
    // create a form element for storing the translation set of the source term.
659
    if (isset($_GET['translation']) && isset($_GET['target']) && ($source_item = menu_link_load($_GET['translation']))) {
660
      if (!empty($source_item['i18n_tsid'])) {
661
        $translation_set = i18n_translation_set_load($source_item['i18n_tsid']);
662
      }
663
      else {
664
        // Create object and stick the source information in the translation set.
665
        $translation_set = i18n_translation_set_build('menu_link')
666
          ->add_item($source_item);
667
      }
668
      $form['link_path']['#default_value'] = $source_item['link_path'];
669

    
670
      // Maybe we should disable the 'link_path' and 'parent' form elements?
671
      // $form['link_path']['#disabled'] = TRUE;
672
      // $form['parent']['#disabled'] = TRUE;
673

    
674
      $form['i18n']['language']['#default_value'] = $_GET['target'];
675
      $form['i18n']['language']['#disabled'] = TRUE;
676

    
677
      drupal_set_title(t('%language translation of menu item %title', array('%language' => locale_language_name($_GET['target']), '%title' => $source_item['link_title'])), PASS_THROUGH);
678
    }
679
    elseif (!empty($item['i18n_tsid'])) {
680
      $translation_set = i18n_translation_set_load($item['i18n_tsid']);
681
    }
682

    
683
    // Add the translation set to the form so we know the new menu item
684
    // needs to be added to that set.
685
    if (!empty($translation_set)) {
686
      $form['translation_set'] = array(
687
        '#type' => 'value',
688
        '#value' => $translation_set,
689
      );
690

    
691
      // If the current term is part of a translation set,
692
      // remove all other languages of the option list.
693
      if ($translations = $translation_set->get_translations()) {
694
        unset($form['i18n']['language']['#options'][LANGUAGE_NONE]);
695
        foreach ($translations as $langcode => $translation) {
696
          if ($translation['mlid'] !== $item['mlid']) {
697
            unset($form['i18n']['language']['#options'][$langcode]);
698
          }
699
        }
700
      }
701
    }
702
  }
703
  else {
704
    $form['language'] = array(
705
      '#type' => 'value',
706
      '#value' => $item['language'],
707
    );
708
  }
709
  if ($node_item && i18n_langcode($item['language'])) {
710
    $form['i18n']['message'] = array(
711
      '#type' => 'item',
712
      '#title' => t('Language'),
713
      '#markup' => i18n_language_name($item['language']),
714
      '#description' => t('This menu item belongs to a node, so it will have the same language as the node and cannot be localized.'),
715
    );
716
  }
717
  array_unshift($form['#validate'], 'i18n_menu_menu_item_prepare_normal_path');
718
}
719

    
720
/**
721
 * Implements hook_form_FORM_ID_alter().
722
 * FORM_ID = menu-overview-form.
723
 * Add a "translate" link in operations column for each menu item.
724
 */
725
function i18n_menu_form_menu_overview_form_alter(&$form, &$form_state) {
726
  foreach (element_children($form) as $element) {
727
    if (substr($element, 0, 5) == 'mlid:') {
728
      $mlid = $form[$element]['#item']['mlid'];
729
      if (i18n_get_object('menu', $mlid)->get_translate_access()) {
730
        $form[$element]['operations']['translate'] = array(
731
          '#type' => 'link',
732
          '#title' => t('translate'),
733
          '#href' => "admin/structure/menu/item/{$mlid}/translate",
734
        );
735
      }
736
    }
737
  }
738
}
739

    
740
/**
741
 * Normal path should be checked with menu item's language to avoid
742
 * troubles when a node and it's translation has the same url alias.
743
 */
744
function i18n_menu_menu_item_prepare_normal_path($form, &$form_state) {
745
  $item = &$form_state['values'];
746
  $item['link_path'] = i18n_prepare_normal_path($item['link_path'], $item['language']);
747
}
748

    
749
/**
750
 * Get language for menu item
751
 */
752
function i18n_menu_item_get_language($item) {
753
  if (isset($item['language'])) {
754
    return $item['language'];
755
  }
756
  else {
757
    $menu = menu_load($item['menu_name']);
758
    if (!isset($menu['i18n_mode'])) {
759
      return LANGUAGE_NONE;
760
    }
761
    switch ($menu['i18n_mode']) {
762
      case I18N_MODE_LANGUAGE:
763
        return $menu['language'];
764
      case I18N_MODE_NONE:
765
      case I18N_MODE_LOCALIZE:
766
        return LANGUAGE_NONE;
767
      default:
768
        if (!empty($item['mlid'])) {
769
          return db_select('menu_links', 'm')
770
            ->fields('m', array('language'))
771
            ->condition('mlid', $item['mlid'])
772
            ->execute()
773
            ->fetchField();
774
        }
775
        else {
776
          return LANGUAGE_NONE;
777
        }
778
    }
779
  }
780
}
781

    
782
/**
783
 * Implements hook_form_node_form_alter().
784
 *
785
 * Add language to menu settings of the node form, as well as setting defaults
786
 * to match the translated item's menu settings.
787
 */
788
function i18n_menu_form_node_form_alter(&$form, &$form_state, $form_id) {
789
  if (isset($form['menu'])) {
790
    $node = $form['#node'];
791
    $link = $node->menu;
792
    if (!empty($link['mlid'])) {
793
      // Preserve the menu item language whatever it is.
794
      $form['menu']['link']['language'] = array('#type' => 'value', '#value' => $link['language']);
795
    }
796
    elseif (i18n_menu_node_supported_type($node->type)) {
797
      // Set menu language to node language but only if it is a supported node type.
798
      $form['menu']['link']['language'] = array('#type' => 'value', '#value' => $node->language);
799
    }
800
    else {
801
      $form['menu']['link']['language'] = array('#type' => 'value', '#value' => LANGUAGE_NONE);
802
    }
803
    // Customized must be set to 1 to save language.
804
    $form['menu']['link']['customized'] = array('#type' => 'value', '#value' => 1);
805
  }
806
}
807

    
808
/**
809
 * Check whether a node type has multilingual support (but not entity translation).
810
 */
811
function i18n_menu_node_supported_type($type) {
812
  $supported = &drupal_static(__FUNCTION__);
813
  if (!isset($supported[$type])) {
814
    $mode = variable_get('language_content_type_' . $type, 0);
815
    $supported[$type] = $mode == 1 || $mode == 2; // 2 == TRANSLATION_ENABLED
816
  }
817
  return $supported[$type];
818
}
819

    
820
/**
821
 * Get the node object for a menu item.
822
 */
823
function i18n_menu_item_get_node($item) {
824
  return isset($item['router_path']) && $item['router_path'] == 'node/%' ? node_load(arg(1, $item['link_path'])) : NULL;
825
}
826

    
827
/**
828
 * Implements hook_node_presave()
829
 *
830
 * Set menu link language to node language
831
 */
832
function i18n_menu_node_presave($node) {
833
  if (!empty($node->menu) && isset($node->language) && i18n_menu_node_supported_type($node->type)) {
834
    $node->menu['language'] = i18n_object_langcode($node, LANGUAGE_NONE);
835
    // Store node type with menu item so we can quickly access it later.
836
    $node->menu['options']['node_type'] = $node->type;
837
  }
838
}
839

    
840
/**
841
 * Implements hook_node_prepare_translation().
842
 */
843
function i18n_menu_node_prepare_translation($node) {
844
  if (empty($node->menu['mlid']) && !empty($node->translation_source)) {
845
    $tnode = $node->translation_source;
846
    // Prepare the tnode so the menu item will be available.
847
    node_object_prepare($tnode);
848
    $node->menu['link_title'] = $tnode->menu['link_title'];
849
    $node->menu['weight'] = $tnode->menu['weight'];
850
  }
851
}
852

    
853
/**
854
 * Process menu and menu item add/edit form submissions.
855
 *
856
 * @todo See where this fits
857
 */
858
/*
859
function i18n_menu_edit_item_form_submit($form, &$form_state) {
860
  $mid = menu_edit_item_save($form_state['values']);
861
  db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", array($form_state['values']['language'], $mid));
862
  return 'admin/build/menu';
863
}
864
*/
865

    
866
/**
867
 * Load translation set. Menu loading callback.
868
 */
869
function i18n_menu_translation_load($tsid) {
870
  return i18n_translation_set_load($tsid, 'menu_link');
871
}
872

    
873
/**
874
 * Load menu item by path, language
875
 */
876
function i18n_menu_link_load($path, $langcode) {
877
  $query = db_select('menu_links', 'ml');
878
  $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
879
  $query->fields('ml');
880
  // Weight should be taken from {menu_links}, not {menu_router}.
881
  $query->addField('ml', 'weight', 'link_weight');
882
  $query->fields('m');
883
  $query->condition('ml.link_path', $path);
884
  $query->condition('ml.language', $langcode);
885
  if ($item = $query->execute()->fetchAssoc()) {
886
    $item['weight'] = $item['link_weight'];
887
    _menu_link_translate($item);
888
    return $item;
889
  }
890
}
891

    
892
/**
893
 * Implements hook_query_TAG_alter() for features_menu_links.
894
 * Add needed fields to properly serialize localization information.
895
 */
896
function i18n_menu_query_features_menu_link_alter($query) {
897
  $query->fields('menu_links', array('language', 'customized'));
898
}
899

    
900
/**
901
 * Implements hook_init().
902
 */
903
function i18n_menu_init() {
904

    
905
  // The only way to override the default preferred menu link for a path is to
906
  // inject it into the static cache of the function menu_link_get_preferred().
907

    
908
  // The problem with the default implementation is that it does not take the
909
  // language of a menu link into account. Whe having different menu trees for
910
  // different menus, this means that the active trail will not work for all but
911
  // one language.
912

    
913
  // The code below is identical to the mentioned function except the added
914
  // language condition on the query.
915

    
916
  // TODO: Adding an alter tag to the query would allow to do this with a simple
917
  // hook_query_alter() implementation.
918

    
919
  $preferred_links = &drupal_static('menu_link_get_preferred');
920

    
921
  $path = $_GET['q'];
922

    
923
  // Look for the correct menu link by building a list of candidate paths,
924
  // which are ordered by priority (translated hrefs are preferred over
925
  // untranslated paths). Afterwards, the most relevant path is picked from
926
  // the menus, ordered by menu preference.
927
  $item = menu_get_item($path);
928
  $path_candidates = array();
929
  // 1. The current item href.
930
  $path_candidates[$item['href']] = $item['href'];
931
  // 2. The tab root href of the current item (if any).
932
  if ($item['tab_parent'] && ($tab_root = menu_get_item($item['tab_root_href']))) {
933
    $path_candidates[$tab_root['href']] = $tab_root['href'];
934
  }
935
  // 3. The current item path (with wildcards).
936
  $path_candidates[$item['path']] = $item['path'];
937
  // 4. The tab root path of the current item (if any).
938
  if (!empty($tab_root)) {
939
    $path_candidates[$tab_root['path']] = $tab_root['path'];
940
  }
941
  // Retrieve a list of menu names, ordered by preference.
942
  $menu_names = menu_get_active_menu_names();
943
  // Use an illegal menu name as the key for the preferred menu link.
944
  $selected_menu = MENU_PREFERRED_LINK;
945
  // Put the selected menu at the front of the list.
946
  array_unshift($menu_names, $selected_menu);
947

    
948
  $query = db_select('menu_links', 'ml', array('fetch' => PDO::FETCH_ASSOC));
949
  $query->leftJoin('menu_router', 'm', 'm.path = ml.router_path');
950
  $query->fields('ml');
951
  // Weight must be taken from {menu_links}, not {menu_router}.
952
  $query->addField('ml', 'weight', 'link_weight');
953
  $query->fields('m');
954
  $query->condition('ml.link_path', $path_candidates, 'IN');
955

    
956
  // Only look menu links with none or the current language.
957
  $query->condition('ml.language', array(LANGUAGE_NONE, i18n_language_interface()->language), 'IN');
958

    
959
  // Sort candidates by link path and menu name.
960
  $candidates = array();
961
  foreach ($query->execute() as $candidate) {
962
    $candidate['weight'] = $candidate['link_weight'];
963
    $candidates[$candidate['link_path']][$candidate['menu_name']] = $candidate;
964
    // Add any menus not already in the menu name search list.
965
    if (!in_array($candidate['menu_name'], $menu_names)) {
966
      $menu_names[] = $candidate['menu_name'];
967
    }
968
  }
969

    
970
  // Store the most specific link for each menu. Also save the most specific
971
  // link of the most preferred menu in $preferred_link.
972
  foreach ($path_candidates as $link_path) {
973
    if (isset($candidates[$link_path])) {
974
      foreach ($menu_names as $menu_name) {
975
        if (empty($preferred_links[$path][$menu_name]) && isset($candidates[$link_path][$menu_name])) {
976
          $candidate_item = $candidates[$link_path][$menu_name];
977
          $map = explode('/', $path);
978
          _menu_translate($candidate_item, $map);
979
          if ($candidate_item['access']) {
980
            $preferred_links[$path][$menu_name] = $candidate_item;
981
            if (empty($preferred_links[$path][MENU_PREFERRED_LINK])) {
982
              // Store the most specific link.
983
              $preferred_links[$path][MENU_PREFERRED_LINK] = $candidate_item;
984
            }
985
          }
986
        }
987
      }
988
    }
989
  }
990
}
991