i18n.module

Tracking 6.x-1.x branch
  1. drupal
    1. 5 contributions/i18n/i18n.module
    2. 6 contributions/i18n/i18n.module
    3. 7 contributions/i18n/i18n.module

Internationalization (i18n) module.

This module extends multilingual support being the base module for the i18n package.

  • Multilingual variables
  • Extended languages for nodes
  • Extended language API

@author Jose A. Reyero, 2004

Functions & methods

NameDescription
i18n_array_variable_getUtility. Get part of array variable.
i18n_array_variable_setUtility. Set part of array variable.
i18n_bootImplementation of hook_boot()
i18n_db_rewrite_sqlImplementation of hook_db_rewrite_sql().
i18n_db_rewrite_whereRewrites queries depending on rewriting mode.
i18n_default_languageReturns default language code.
i18n_elementsImplementation of hook_elements().
i18n_exitImplementation of hook_exit().
i18n_form_alterImplementation of hook_form_alter();
i18n_form_alter_settingsCheck for multilingual variables in form.
i18n_get_langThis one expects to be called first from common.inc
i18n_helpImplementation of hook_help().
i18n_initImplementation of hook_init().
i18n_language_listReturns language lists.
i18n_language_propertyGet language properties.
i18n_link_alterImplementation of hook_link_alter().
i18n_menuImplementation of hook_menu().
i18n_menu_alterImplementation of hook_menu_alter().
i18n_menu_edit_item_form_submitProcess menu and menu item add/edit form submissions.
i18n_nodeapiImplementation of hook_nodeapi().
i18n_node_get_langGet node language.
i18n_node_language_listGet allowed languages for node.
i18n_permImplementation of hook_perm().
i18n_preprocess_pageImplementation of hook_preprocess_page().
i18n_selection_modeSelection mode for content.
i18n_supported_languagesGet list of supported languages, native name.
i18n_textfield_processProcess callback for textfield elements.
i18n_themeImplementation of hook_theme().
i18n_translation_link_alterImplementation of hook_alter_translation_link().
i18n_userImplementation of hook_user().
i18n_variableGet list of multilingual variables or check whether a variable is multilingual
i18n_variable_delUnset a persistent multilingual variable.
i18n_variable_form_submitSave multilingual variables and remove them from form.
i18n_variable_getGet single multilingual variable
i18n_variable_initInitialization of multilingual variables.
i18n_variable_setSet a persistent language dependent variable.
_i18n_content_language_optionsList of language support modes for content.
_i18n_get_context_langGet language from context.
_i18n_initInitialize multilingual variables and use them for site_frontpage
_i18n_init_modeInitialize selection mode
_i18n_is_bootstrapCheck whether we are in bootstrap mode.
_i18n_language_selectHelper function to create language selector.
_i18n_variable_exitSave multilingual variables that may have been changed by other methods than settings pages.
_i18n_variable_initLoad language variables into array.

Constants

NameDescription
LANGUAGE_SUPPORT_EXTENDED
LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED
LANGUAGE_SUPPORT_NONE
LANGUAGE_SUPPORT_NORMAL

File

View source
  1. <?php
  2. /**
  3. * @file
  4. * Internationalization (i18n) module.
  5. *
  6. * This module extends multilingual support being the base module for the i18n package.
  7. * - Multilingual variables
  8. * - Extended languages for nodes
  9. * - Extended language API
  10. *
  11. * @author Jose A. Reyero, 2004
  12. */
  13. // Some constants. Language support modes for content
  14. define('LANGUAGE_SUPPORT_NONE', 0);
  15. define('LANGUAGE_SUPPORT_NORMAL', 1);
  16. define('LANGUAGE_SUPPORT_EXTENDED', 2);
  17. define('LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED', 3);
  18. /**
  19. * Implementation of hook_boot()
  20. *
  21. * Initialize variables, that will be used to decide on site_frontpage
  22. */
  23. function i18n_boot() {
  24. drupal_bootstrap(DRUPAL_BOOTSTRAP_LANGUAGE);
  25. _i18n_init();
  26. }
  27. /**
  28. * Implementation of hook_init().
  29. *
  30. * Special fix for site_frontpage, that may have been used before the language variables are loaded.
  31. */
  32. function i18n_init() {
  33. // If not in bootstrap, variable init. Otherwise we are serving a cached page so we don't need anything else.
  34. if (!_i18n_is_bootstrap()) {
  35. _i18n_init(TRUE);
  36. _i18n_init_mode();
  37. }
  38. }
  39. /**
  40. * Initialize multilingual variables and use them for site_frontpage
  41. *
  42. * Special fix for site_frontpage, that may have been used before the language variables are loaded.
  43. */
  44. function _i18n_init($check_frontpage = FALSE) {
  45. static $done, $default_frontpage;
  46. // Prevent this function from running twice;
  47. if (!isset($done)) {
  48. $done = TRUE;
  49. $default_frontpage = variable_get('site_frontpage', 'node');
  50. i18n_variable_init();
  51. }
  52. // We do aditional frontpage check if this has run after first bootstrap phase.
  53. // But if this runs in hook_boot we should be ok
  54. if ($check_frontpage && $default_frontpage != variable_get('site_frontpage', 'node') && $_GET['q'] == drupal_get_normal_path($default_frontpage)) {
  55. $_GET['q'] = drupal_get_normal_path(variable_get('site_frontpage', 'node'));
  56. }
  57. }
  58. /**
  59. * Initialize selection mode
  60. */
  61. function _i18n_init_mode() {
  62. if (i18n_selection_mode() != 'off') {
  63. // Node language when loading specific nodes or creating translations.
  64. if (arg(0) == 'node' ) {
  65. // We shouldn't call menu_get_object() because it may end up calling i18n_selection_mode(), see #614548
  66. // Also we better don't do the full node loading here because there may be missing modules (init)
  67. if (is_numeric(arg(1)) && (arg(2) == 'edit') && ($lang = i18n_node_get_lang(arg(1)))) {
  68. i18n_selection_mode('node', $lang);
  69. }
  70. elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language'])) {
  71. i18n_selection_mode('translation', db_escape_string($_GET['language']));
  72. }
  73. }
  74. elseif (arg(0) == 'admin') {
  75. // There are some exceptions for admin pages.
  76. if (arg(1) == 'content' && user_access('administer all languages')) {
  77. // No restrictions for administration pages.
  78. i18n_selection_mode('off');
  79. }
  80. elseif (arg(1) == 'build' && (arg(2) == 'menu-customize' || arg(2) == 'menu')) {
  81. // All nodes available when editing custom menu items.
  82. i18n_selection_mode('off');
  83. }
  84. }
  85. }
  86. }
  87. /**
  88. * Implementation of hook_help().
  89. */
  90. function i18n_help($path = 'admin/help#i18n', $arg) {
  91. switch ($path) {
  92. case 'admin/help#i18n' :
  93. $output = '<p>'. t('This module improves support for multilingual content in Drupal sites:') .'</p>';
  94. $output .= '<ul>';
  95. $output .= '<li>'. t('Shows content depending on page language.') .'</li>';
  96. $output .= '<li>'. t('Handles multilingual variables.') .'</li>';
  97. $output .= '<li>'. t('Extended language option for chosen content types. For these content types transations will be allowed for all defined languages, not only for enabled ones.') .'</li>';
  98. $output .= '<li>'. t('Provides a block for language selection and two theme functions: <em>i18n_flags</em> and <em>i18n_links</em>.') .'</li>';
  99. $output .= '</ul>';
  100. $output .= '<p>'. t('This is the base module for several others adding different features:') .'</p>';
  101. $output .= '<ul>';
  102. $output .= '<li>'. t('Multilingual menu items.') .'</li>';
  103. $output .= '<li>'. t('Multilingual taxonomy adds a language field for taxonomy vocabularies and terms.') .'</li>';
  104. $output .= '</ul>';
  105. $output .= '<p>'. t('For more information, see the online handbook entry for <a href="@i18n">Internationalization module</a>.', array('@i18n' => 'http://drupal.org/node/133977')) .'</p>';
  106. return $output;
  107. case 'admin/settings/language/i18n':
  108. $output = '<ul>';
  109. $output .= '<li>'. t('To enable multilingual support for specific content types go to <a href="@configure_content_types">configure content types</a>.', array('@configure_content_types' => url('admin/content/types'))) .'</li>';
  110. $output .= '</ul>';
  111. return $output;
  112. }
  113. }
  114. /**
  115. * Implementation of hook_menu().
  116. */
  117. function i18n_menu() {
  118. $items['admin/settings/language/i18n'] = array(
  119. 'title' => 'Multilingual system',
  120. 'description' => 'Configure extended options for multilingual content and translations.',
  121. 'page callback' => 'drupal_get_form',
  122. 'page arguments' => array('i18n_admin_settings'),
  123. 'access arguments' => array('administer site configuration'),
  124. 'file' => 'i18n.admin.inc',
  125. 'type' => MENU_LOCAL_TASK,
  126. 'weight' => 10,
  127. );
  128. $items['admin/settings/language/i18n/configure'] = array(
  129. 'title' => 'Options',
  130. 'description' => 'Configure extended options for multilingual content and translations.',
  131. //'page callback' => 'drupal_get_form',
  132. //'page arguments' => array('i18n_admin_settings'),
  133. //'access arguments' => array('administer site configuration'),
  134. 'file' => 'i18n.admin.inc',
  135. 'type' => MENU_DEFAULT_LOCAL_TASK,
  136. );
  137. $items['admin/settings/language/i18n/variables'] = array(
  138. 'title' => 'Variables',
  139. 'description' => 'Multilingual variables.',
  140. 'page callback' => 'drupal_get_form',
  141. 'page arguments' => array('i18n_admin_variables_form'),
  142. 'access arguments' => array('administer site configuration'),
  143. 'file' => 'i18n.admin.inc',
  144. 'type' => MENU_LOCAL_TASK,
  145. );
  146. // Autocomplete callback for nodes
  147. $items['i18n/node/autocomplete'] = array(
  148. 'title' => 'Node title autocomplete',
  149. 'page callback' => 'i18n_node_autocomplete',
  150. 'access arguments' => array('access content'),
  151. 'type' => MENU_CALLBACK,
  152. 'file' => 'i18n.pages.inc',
  153. );
  154. return $items;
  155. }
  156. /**
  157. * Implementation of hook_menu_alter().
  158. *
  159. * Take over the node translation page.
  160. */
  161. function i18n_menu_alter(&$items) {
  162. $items['node/%node/translate']['page callback'] = 'i18n_translation_node_overview';
  163. $items['node/%node/translate']['file'] = 'i18n.pages.inc';
  164. $items['node/%node/translate']['module'] = 'i18n';
  165. }
  166. /**
  167. * Implementation of hook_nodeapi().
  168. */
  169. function i18n_nodeapi(&$node, $op, $a3 = NULL, $a4 = NULL) {
  170. global $language;
  171. if (variable_get('language_content_type_' . $node->type, 0)) {
  172. // Set current language for new nodes if option enabled
  173. if ($op == 'prepare' && empty($node->nid) && empty($node->language) && variable_get('i18n_newnode_current_' . $node->type, 0)) {
  174. $node->language = $language->language;
  175. }
  176. }
  177. }
  178. /**
  179. * Implementation of hook_alter_translation_link().
  180. *
  181. * Handles links for extended language. The links will have current language.
  182. */
  183. function i18n_translation_link_alter(&$links, $path) {
  184. global $language;
  185. // Check for a node related path, and for its translations.
  186. if ((preg_match("!^node/([0-9]+)(/.+|)$!", $path, $matches)) && ($node = node_load((int)$matches[1])) && !empty($node->tnid)) {
  187. // make sure language support is set to LANGUAGE_SUPPORT_EXTENDED, so links
  188. // dont get added for LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED
  189. if (variable_get('i18n_node_'. $node->type, LANGUAGE_SUPPORT_NORMAL) == LANGUAGE_SUPPORT_EXTENDED) {
  190. $languages = language_list();
  191. $extended = array();
  192. foreach (translation_node_get_translations($node->tnid) as $langcode => $translation_node) {
  193. if (!isset($links[$langcode]) && isset($languages[$langcode])) {
  194. $extended[$langcode] = array(
  195. 'href' => 'node/'. $translation_node->nid . $matches[2],
  196. 'language' => $language,
  197. 'language_icon' => $languages[$langcode],
  198. 'title' => $languages[$langcode]->native,
  199. 'attributes' => array('class' => 'language-link'),
  200. );
  201. }
  202. }
  203. // This will run after languageicon module, so we add icon in case that one is enabled.
  204. if ($extended && function_exists('languageicons_translation_link_alter')) {
  205. languageicons_translation_link_alter($extended, $path);
  206. }
  207. $links = array_merge($links, $extended);
  208. }
  209. }
  210. }
  211. /**
  212. * Implementation of hook_link_alter().
  213. *
  214. * Handles links for extended languages. Sets current interface language.
  215. */
  216. function i18n_link_alter(&$links, $node) {
  217. global $language;
  218. $language_support = variable_get('i18n_node_'. $node->type, LANGUAGE_SUPPORT_NORMAL);
  219. // Hide node translation links.
  220. if (variable_get('i18n_hide_translation_links', 0) == 1) {
  221. foreach ($links as $module => $link) {
  222. if (strpos($module, 'node_translation') === 0) {
  223. unset($links[$module]);
  224. }
  225. }
  226. }
  227. if (!empty($node->tnid)) {
  228. foreach (array_keys(i18n_language_list('extended')) as $langcode) {
  229. $index = 'node_translation_'. $langcode;
  230. if (!empty($links[$index])) {
  231. if ($language_support != LANGUAGE_SUPPORT_EXTENDED && $links[$index]['language']->enabled == 0) {
  232. unset($links[$index]);
  233. }
  234. else {
  235. $links[$index]['language'] = $language;
  236. }
  237. }
  238. }
  239. }
  240. }
  241. /**
  242. * Implementation of hook_user().
  243. *
  244. * Switch to user's language after login.
  245. */
  246. function i18n_user($op, &$edit, &$account, $category = NULL) {
  247. if ($op == 'login' && $account->language) {
  248. i18n_get_lang($account->language);
  249. }
  250. }
  251. /**
  252. * Implementation of hook_elements().
  253. *
  254. * Add a process callback for textfields.
  255. */
  256. function i18n_elements() {
  257. $type = array();
  258. $type['textfield'] = array('#process' => array('i18n_textfield_process'));
  259. return $type;
  260. }
  261. /**
  262. * Process callback for textfield elements.
  263. *
  264. * When editing or translating a node, set Javascript to rewrite autocomplete
  265. * paths to use the node language prefix rather than the current content one.
  266. */
  267. function i18n_textfield_process($element) {
  268. global $language;
  269. static $sent = FALSE;
  270. // Ensure we send the Javascript only once.
  271. if (!$sent && isset($element['#autocomplete_path']) && !empty($element['#autocomplete_path']) && variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) != LANGUAGE_NEGOTIATION_NONE) {
  272. // Add a JS file for node forms.
  273. // Determine if we are either editing or translating an existing node.
  274. // We can't act on regular node creation because we don't have a specified
  275. // node language.
  276. $node_edit = $node = menu_get_object() && arg(2) == 'edit' && isset($node->language) && !empty($node->language);
  277. $node_translate = arg(0) == 'node' && arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language']);
  278. if ($node_edit || $node_translate) {
  279. $node_language = $node_edit ? $node->language : $_GET['language'];
  280. // Only needed if the node language is different from the interface one.
  281. if ($node_language != $language->language) {
  282. $languages = language_list();
  283. if (isset($languages[$node_language])) {
  284. drupal_add_js(drupal_get_path('module', 'i18n') . '/i18n.js');
  285. // Pass the interface and content language base paths.
  286. // Remove any trailing forward slash. Doing so prevents a mismatch
  287. // that occurs when a language has no prefix and hence gets a path
  288. // with a trailing forward slash.
  289. $interface = rtrim(url('', array('absolute' => TRUE)), '/');
  290. $content = rtrim(url('', array('absolute' => TRUE, 'language' => $languages[$node_language])), '/');
  291. $data = array('interface_path' => $interface, 'content_path' => $content);
  292. drupal_add_js(array('i18n' => $data), 'setting');
  293. }
  294. }
  295. }
  296. $sent = TRUE;
  297. }
  298. return $element;
  299. }
  300. /**
  301. * Simple i18n API
  302. */
  303. /**
  304. * Get language properties.
  305. *
  306. * @param $code
  307. * Language code.
  308. * @param $property
  309. * It may be 'name', 'native', 'ltr'...
  310. */
  311. function i18n_language_property($code, $property) {
  312. $languages = language_list();
  313. return isset($languages[$code]->$property) ? $languages[$code]->$property : NULL;
  314. }
  315. /**
  316. * Get node language.
  317. */
  318. function i18n_node_get_lang($nid, $default = '') {
  319. $lang = db_result(db_query('SELECT language FROM {node} WHERE nid = %d', $nid));
  320. return $lang ? $lang : $default ;
  321. }
  322. /**
  323. * Get allowed languages for node.
  324. *
  325. * This allows node types to define its own language list implementing hook 'language_list'.
  326. *
  327. * @param $node
  328. * Node to retrieve language list for.
  329. * @param $translate
  330. * Only languages available for translation. Filter out existing translations.
  331. */
  332. function i18n_node_language_list($node, $translate = FALSE) {
  333. // Check if the node module manages its own language list.
  334. $languages = node_invoke($node, 'language_list', $translate);
  335. if (!$languages) {
  336. if (variable_get('i18n_node_'. $node->type, 0) >= LANGUAGE_SUPPORT_EXTENDED) {
  337. $languages = locale_language_list('name', TRUE); // All defined languages
  338. }
  339. else {
  340. $languages = locale_language_list(); // All enabled languages
  341. }
  342. if ($translate && isset($node->tnid) && $node->tnid && ($translations = translation_node_get_translations($node->tnid))) {
  343. unset($translations[$node->language]);
  344. foreach (array_keys($translations) as $langcode) {
  345. unset($languages[$langcode]);
  346. }
  347. }
  348. // Language may be locked for this node type, restrict options to current one
  349. if (variable_get('i18n_lock_node_' . $node->type, 0) && !empty($node->language) && !empty($languages[$node->language])) {
  350. $languages = array($node->language => $languages[$node->language]);
  351. }
  352. // Check language required for this type (no language neutral)
  353. elseif (!variable_get('i18n_required_node_' . $node->type, 0)) {
  354. $languages = array('' => t('Language neutral')) + $languages;
  355. }
  356. }
  357. return $languages;
  358. }
  359. /**
  360. * Selection mode for content.
  361. *
  362. * Warning: when used with params they need to be escaped, as some values are thrown directly in queries.
  363. *
  364. * Allows several modes for query rewriting and to change them programatically.
  365. * off = No language conditions inserted.
  366. * simple = Only current language and no language.
  367. * mixed = Only current and default languages.
  368. * strict = Only current language.
  369. * default = Only default language.
  370. * user = User defined, in the module's settings page.
  371. * params = Gets the stored params.
  372. * reset = Returns to previous.
  373. * custom = add custom where clause, like "%alias.language = 'en'".
  374. */
  375. function i18n_selection_mode($mode = NULL, $params = NULL) {
  376. static $current_mode;
  377. static $current_value = '';
  378. static $store = array();
  379. // Initialization, first time this runs
  380. if (!isset($current_mode)) {
  381. $current_mode = variable_get('i18n_selection_mode', 'simple');
  382. }
  383. if (!$mode) {
  384. return $current_mode;
  385. }
  386. elseif ($mode == 'params') {
  387. return $current_value;
  388. }
  389. elseif ($mode == 'reset') {
  390. list($current_mode, $current_value) = array_pop($store);
  391. }
  392. else {
  393. array_push($store, array($current_mode, $current_value));
  394. $current_mode = $mode;
  395. $current_value = $params;
  396. }
  397. }
  398. /**
  399. * Implementation of hook_db_rewrite_sql().
  400. *
  401. * Rewrite node queries so language selection options are enforced.
  402. */
  403. function i18n_db_rewrite_sql($query, $primary_table, $primary_key, $args = array()) {
  404. // If mode is 'off' = no rewrite, we cannot return any empty 'where' string so check here.
  405. $mode = i18n_selection_mode();
  406. if ($mode == 'off') return;
  407. // Disable language conditions for views.
  408. if (array_key_exists('view', $args)) return;
  409. switch ($primary_table) {
  410. case 'n':
  411. case 'node':
  412. // No rewrite for queries with subselect ? (views count queries).
  413. // @ TO DO Actually these queries look un-rewrittable, check with other developers.
  414. if (preg_match("/FROM \(SELECT/", $query)) return;
  415. // No rewrite for translation module queries.
  416. if (preg_match("/.*FROM {node} $primary_table WHERE.*$primary_table\.tnid/", $query)) return;
  417. // When loading specific nodes, language conditions shouldn't apply.
  418. if (preg_match("/WHERE.*\s$primary_table.nid\s*=\s*(\d|%d)/", $query)) return;
  419. // If language conditions already there, get out.
  420. if (preg_match("/i18n/", $query)) return;
  421. // Mixed mode is a bit more complex, we need to join in one more table
  422. // and add some more conditions, but only if language is not default.
  423. if ($mode == 'mixed') {
  424. $result['where'] = i18n_db_rewrite_where($primary_table, 'node', 'simple');
  425. if (i18n_get_lang() != i18n_default_language()) {
  426. $result['join'] = "LEFT JOIN {node} i18n ON $primary_table.tnid > 0 AND $primary_table.tnid = i18n.tnid AND i18n.language = '". i18n_get_lang() ."'";
  427. // So we show also nodes that have default language.
  428. $result['where'] .= " OR $primary_table.language = '". i18n_default_language() ."' AND i18n.nid IS NULL";
  429. }
  430. }
  431. else {
  432. $result['where'] = i18n_db_rewrite_where($primary_table, 'node', $mode);
  433. }
  434. return $result;
  435. }
  436. }
  437. /**
  438. * Rewrites queries depending on rewriting mode.
  439. */
  440. function i18n_db_rewrite_where($alias, $type, $mode = NULL) {
  441. if (!$mode) {
  442. // Some exceptions for query rewrites.
  443. $mode = i18n_selection_mode();
  444. }
  445. // Get languages to simplify query building.
  446. $current = i18n_get_lang();
  447. $default = i18n_default_language();
  448. if ($mode == 'strict' && $type != 'node') {
  449. // Special case. Selection mode is 'strict' but this should be only for node queries.
  450. $mode = 'simple';
  451. }
  452. elseif ($mode == 'mixed' && $current == $default) {
  453. // If mode is mixed but current = default, is the same as 'simple'.
  454. $mode = 'simple';
  455. }
  456. switch ($mode) {
  457. case 'off':
  458. return '';
  459. case 'simple':
  460. return "$alias.language ='$current' OR $alias.language ='' OR $alias.language IS NULL" ;
  461. case 'mixed':
  462. return "$alias.language ='$current' OR $alias.language ='$default' OR $alias.language ='' OR $alias.language IS NULL" ;
  463. case 'strict':
  464. return "$alias.language ='$current'" ;
  465. case 'node':
  466. case 'translation':
  467. return "$alias.language ='". i18n_selection_mode('params') ."' OR $alias.language ='' OR $alias.language IS NULL" ;
  468. case 'default':
  469. return "$alias.language ='$default' OR $alias.language ='' OR $alias.language IS NULL" ;
  470. case 'custom':
  471. return str_replace('%alias', $alias, i18n_selection_mode('params'));
  472. }
  473. }
  474. /**
  475. * Implementation of hook_preprocess_page().
  476. *
  477. * Add the language code to the classes for the <body> tag. Unfortunately, some
  478. * themes will not respect the variable we're modifying to achieve this - in
  479. * particular, Garland and Minelli do not.
  480. */
  481. function i18n_preprocess_page(&$variables) {
  482. if (isset($variables['body_classes'])) {
  483. global $language;
  484. $variables['body_classes'] .= ' i18n-' . $language->language;
  485. }
  486. }
  487. /**
  488. * Implementation of hook_exit().
  489. */
  490. function i18n_exit() {
  491. _i18n_variable_exit();
  492. }
  493. /**
  494. * Implementation of hook_form_alter();
  495. *
  496. * This is the place to add language fields to all forms.
  497. */
  498. function i18n_form_alter(&$form, $form_state, $form_id) {
  499. global $language;
  500. switch ($form_id) {
  501. case 'node_type_form':
  502. $disabled = !variable_get('language_content_type_'. $form['#node_type']->type, 0);
  503. $form['i18n'] = array(
  504. '#type' => 'fieldset',
  505. '#title' => t('Multilanguage options'),
  506. '#collapsible' => TRUE,
  507. '#collapsed' => TRUE,
  508. '#description' => t('Extended multilingual options provided by Internationalization module.'),
  509. '#disabled' => $disabled,
  510. );
  511. // Add disabled message
  512. if ($disabled) {
  513. $form['i18n']['#description'] .= ' <em>' . t('These will be available only when you enable Multilingual support in Workflow settings above.') . '</em>';
  514. }
  515. // Some settings about node languages
  516. $form['i18n']['options'] = array(
  517. '#title' => t('Options for node language'),
  518. '#type' => 'fieldset',
  519. '#disabled' => $disabled,
  520. );
  521. $form['i18n']['options']['i18n_newnode_current'] = array(
  522. '#type' => 'checkbox',
  523. '#title' => t('Set current language as default for new content.'),
  524. '#default_value' => variable_get('i18n_newnode_current_' . $form['#node_type']->type, 0),
  525. '#disabled' => $disabled,
  526. );
  527. $form['i18n']['options']['i18n_required_node'] = array(
  528. '#type' => 'checkbox',
  529. '#title' => t('Require language (Do not allow Language Neutral).'),
  530. '#default_value' => variable_get('i18n_required_node_' . $form['#node_type']->type, 0),
  531. '#disabled' => $disabled,
  532. );
  533. $form['i18n']['options']['i18n_lock_node'] = array(
  534. '#type' => 'checkbox',
  535. '#title' => t('Lock language (Cannot be changed).'),
  536. '#default_value' => variable_get('i18n_lock_node_' . $form['#node_type']->type, 0),
  537. '#disabled' => $disabled,
  538. );
  539. // Add extended language support option to content type form.
  540. $form['i18n']['i18n_node'] = array(
  541. '#type' => 'radios',
  542. '#title' => t('Extended language support'),
  543. '#default_value' => variable_get('i18n_node_'. $form['#node_type']->type, LANGUAGE_SUPPORT_NORMAL),
  544. '#options' => _i18n_content_language_options(),
  545. '#description' => t('If enabled, all defined languages will be allowed for this content type in addition to only enabled ones. This is useful to have more languages for content than for the interface.'),
  546. '#disabled' => $disabled,
  547. );
  548. break;
  549. default:
  550. // Extensions for node edit forms
  551. if (isset($form['#id']) && $form['#id'] == 'node-form') {
  552. if (isset($form['#node']->type)) {
  553. if (variable_get('language_content_type_'. $form['#node']->type, 0)) {
  554. if (!empty($form['language']['#options'])) {
  555. $form['language']['#options'] = i18n_node_language_list($form['#node'], TRUE);
  556. }
  557. }
  558. elseif (!isset($form['#node']->nid)) {
  559. // Set language to empty for not multilingual nodes when creating
  560. $form['language'] = array('#type' => 'value', '#value' => '');
  561. }
  562. }
  563. }
  564. // Multilingual variables in settings form.
  565. if (isset($form['#theme']) && $form['#theme'] == 'system_settings_form' && $variables = i18n_variable()) {
  566. if ($i18n_variables = i18n_form_alter_settings($form, $variables)) {
  567. array_unshift($form['#submit'], 'i18n_variable_form_submit');
  568. $form['#i18n_variables'] = $i18n_variables;
  569. }
  570. }
  571. }
  572. }
  573. /**
  574. * Implementation of hook_perm().
  575. *
  576. * Permissions defined
  577. * - administer all languages
  578. * Disables language conditions for administration pages, so the user can view objects for all languages at the same time.
  579. * This applies for: menu items, taxonomy
  580. * - administer translations
  581. * Will allow to add/remove existing nodes to/from translation sets.
  582. */
  583. function i18n_perm() {
  584. return array('administer all languages', 'administer translations');
  585. }
  586. /**
  587. * Implementation of hook_theme().
  588. */
  589. function i18n_theme() {
  590. return array(
  591. 'i18n_node_select_translation' => array(
  592. 'arguments' => array('element' => NULL),
  593. 'file' => 'i18n.pages.inc',
  594. ),
  595. );
  596. }
  597. /**
  598. * Process menu and menu item add/edit form submissions.
  599. */
  600. function i18n_menu_edit_item_form_submit($form, &$form_state) {
  601. $mid = menu_edit_item_save($form_state['values']);
  602. db_query("UPDATE {menu} SET language = '%s' WHERE mid = %d", $form_state['values']['language'], $mid);
  603. return 'admin/build/menu';
  604. }
  605. /**
  606. * Check for multilingual variables in form.
  607. */
  608. function i18n_form_alter_settings(&$form, &$variables) {
  609. $result = array();
  610. foreach (element_children($form) as $field) {
  611. if (count(element_children($form[$field])) && empty($form[$field]['#tree'])) {
  612. $result += i18n_form_alter_settings($form[$field], $variables);
  613. }
  614. elseif (in_array($field, $variables)) {
  615. // Add form field class: i18n-variable
  616. $form[$field]['#attributes']['class'] = !empty($form[$field]['#attributes']['class']) ? $form[$field]['#attributes']['class'] . ' i18n-variable' : 'i18n-variable';
  617. $form[$field]['#description'] = !empty($form[$field]['#description']) ? $form[$field]['#description'] : '';
  618. $form[$field]['#description'] .= ' <strong>'. t('This is a multilingual variable.') .'</strong>';
  619. // Addd field => name to result
  620. $result[$field] = !empty($form[$field]['#title']) ? $form[$field]['#title'] : $field;
  621. }
  622. }
  623. return $result;
  624. }
  625. /**
  626. * Save multilingual variables and remove them from form.
  627. */
  628. function i18n_variable_form_submit($form, &$form_state) {
  629. $op = isset($form_state['values']['op']) ? $form_state['values']['op'] : '';
  630. $variables = i18n_variable();
  631. $language = i18n_get_lang();
  632. $is_default = $language == language_default('language');
  633. foreach ($form_state['values'] as $key => $value) {
  634. if (i18n_variable($key)) {
  635. if ($op == t('Reset to defaults')) {
  636. i18n_variable_del($key, $language);
  637. }
  638. else {
  639. if (is_array($value) && isset($form_state['values']['array_filter'])) {
  640. $value = array_keys(array_filter($value));
  641. }
  642. i18n_variable_set($key, $value, $language);
  643. }
  644. // If current is default language, we allow global (without language) variables to be set too
  645. if (!$is_default) {
  646. unset($form_state['values'][$key]);
  647. }
  648. }
  649. }
  650. // The form will go now through system_settings_form_submit()
  651. }
  652. /**
  653. * Initialization of multilingual variables.
  654. *
  655. * @param $langcode
  656. * Language to retrieve variables. Defaults to current language.
  657. */
  658. function i18n_variable_init($langcode = NULL) {
  659. global $conf;
  660. $langcode = $langcode ? $langcode : i18n_get_lang();
  661. if ($variables = _i18n_variable_init($langcode)) {
  662. $conf = array_merge($conf, $variables);
  663. }
  664. }
  665. /**
  666. * Get language from context.
  667. */
  668. function _i18n_get_context_lang() {
  669. // Node language when loading specific nodes or creating translations.
  670. if (arg(0) == 'node' ) {
  671. if (($node = menu_get_object('node')) && $node->language) {
  672. return $node->language;
  673. }
  674. elseif (arg(1) == 'add' && !empty($_GET['translation']) && !empty($_GET['language'])) {
  675. return $_GET['language'];
  676. }
  677. }
  678. }
  679. /**
  680. * Helper function to create language selector.
  681. */
  682. function _i18n_language_select($value ='', $description ='', $weight = -20, $languages = NULL) {
  683. $languages = $languages ? $languages : locale_language_list();
  684. return array(
  685. '#type' => 'select',
  686. '#title' => t('Language'),
  687. '#default_value' => $value,
  688. '#options' => array_merge(array('' => ''), $languages),
  689. '#description' => $description,
  690. '#weight' => $weight,
  691. );
  692. }
  693. /**
  694. * Load language variables into array.
  695. */
  696. function _i18n_variable_init($langcode) {
  697. global $i18n_conf;
  698. if (!isset($i18n_conf[$langcode])) {
  699. $cacheid = 'variables:'. $langcode;
  700. if ($cached = cache_get($cacheid)) {
  701. $i18n_conf[$langcode] = $cached->data;
  702. }
  703. else {
  704. $result = db_query("SELECT * FROM {i18n_variable} WHERE language = '%s'", $langcode);
  705. $i18n_conf[$langcode] = array();
  706. while ($variable = db_fetch_object($result)) {
  707. $i18n_conf[$langcode][$variable->name] = unserialize($variable->value);
  708. }
  709. cache_set($cacheid, $i18n_conf[$langcode]);
  710. }
  711. }
  712. return $i18n_conf[$langcode];
  713. }
  714. /**
  715. * Save multilingual variables that may have been changed by other methods than settings pages.
  716. */
  717. function _i18n_variable_exit() {
  718. global $conf, $i18n_conf;
  719. $langcode = i18n_get_lang();
  720. if (isset($i18n_conf[$langcode])) {
  721. $refresh = FALSE;
  722. // Rewritten because array_diff_assoc may fail with array variables.
  723. foreach (i18n_variable() as $name) {
  724. if (isset($conf[$name]) && isset($i18n_conf[$langcode][$name]) && $conf[$name] != $i18n_conf[$langcode][$name]) {
  725. $refresh = TRUE;
  726. $i18n_conf[$langcode][$name] = $conf[$name];
  727. db_query("DELETE FROM {i18n_variable} WHERE name='%s' AND language='%s'", $name, $langcode);
  728. db_query("INSERT INTO {i18n_variable} (language, name, value) VALUES('%s', '%s', '%s')", $langcode, $name, serialize($conf[$name]));
  729. }
  730. }
  731. if ($refresh) {
  732. cache_set('variables:'. $langcode, $i18n_conf[$langcode]);
  733. }
  734. }
  735. }
  736. /**
  737. * Check whether we are in bootstrap mode.
  738. */
  739. function _i18n_is_bootstrap() {
  740. return !function_exists('drupal_get_headers');
  741. }
  742. /**
  743. * Drupal 6, backwards compatibility layer.
  744. *
  745. * @ TO DO Fully upgrade all the modules and remove
  746. */
  747. /**
  748. * This one expects to be called first from common.inc
  749. */
  750. function i18n_get_lang() {
  751. global $language;
  752. return $language->language;
  753. }
  754. /**
  755. * @defgroup i18n_api Extended language API
  756. * @{
  757. * This is an extended language API to be used by modules in i18n package.
  758. */
  759. /**
  760. * Returns language lists.
  761. */
  762. function i18n_language_list($type = 'enabled', $field = 'name') {
  763. switch ($type) {
  764. case 'enabled':
  765. return locale_language_list($field);
  766. case 'extended':
  767. $enabled = locale_language_list($field);
  768. $defined = locale_language_list($field, TRUE);
  769. return array_diff_assoc($defined, $enabled);
  770. }
  771. }
  772. /**
  773. * Returns default language code.
  774. */
  775. function i18n_default_language() {
  776. return language_default('language');
  777. }
  778. /**
  779. * Get list of supported languages, native name.
  780. *
  781. * @param $all
  782. * TRUE to get all defined languages.
  783. */
  784. function i18n_supported_languages($all = FALSE) {
  785. return locale_language_list('native', $all);
  786. }
  787. /**
  788. * Get list of multilingual variables or check whether a variable is multilingual
  789. */
  790. function i18n_variable($name = NULL) {
  791. $variables = variable_get('i18n_variables', array());
  792. return $name ? in_array($name, $variables) : $variables;
  793. }
  794. /**
  795. * Set a persistent language dependent variable.
  796. *
  797. * @param $name
  798. * The name of the variable to set.
  799. * @param $value
  800. * The value to set. This can be any PHP data type; these functions take care
  801. * of serialization as necessary.
  802. * @param $langcode
  803. * Language code.
  804. */
  805. function i18n_variable_set($name, $value, $langcode) {
  806. global $conf, $i18n_conf;
  807. $serialized_value = serialize($value);
  808. db_query("UPDATE {i18n_variable} SET value = '%s' WHERE name = '%s' AND language = '%s'", $serialized_value, $name, $langcode);
  809. if (!db_affected_rows()) {
  810. @db_query("INSERT INTO {i18n_variable} (name, language, value) VALUES ('%s', '%s', '%s')", $name, $langcode, $serialized_value);
  811. }
  812. cache_clear_all('variables:'. $langcode, 'cache');
  813. $i18n_conf[$langcode][$name] = $value;
  814. if ($langcode == i18n_get_lang()) {
  815. $conf[$name] = $value;
  816. }
  817. }
  818. /**
  819. * Get single multilingual variable
  820. */
  821. function i18n_variable_get($name, $langcode, $default = NULL) {
  822. if ($variables = _i18n_variable_init($langcode)) {
  823. return isset($variables[$name]) ? $variables[$name] : $default;
  824. }
  825. else {
  826. return $default;
  827. }
  828. }
  829. /**
  830. * Unset a persistent multilingual variable.
  831. *
  832. * @param $name
  833. * The name of the variable to undefine.
  834. * @param $langcode
  835. * Optional language code. If not set it will delete the variable for all languages.
  836. */
  837. function i18n_variable_del($name, $langcode = NULL) {
  838. global $conf, $i18n_conf;
  839. if ($langcode) {
  840. db_query("DELETE FROM {i18n_variable} WHERE name = '%s' AND language='%s'", $name, $langcode);
  841. cache_clear_all('variables:'. $langcode, 'cache');
  842. unset($i18n_conf[$langcode][$name]);
  843. // If current language, unset also global conf
  844. if ($langcode == i18n_get_lang()) {
  845. unset($conf[$name]);
  846. }
  847. }
  848. else {
  849. db_query("DELETE FROM {i18n_variable} WHERE name = '%s'", $name);
  850. if (db_affected_rows()) {
  851. cache_clear_all('variables:', 'cache', TRUE);
  852. if (is_array($i18n_conf)) {
  853. foreach (array_keys($i18n_conf) as $lang) {
  854. unset($i18n_conf[$lang][$name]);
  855. }
  856. }
  857. }
  858. }
  859. }
  860. /**
  861. * Utility. Get part of array variable.
  862. */
  863. function i18n_array_variable_get($name, $element, $default = NULL) {
  864. if (($values = variable_get($name, array())) && isset($values[$element])) {
  865. return $values[$element];
  866. }
  867. else {
  868. return $default;
  869. }
  870. }
  871. /**
  872. * Utility. Set part of array variable.
  873. */
  874. function i18n_array_variable_set($name, $element, $value) {
  875. $values = variable_get($name, array());
  876. $values[$element] = $value;
  877. variable_set($name, $values);
  878. }
  879. /**
  880. * @} End of "defgroup i18n_api".
  881. */
  882. /**
  883. * List of language support modes for content.
  884. */
  885. function _i18n_content_language_options() {
  886. return array(
  887. LANGUAGE_SUPPORT_NORMAL => t('Normal - All enabled languages will be allowed.'),
  888. LANGUAGE_SUPPORT_EXTENDED => t('Extended - All defined languages will be allowed.'),
  889. LANGUAGE_SUPPORT_EXTENDED_NOT_DISPLAYED => t('Extended, but not displayed - All defined languages will be allowed for input, but not displayed in links.'),
  890. );
  891. }