Projet

Général

Profil

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

root / drupal7 / sites / all / modules / i18n / i18n_menu / i18n_menu.module @ fc3d89c3

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
    $item['i18n_tsid'] = 0;
184
  }
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
/**
195
 * Implements hook_menu_link_update().
196
 */
197
function i18n_menu_menu_link_update($link) {
198
  // Stores the fields to update.
199
  $fields = array();
200
  $menu_mode = i18n_menu_mode($link['menu_name']);
201

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
639
  $form += i18n_translation_mode_element('menu', $i18n_mode, $langcode);
640
}
641

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

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

    
673
      // Maybe we should disable the 'link_path' and 'parent' form elements?
674
      // $form['link_path']['#disabled'] = TRUE;
675
      // $form['parent']['#disabled'] = TRUE;
676

    
677
      $form['i18n']['language']['#default_value'] = $_GET['target'];
678
      $form['i18n']['language']['#disabled'] = TRUE;
679

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

    
903
/**
904
 * Implements hook_init().
905
 */
906
function i18n_menu_init() {
907

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

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

    
916
  // The code below is identical to the mentioned function except the added
917
  // language condition on the query.
918

    
919
  // TODO: Adding an alter tag to the query would allow to do this with a simple
920
  // hook_query_alter() implementation.
921

    
922
  $preferred_links = &drupal_static('menu_link_get_preferred');
923

    
924
  $path = $_GET['q'];
925

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

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

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

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

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