- Patch #540294 by Gábor Hojtsy, clemens.tolboom, Xano: node language settings from...
[project/drupal.git] / core / modules / locale / locale.test
1 <?php
2
3 /**
4 * @file
5 * Tests for locale.module.
6 *
7 * The test file includes:
8 * - a functional test for the language configuration forms;
9 * - functional tests for the translation functionalities, including searching;
10 * - a functional test for the PO files import feature, including validation;
11 * - functional tests for translations and templates export feature;
12 * - functional tests for the uninstall process;
13 * - a functional test for the language switching feature;
14 * - a functional test for a user's ability to change their default language;
15 * - a functional test for configuring a different path alias per language;
16 * - a functional test for configuring a different path alias per language;
17 * - a functional test for multilingual support by content type and on nodes.
18 * - a functional test for multilingual fields.
19 * - a functional test for comment language.
20 * - a functional test fot language types/negotiation info.
21 */
22
23
24 /**
25 * Functional tests for language configuration's effect on negotiation setup.
26 */
27 class LocaleConfigurationTest extends DrupalWebTestCase {
28 public static function getInfo() {
29 return array(
30 'name' => 'Language negotiation autoconfiguration',
31 'description' => 'Adds and configures languages to check negotiation changes.',
32 'group' => 'Locale',
33 );
34 }
35
36 function setUp() {
37 parent::setUp('locale');
38 }
39
40 /**
41 * Functional tests for adding, editing and deleting languages.
42 */
43 function testLanguageConfiguration() {
44 global $base_url;
45
46 // User to add and remove language.
47 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
48 $this->drupalLogin($admin_user);
49
50 // Check if the Default English language has no path prefix.
51 $this->drupalGet('admin/config/regional/language/detection/url');
52 $this->assertFieldByXPath('//input[@name="prefix[en]"]', '', t('Default English has no path prefix.'));
53
54 // Add predefined language.
55 $edit = array(
56 'predefined_langcode' => 'fr',
57 );
58 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
59 $this->assertText('fr', t('Language added successfully.'));
60 $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
61
62 // Check if the Default English language has no path prefix.
63 $this->drupalGet('admin/config/regional/language/detection/url');
64 $this->assertFieldByXPath('//input[@name="prefix[en]"]', '', t('Default English has no path prefix.'));
65 // Check if French has a path prefix.
66 $this->drupalGet('admin/config/regional/language/detection/url');
67 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', t('French has a path prefix.'));
68
69 // Check if we can change the default language.
70 $this->drupalGet('admin/config/regional/language');
71 $this->assertFieldChecked('edit-site-default-en', t('English is the default language.'));
72 // Change the default language.
73 $edit = array(
74 'site_default' => 'fr',
75 );
76 $this->drupalPost(NULL, $edit, t('Save configuration'));
77 $this->assertNoFieldChecked('edit-site-default-en', t('Default language updated.'));
78 $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
79
80 // Check if a valid language prefix is added afrer changing the default
81 // language.
82 $this->drupalGet('admin/config/regional/language/detection/url');
83 $this->assertFieldByXPath('//input[@name="prefix[en]"]', 'en', t('A valid path prefix has been added to the previous default language.'));
84 // Check if French still has a path prefix.
85 $this->drupalGet('admin/config/regional/language/detection/url');
86 $this->assertFieldByXPath('//input[@name="prefix[fr]"]', 'fr', t('French still has a path prefix.'));
87 }
88 }
89
90 /**
91 * Functional tests for JavaScript parsing for translatable strings.
92 */
93 class LocaleJavascriptTranslationTest extends DrupalWebTestCase {
94 public static function getInfo() {
95 return array(
96 'name' => 'Javascript translation',
97 'description' => 'Tests parsing js files for translatable strings',
98 'group' => 'Locale',
99 );
100 }
101
102 function setUp() {
103 parent::setUp('locale', 'locale_test');
104 }
105
106 function testFileParsing() {
107
108 $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js';
109
110 // Parse the file to look for source strings.
111 _locale_parse_js_file($filename);
112
113 // Get all of the source strings that were found.
114 $source_strings = db_select('locales_source', 's')
115 ->fields('s', array('source', 'context'))
116 ->condition('s.location', $filename)
117 ->execute()
118 ->fetchAllKeyed();
119
120 // List of all strings that should be in the file.
121 $test_strings = array(
122 "Standard Call t" => '',
123 "Whitespace Call t" => '',
124
125 "Single Quote t" => '',
126 "Single Quote \\'Escaped\\' t" => '',
127 "Single Quote Concat strings t" => '',
128
129 "Double Quote t" => '',
130 "Double Quote \\\"Escaped\\\" t" => '',
131 "Double Quote Concat strings t" => '',
132
133 "Context !key Args t" => "Context string",
134
135 "Context Unquoted t" => "Context string unquoted",
136 "Context Single Quoted t" => "Context string single quoted",
137 "Context Double Quoted t" => "Context string double quoted",
138
139 "Standard Call plural" => '',
140 "Standard Call @count plural" => '',
141 "Whitespace Call plural" => '',
142 "Whitespace Call @count plural" => '',
143
144 "Single Quote plural" => '',
145 "Single Quote @count plural" => '',
146 "Single Quote \\'Escaped\\' plural" => '',
147 "Single Quote \\'Escaped\\' @count plural" => '',
148
149 "Double Quote plural" => '',
150 "Double Quote @count plural" => '',
151 "Double Quote \\\"Escaped\\\" plural" => '',
152 "Double Quote \\\"Escaped\\\" @count plural" => '',
153
154 "Context !key Args plural" => "Context string",
155 "Context !key Args @count plural" => "Context string",
156
157 "Context Unquoted plural" => "Context string unquoted",
158 "Context Unquoted @count plural" => "Context string unquoted",
159 "Context Single Quoted plural" => "Context string single quoted",
160 "Context Single Quoted @count plural" => "Context string single quoted",
161 "Context Double Quoted plural" => "Context string double quoted",
162 "Context Double Quoted @count plural" => "Context string double quoted",
163 );
164
165 // Assert that all strings were found properly.
166 foreach ($test_strings as $str => $context) {
167 $args = array('%source' => $str, '%context' => $context);
168
169 // Make sure that the string was found in the file.
170 $this->assertTrue(isset($source_strings[$str]), t("Found source string: %source", $args));
171
172 // Make sure that the proper context was matched.
173 $this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ? t("Context for %source is %context", $args) : t("Context for %source is blank", $args));
174 }
175
176 $this->assertEqual(count($source_strings), count($test_strings), t("Found correct number of source strings."));
177 }
178 }
179 /**
180 * Functional test for string translation and validation.
181 */
182 class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
183 public static function getInfo() {
184 return array(
185 'name' => 'String translate, search and validate',
186 'description' => 'Adds a new locale and translates its name. Checks the validation of translation strings and search results.',
187 'group' => 'Locale',
188 );
189 }
190
191 function setUp() {
192 parent::setUp('locale');
193 }
194
195 /**
196 * Adds a language and tests string translation by users with the appropriate permissions.
197 */
198 function testStringTranslation() {
199 global $base_url;
200
201 // User to add and remove language.
202 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
203 // User to translate and delete string.
204 $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
205 // Code for the language.
206 $langcode = 'xx';
207 // The English name for the language. This will be translated.
208 $name = $this->randomName(16);
209 // This is the language indicator on the translation search screen for
210 // untranslated strings. Copied straight from locale.inc.
211 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
212 // This will be the translation of $name.
213 $translation = $this->randomName(16);
214 $translation_to_en = $this->randomName(16);
215
216 // Add custom language.
217 $this->drupalLogin($admin_user);
218 $edit = array(
219 'predefined_langcode' => 'custom',
220 'langcode' => $langcode,
221 'name' => $name,
222 'direction' => '0',
223 );
224 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
225 // Add string.
226 t($name, array(), array('langcode' => $langcode));
227 // Reset locale cache.
228 locale_reset();
229 $this->assertRaw('"edit-site-default-' . $langcode .'"', t('Language code found.'));
230 $this->assertText(t($name), t('Test language added.'));
231 $this->drupalLogout();
232
233 // Search for the name and translate it.
234 $this->drupalLogin($translate_user);
235 $search = array(
236 'string' => $name,
237 'language' => 'all',
238 'translation' => 'all',
239 );
240 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
241 // assertText() seems to remove the input field where $name always could be
242 // found, so this is not a false assert. See how assertNoText succeeds
243 // later.
244 $this->assertText($name, t('Search found the name.'));
245 $this->assertRaw($language_indicator, t('Name is untranslated.'));
246 // Assume this is the only result, given the random name.
247 $this->clickLink(t('edit'));
248 // We save the lid from the path.
249 $matches = array();
250 preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches);
251 $lid = $matches[1];
252 // No t() here, it's surely not translated yet.
253 $this->assertText($name, t('name found on edit screen.'));
254 $this->assertNoText('English', t('No way to translate the string to English.'));
255 $this->drupalLogout();
256 $this->drupalLogin($admin_user);
257 $this->drupalPost('admin/config/regional/language/edit/en', array('locale_translate_english' => TRUE), t('Save language'));
258 $this->drupalLogout();
259 $this->drupalLogin($translate_user);
260 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
261 // assertText() seems to remove the input field where $name always could be
262 // found, so this is not a false assert. See how assertNoText succeeds
263 // later.
264 $this->assertText($name, t('Search found the name.'));
265 $this->assertRaw($language_indicator, t('Name is untranslated.'));
266 // Assume this is the only result, given the random name.
267 $this->clickLink(t('edit'));
268 $string_edit_url = $this->getUrl();
269 $edit = array(
270 "translations[$langcode]" => $translation,
271 'translations[en]' => $translation_to_en,
272 );
273 $this->drupalPost(NULL, $edit, t('Save translations'));
274 $this->assertText(t('The string has been saved.'), t('The string has been saved.'));
275 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
276 $this->drupalGet($string_edit_url);
277 $this->assertRaw($translation, t('Non-English translation properly saved.'));
278 $this->assertRaw($translation_to_en, t('English translation properly saved.'));
279 $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, t('t() works for non-English.'));
280 // Refresh the locale() cache to get fresh data from t() below. We are in
281 // the same HTTP request and therefore t() is not refreshed by saving the
282 // translation above.
283 locale_reset();
284 // Now we should get the proper fresh translation from t().
285 $this->assertTrue($name != $translation_to_en && t($name, array(), array('langcode' => 'en')) == $translation_to_en, t('t() works for English.'));
286 $this->assertTrue(t($name, array(), array('langcode' => LANGUAGE_SYSTEM)) == $name, t('t() works for LANGUAGE_SYSTEM.'));
287 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
288 // The indicator should not be here.
289 $this->assertNoRaw($language_indicator, t('String is translated.'));
290
291 // Try to edit a non-existent string and ensure we're redirected correctly.
292 // Assuming we don't have 999,999 strings already.
293 $random_lid = 999999;
294 $this->drupalGet('admin/config/regional/translate/edit/' . $random_lid);
295 $this->assertText(t('String not found'), t('String not found.'));
296 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
297 $this->drupalLogout();
298
299 // Delete the language.
300 $this->drupalLogin($admin_user);
301 $path = 'admin/config/regional/language/delete/' . $langcode;
302 // This a confirm form, we do not need any fields changed.
303 $this->drupalPost($path, array(), t('Delete'));
304 // We need raw here because %language and %langcode will add HTML.
305 $t_args = array('%language' => $name, '%langcode' => $langcode);
306 $this->assertRaw(t('The %language (%langcode) language has been removed.', $t_args), t('The test language has been removed.'));
307 // Reload to remove $name.
308 $this->drupalGet($path);
309 // Verify that language is no longer found.
310 $this->assertResponse(404, t('Language no longer found.'));
311 $this->drupalLogout();
312
313 // Delete the string.
314 $this->drupalLogin($translate_user);
315 $search = array(
316 'string' => $name,
317 'language' => 'all',
318 'translation' => 'all',
319 );
320 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
321 // Assume this is the only result, given the random name.
322 $this->clickLink(t('delete'));
323 $this->assertText(t('Are you sure you want to delete the string'), t('"delete" link is correct.'));
324 // Delete the string.
325 $path = 'admin/config/regional/translate/delete/' . $lid;
326 $this->drupalGet($path);
327 // First test the 'cancel' link.
328 $this->clickLink(t('Cancel'));
329 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
330 $this->assertRaw($name, t('The string was not deleted.'));
331 // Delete the name string.
332 $this->drupalPost('admin/config/regional/translate/delete/' . $lid, array(), t('Delete'));
333 $this->assertText(t('The string has been removed.'), t('The string has been removed message.'));
334 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
335 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
336 $this->assertNoText($name, t('Search now can not find the name.'));
337 }
338
339 /*
340 * Adds a language and checks that the JavaScript translation files are
341 * properly created and rebuilt on deletion.
342 */
343 function testJavaScriptTranslation() {
344 $user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
345 $this->drupalLogin($user);
346
347 $langcode = 'xx';
348 // The English name for the language. This will be translated.
349 $name = $this->randomName(16);
350
351 // Add custom language.
352 $edit = array(
353 'predefined_langcode' => 'custom',
354 'langcode' => $langcode,
355 'name' => $name,
356 'direction' => '0',
357 );
358 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
359 drupal_static_reset('language_list');
360
361 // Build the JavaScript translation file.
362 $this->drupalGet('admin/config/regional/translate/translate');
363
364 // Retrieve the id of the first string available in the {locales_source}
365 // table and translate it.
366 $query = db_select('locales_source', 'l');
367 $query->addExpression('min(l.lid)', 'lid');
368 $result = $query->condition('l.location', '%.js%', 'LIKE')->execute();
369 $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid;
370 $edit = array('translations['. $langcode .']' => $this->randomName());
371 $this->drupalPost($url, $edit, t('Save translations'));
372
373 // Trigger JavaScript translation parsing and building.
374 require_once DRUPAL_ROOT . '/core/includes/locale.inc';
375 _locale_rebuild_js($langcode);
376
377 $locale_javascripts = variable_get('locale_translation_javascript', array());
378 $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $locale_javascripts[$langcode] . '.js';
379 $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('not found'))));
380
381 // Test JavaScript translation rebuilding.
382 file_unmanaged_delete($js_file);
383 $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found'))));
384 cache_clear_all();
385 _locale_rebuild_js($langcode);
386 $this->assertTrue($result = file_exists($js_file), t('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : t('not found'))));
387 }
388
389 /**
390 * Tests the validation of the translation input.
391 */
392 function testStringValidation() {
393 global $base_url;
394
395 // User to add language and strings.
396 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
397 $this->drupalLogin($admin_user);
398 $langcode = 'xx';
399 // The English name for the language. This will be translated.
400 $name = $this->randomName(16);
401 // This is the language indicator on the translation search screen for
402 // untranslated strings. Copied straight from locale.inc.
403 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
404 // These will be the invalid translations of $name.
405 $key = $this->randomName(16);
406 $bad_translations[$key] = "<script>alert('xss');</script>" . $key;
407 $key = $this->randomName(16);
408 $bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
409 $key = $this->randomName(16);
410 $bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
411 $key = $this->randomName(16);
412 $bad_translations[$key] ="<BODY ONLOAD=alert('xss')>" . $key;
413
414 // Add custom language.
415 $edit = array(
416 'predefined_langcode' => 'custom',
417 'langcode' => $langcode,
418 'name' => $name,
419 'direction' => '0',
420 );
421 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
422 // Add string.
423 t($name, array(), array('langcode' => $langcode));
424 // Reset locale cache.
425 $search = array(
426 'string' => $name,
427 'language' => 'all',
428 'translation' => 'all',
429 );
430 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
431 // Find the edit path.
432 $content = $this->drupalGetContent();
433 $this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.'));
434 $path = $matches[0];
435 foreach ($bad_translations as $key => $translation) {
436 $edit = array(
437 "translations[$langcode]" => $translation,
438 );
439 $this->drupalPost($path, $edit, t('Save translations'));
440 // Check for a form error on the textarea.
441 $form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
442 $this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), t('The string was rejected as unsafe.'));
443 $this->assertNoText(t('The string has been saved.'), t('The string was not saved.'));
444 }
445 }
446
447 /**
448 * Tests translation search form.
449 */
450 function testStringSearch() {
451 global $base_url;
452
453 // User to add and remove language.
454 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
455 // User to translate and delete string.
456 $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
457
458 // Code for the language.
459 $langcode = 'xx';
460 // The English name for the language. This will be translated.
461 $name = $this->randomName(16);
462 // This is the language indicator on the translation search screen for
463 // untranslated strings. Copied straight from locale.inc.
464 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
465 // This will be the translation of $name.
466 $translation = $this->randomName(16);
467
468 // Add custom language.
469 $this->drupalLogin($admin_user);
470 $edit = array(
471 'predefined_langcode' => 'custom',
472 'langcode' => $langcode,
473 'name' => $name,
474 'direction' => '0',
475 );
476 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
477 // Add string.
478 t($name, array(), array('langcode' => $langcode));
479 // Reset locale cache.
480 locale_reset();
481 $this->drupalLogout();
482
483 // Search for the name.
484 $this->drupalLogin($translate_user);
485 $search = array(
486 'string' => $name,
487 'language' => 'all',
488 'translation' => 'all',
489 );
490 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
491 // assertText() seems to remove the input field where $name always could be
492 // found, so this is not a false assert. See how assertNoText succeeds
493 // later.
494 $this->assertText($name, t('Search found the string.'));
495
496 // Ensure untranslated string doesn't appear if searching on 'only
497 // translated strings'.
498 $search = array(
499 'string' => $name,
500 'language' => 'all',
501 'translation' => 'translated',
502 );
503 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
504 $this->assertText(t('No strings available.'), t("Search didn't find the string."));
505
506 // Ensure untranslated string appears if searching on 'only untranslated
507 // strings'.
508 $search = array(
509 'string' => $name,
510 'language' => 'all',
511 'translation' => 'untranslated',
512 );
513 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
514 $this->assertNoText(t('No strings available.'), t('Search found the string.'));
515
516 // Add translation.
517 // Assume this is the only result, given the random name.
518 $this->clickLink(t('edit'));
519 // We save the lid from the path.
520 $matches = array();
521 preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches);
522 $lid = $matches[1];
523 $edit = array(
524 "translations[$langcode]" => $translation,
525 );
526 $this->drupalPost(NULL, $edit, t('Save translations'));
527
528 // Ensure translated string does appear if searching on 'only
529 // translated strings'.
530 $search = array(
531 'string' => $translation,
532 'language' => 'all',
533 'translation' => 'translated',
534 );
535 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
536 $this->assertNoText(t('No strings available.'), t('Search found the translation.'));
537
538 // Ensure translated source string doesn't appear if searching on 'only
539 // untranslated strings'.
540 $search = array(
541 'string' => $name,
542 'language' => 'all',
543 'translation' => 'untranslated',
544 );
545 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
546 $this->assertText(t('No strings available.'), t("Search didn't find the source string."));
547
548 // Ensure translated string doesn't appear if searching on 'only
549 // untranslated strings'.
550 $search = array(
551 'string' => $translation,
552 'language' => 'all',
553 'translation' => 'untranslated',
554 );
555 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
556 $this->assertText(t('No strings available.'), t("Search didn't find the translation."));
557
558 // Ensure translated string does appear if searching on the custom language.
559 $search = array(
560 'string' => $translation,
561 'language' => $langcode,
562 'translation' => 'all',
563 );
564 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
565 $this->assertNoText(t('No strings available.'), t('Search found the translation.'));
566
567 // Ensure translated string doesn't appear if searching in System (English).
568 $search = array(
569 'string' => $translation,
570 'language' => LANGUAGE_SYSTEM,
571 'translation' => 'all',
572 );
573 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
574 $this->assertText(t('No strings available.'), t("Search didn't find the translation."));
575
576 // Search for a string that isn't in the system.
577 $unavailable_string = $this->randomName(16);
578 $search = array(
579 'string' => $unavailable_string,
580 'language' => 'all',
581 'translation' => 'all',
582 );
583 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
584 $this->assertText(t('No strings available.'), t("Search didn't find the invalid string."));
585 }
586 }
587
588 /**
589 * Tests plural index computation functionality.
590 */
591 class LocalePluralFormatTest extends DrupalWebTestCase {
592 public static function getInfo() {
593 return array(
594 'name' => 'Plural formula evaluation',
595 'description' => 'Tests plural formula evaluation for various languages.',
596 'group' => 'Locale',
597 );
598 }
599
600 function setUp() {
601 parent::setUp('locale', 'locale_test');
602
603 $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
604 $this->drupalLogin($admin_user);
605 }
606
607 /**
608 * Tests locale_get_plural() functionality.
609 */
610 function testGetPluralFormat() {
611 // Import some .po files with formulas to set up the environment.
612 // These will also add the languages to the system and enable them.
613 $this->importPoFile($this->getPoFileWithSimplePlural(), array(
614 'langcode' => 'fr',
615 ));
616 $this->importPoFile($this->getPoFileWithComplexPlural(), array(
617 'langcode' => 'hr',
618 ));
619
620 // Reset static caches from locale_get_plural() to ensure we get fresh data.
621 drupal_static_reset('locale_get_plural');
622 drupal_static_reset('locale_get_plural:plurals');
623
624 // Test locale_get_plural() for English (no formula presnt).
625 $this->assertIdentical(locale_get_plural(1, 'en'), 0, t("Computed plural index for 'en' with count 1 is 0."));
626 $this->assertIdentical(locale_get_plural(0, 'en'), 1, t("Computed plural index for 'en' with count 0 is 1."));
627 $this->assertIdentical(locale_get_plural(5, 'en'), 1, t("Computed plural index for 'en' with count 5 is 1."));
628
629 // Test locale_get_plural() for French (simpler formula).
630 $this->assertIdentical(locale_get_plural(1, 'fr'), 0, t("Computed plural index for 'fr' with count 1 is 0."));
631 $this->assertIdentical(locale_get_plural(0, 'fr'), 0, t("Computed plural index for 'fr' with count 0 is 0."));
632 $this->assertIdentical(locale_get_plural(5, 'fr'), 1, t("Computed plural index for 'fr' with count 5 is 1."));
633
634 // Test locale_get_plural() for Croatian (more complex formula).
635 $this->assertIdentical(locale_get_plural( 1, 'hr'), 0, t("Computed plural index for 'hr' with count 1 is 0."));
636 $this->assertIdentical(locale_get_plural(21, 'hr'), 0, t("Computed plural index for 'hr' with count 21 is 0."));
637 $this->assertIdentical(locale_get_plural( 0, 'hr'), 2, t("Computed plural index for 'hr' with count 0 is 2."));
638 $this->assertIdentical(locale_get_plural( 2, 'hr'), 1, t("Computed plural index for 'hr' with count 2 is 1."));
639 $this->assertIdentical(locale_get_plural( 8, 'hr'), 2, t("Computed plural index for 'hr' with count 8 is 2."));
640
641 // Test locale_get_plural() for Hungarian (nonexistent language).
642 $this->assertIdentical(locale_get_plural( 1, 'hu'), -1, t("Computed plural index for 'hu' with count 1 is -1."));
643 $this->assertIdentical(locale_get_plural(21, 'hu'), -1, t("Computed plural index for 'hu' with count 21 is -1."));
644 $this->assertIdentical(locale_get_plural( 0, 'hu'), -1, t("Computed plural index for 'hu' with count 0 is -1."));
645 }
646
647 /**
648 * Imports a standalone .po file in a given language.
649 *
650 * @param $contents
651 * Contents of the .po file to import.
652 * @param $options
653 * Additional options to pass to the translation import form.
654 */
655 function importPoFile($contents, array $options = array()) {
656 $name = tempnam('temporary://', "po_") . '.po';
657 file_put_contents($name, $contents);
658 $options['files[file]'] = $name;
659 $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
660 drupal_unlink($name);
661 }
662
663 /**
664 * Returns a .po file with a simple plural formula.
665 */
666 function getPoFileWithSimplePlural() {
667 return <<< EOF
668 msgid ""
669 msgstr ""
670 "Project-Id-Version: Drupal 7\\n"
671 "MIME-Version: 1.0\\n"
672 "Content-Type: text/plain; charset=UTF-8\\n"
673 "Content-Transfer-Encoding: 8bit\\n"
674 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
675
676 msgid "1 hour"
677 msgid_plural "@count hours"
678 msgstr[0] "1 heure"
679 msgstr[1] "@count heures"
680
681 msgid "Monday"
682 msgstr "lundi"
683 EOF;
684 }
685
686 /**
687 * Returns a .po file with a complex plural formula.
688 */
689 function getPoFileWithComplexPlural() {
690 return <<< EOF
691 msgid ""
692 msgstr ""
693 "Project-Id-Version: Drupal 7\\n"
694 "MIME-Version: 1.0\\n"
695 "Content-Type: text/plain; charset=UTF-8\\n"
696 "Content-Transfer-Encoding: 8bit\\n"
697 "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
698
699 msgid "1 hour"
700 msgid_plural "@count hours"
701 msgstr[0] "@count sat"
702 msgstr[1] "@count sata"
703 msgstr[2] "@count sati"
704
705 msgid "Monday"
706 msgstr "Ponedjeljak"
707 EOF;
708 }
709 }
710
711 /**
712 * Functional tests for the import of translation files.
713 */
714 class LocaleImportFunctionalTest extends DrupalWebTestCase {
715 public static function getInfo() {
716 return array(
717 'name' => 'Translation import',
718 'description' => 'Tests the import of locale files.',
719 'group' => 'Locale',
720 );
721 }
722
723 /**
724 * A user able to create languages and import translations.
725 */
726 protected $admin_user = NULL;
727
728 function setUp() {
729 parent::setUp('locale', 'locale_test');
730
731 // Set the translation file directory.
732 variable_set('locale_translate_file_directory', drupal_get_path('module', 'locale_test'));
733
734 $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
735 $this->drupalLogin($this->admin_user);
736 }
737
738 /**
739 * Test import of standalone .po files.
740 */
741 function testStandalonePoFile() {
742 // Try importing a .po file.
743 $this->importPoFile($this->getPoFile(), array(
744 'langcode' => 'fr',
745 ));
746
747 // The import should automatically create the corresponding language.
748 $this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.'));
749
750 // The import should have created 7 strings.
751 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 9, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
752
753 // This import should have saved plural forms to have 2 variants.
754 $locale_plurals = variable_get('locale_translation_plurals', array());
755 $this->assert($locale_plurals['fr']['plurals'] == 2, t('Plural number initialized.'));
756
757 // Ensure we were redirected correctly.
758 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
759
760
761 // Try importing a .po file with invalid tags.
762 $this->importPoFile($this->getBadPoFile(), array(
763 'langcode' => 'fr',
764 ));
765
766 // The import should have created 1 string and rejected 2.
767 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
768 $skip_message = format_plural(2, 'A translation string was skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', '@count translation strings were skipped because of disallowed or malformed HTML. <a href="@url">See the log</a> for details.', array('@url' => url('admin/reports/dblog')));
769 $this->assertRaw($skip_message, t('Unsafe strings were skipped.'));
770
771
772 // Try importing a .po file which doesn't exist.
773 $name = $this->randomName(16);
774 $this->drupalPost('admin/config/regional/translate/import', array(
775 'langcode' => 'fr',
776 'files[file]' => $name,
777 ), t('Import'));
778 $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), t('Correct page redirection.'));
779 $this->assertText(t('File to import not found.'), t('File to import not found message.'));
780
781
782 // Try importing a .po file with overriding strings, and ensure existing
783 // strings are kept.
784 $this->importPoFile($this->getOverwritePoFile(), array(
785 'langcode' => 'fr',
786 'mode' => 1, // Existing strings are kept, only new strings are added.
787 ));
788
789 // The import should have created 1 string.
790 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
791 // Ensure string wasn't overwritten.
792 $search = array(
793 'string' => 'Montag',
794 'language' => 'fr',
795 'translation' => 'translated',
796 );
797 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
798 $this->assertText(t('No strings available.'), t('String not overwritten by imported string.'));
799
800 // This import should not have changed number of plural forms.
801 $locale_plurals = variable_get('locale_translation_plurals', array());
802 $this->assert($locale_plurals['fr']['plurals'] == 2, t('Plural numbers untouched.'));
803
804 // Try importing a .po file with overriding strings, and ensure existing
805 // strings are overwritten.
806 $this->importPoFile($this->getOverwritePoFile(), array(
807 'langcode' => 'fr',
808 'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
809 ));
810
811 // The import should have updated 2 strings.
812 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), t('The translation file was successfully imported.'));
813 // Ensure string was overwritten.
814 $search = array(
815 'string' => 'Montag',
816 'language' => 'fr',
817 'translation' => 'translated',
818 );
819 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
820 $this->assertNoText(t('No strings available.'), t('String overwritten by imported string.'));
821 // This import should have changed number of plural forms.
822 $locale_plurals = variable_get('locale_translation_plurals', array());
823 $this->assert($locale_plurals['fr']['plurals'] == 3, t('Plural numbers changed.'));
824 }
825
826 /**
827 * Test automatic import of a module's translation files.
828 */
829 function testAutomaticModuleTranslationImportLanguageEnable() {
830 // Code for the language - manually set to match the test translation file.
831 $langcode = 'xx';
832 // The English name for the language.
833 $name = $this->randomName(16);
834
835 // Create a custom language.
836 $edit = array(
837 'predefined_langcode' => 'custom',
838 'langcode' => $langcode,
839 'name' => $name,
840 'direction' => '0',
841 );
842 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
843
844 // Ensure the translation file was automatically imported when language was
845 // added.
846 $this->assertText(t('One translation file imported.'), t('Language file automatically imported.'));
847
848 // Ensure strings were successfully imported.
849 $search = array(
850 'string' => 'lundi',
851 'language' => $langcode,
852 'translation' => 'translated',
853 );
854 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
855 $this->assertNoText(t('No strings available.'), t('String successfully imported.'));
856 }
857
858 /**
859 * Test msgctxt context support.
860 */
861 function testLanguageContext() {
862 // Try importing a .po file.
863 $this->importPoFile($this->getPoFileWithContext(), array(
864 'langcode' => 'hr',
865 ));
866
867 $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', t('Long month name context is working.'));
868 $this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', t('Default context is working.'));
869 }
870
871 /**
872 * Test empty msgstr at end of .po file see #611786.
873 */
874 function testEmptyMsgstr() {
875 $langcode = 'hu';
876
877 // Try importing a .po file.
878 $this->importPoFile($this->getPoFileWithMsgstr(), array(
879 'langcode' => $langcode,
880 ));
881
882 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
883 $this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'MĹąveletek', t('String imported and translated.'));
884
885 // Try importing a .po file.
886 $this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
887 'langcode' => $langcode,
888 'mode' => 0,
889 ));
890 $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), t('The translation file was successfully imported.'));
891 // This is the language indicator on the translation search screen for
892 // untranslated strings. Copied straight from locale.inc.
893 $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
894 $str = "Operations";
895 $search = array(
896 'string' => $str,
897 'language' => 'all',
898 'translation' => 'all',
899 );
900 $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
901 // assertText() seems to remove the input field where $str always could be
902 // found, so this is not a false assert.
903 $this->assertText($str, t('Search found the string.'));
904 $this->assertRaw($language_indicator, t('String is untranslated again.'));
905 }
906
907 /**
908 * Helper function: import a standalone .po file in a given language.
909 *
910 * @param $contents
911 * Contents of the .po file to import.
912 * @param $options
913 * Additional options to pass to the translation import form.
914 */
915 function importPoFile($contents, array $options = array()) {
916 $name = tempnam('temporary://', "po_") . '.po';
917 file_put_contents($name, $contents);
918 $options['files[file]'] = $name;
919 $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
920 drupal_unlink($name);
921 }
922
923 /**
924 * Helper function that returns a proper .po file.
925 */
926 function getPoFile() {
927 return <<< EOF
928 msgid ""
929 msgstr ""
930 "Project-Id-Version: Drupal 7\\n"
931 "MIME-Version: 1.0\\n"
932 "Content-Type: text/plain; charset=UTF-8\\n"
933 "Content-Transfer-Encoding: 8bit\\n"
934 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
935
936 msgid "One sheep"
937 msgid_plural "@count sheep"
938 msgstr[0] "un mouton"
939 msgstr[1] "@count moutons"
940
941 msgid "Monday"
942 msgstr "lundi"
943
944 msgid "Tuesday"
945 msgstr "mardi"
946
947 msgid "Wednesday"
948 msgstr "mercredi"
949
950 msgid "Thursday"
951 msgstr "jeudi"
952
953 msgid "Friday"
954 msgstr "vendredi"
955
956 msgid "Saturday"
957 msgstr "samedi"
958
959 msgid "Sunday"
960 msgstr "dimanche"
961 EOF;
962 }
963
964 /**
965 * Helper function that returns a bad .po file.
966 */
967 function getBadPoFile() {
968 return <<< EOF
969 msgid ""
970 msgstr ""
971 "Project-Id-Version: Drupal 7\\n"
972 "MIME-Version: 1.0\\n"
973 "Content-Type: text/plain; charset=UTF-8\\n"
974 "Content-Transfer-Encoding: 8bit\\n"
975 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
976
977 msgid "Save configuration"
978 msgstr "Enregistrer la configuration"
979
980 msgid "edit"
981 msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
982
983 msgid "delete"
984 msgstr "supprimer<script>alert('xss');</script>"
985
986 EOF;
987 }
988
989 /**
990 * Helper function that returns a proper .po file for testing.
991 */
992 function getOverwritePoFile() {
993 return <<< EOF
994 msgid ""
995 msgstr ""
996 "Project-Id-Version: Drupal 7\\n"
997 "MIME-Version: 1.0\\n"
998 "Content-Type: text/plain; charset=UTF-8\\n"
999 "Content-Transfer-Encoding: 8bit\\n"
1000 "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
1001
1002 msgid "Monday"
1003 msgstr "Montag"
1004
1005 msgid "Day"
1006 msgstr "Jour"
1007 EOF;
1008 }
1009
1010 /**
1011 * Helper function that returns a .po file with context.
1012 */
1013 function getPoFileWithContext() {
1014 // Croatian (code hr) is one the the languages that have a different
1015 // form for the full name and the abbreviated name for the month May.
1016 return <<< EOF
1017 msgid ""
1018 msgstr ""
1019 "Project-Id-Version: Drupal 7\\n"
1020 "MIME-Version: 1.0\\n"
1021 "Content-Type: text/plain; charset=UTF-8\\n"
1022 "Content-Transfer-Encoding: 8bit\\n"
1023 "Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
1024
1025 msgctxt "Long month name"
1026 msgid "May"
1027 msgstr "Svibanj"
1028
1029 msgid "May"
1030 msgstr "Svi."
1031 EOF;
1032 }
1033
1034 /**
1035 * Helper function that returns a .po file with an empty last item.
1036 */
1037 function getPoFileWithEmptyMsgstr() {
1038 return <<< EOF
1039 msgid ""
1040 msgstr ""
1041 "Project-Id-Version: Drupal 7\\n"
1042 "MIME-Version: 1.0\\n"
1043 "Content-Type: text/plain; charset=UTF-8\\n"
1044 "Content-Transfer-Encoding: 8bit\\n"
1045 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1046
1047 msgid "Operations"
1048 msgstr ""
1049
1050 EOF;
1051 }
1052 /**
1053 * Helper function that returns a .po file with an empty last item.
1054 */
1055 function getPoFileWithMsgstr() {
1056 return <<< EOF
1057 msgid ""
1058 msgstr ""
1059 "Project-Id-Version: Drupal 7\\n"
1060 "MIME-Version: 1.0\\n"
1061 "Content-Type: text/plain; charset=UTF-8\\n"
1062 "Content-Transfer-Encoding: 8bit\\n"
1063 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1064
1065 msgid "Operations"
1066 msgstr "MĹąveletek"
1067
1068 msgid "Will not appear in Drupal core, so we can ensure the test passes"
1069 msgstr ""
1070
1071 EOF;
1072 }
1073
1074 }
1075
1076 /**
1077 * Functional tests for the export of translation files.
1078 */
1079 class LocaleExportFunctionalTest extends DrupalWebTestCase {
1080 public static function getInfo() {
1081 return array(
1082 'name' => 'Translation export',
1083 'description' => 'Tests the exportation of locale files.',
1084 'group' => 'Locale',
1085 );
1086 }
1087
1088 /**
1089 * A user able to create languages and export translations.
1090 */
1091 protected $admin_user = NULL;
1092
1093 function setUp() {
1094 parent::setUp('locale', 'locale_test');
1095
1096 $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
1097 $this->drupalLogin($this->admin_user);
1098 }
1099
1100 /**
1101 * Test exportation of translations.
1102 */
1103 function testExportTranslation() {
1104 // First import some known translations.
1105 // This will also automatically enable the 'fr' language.
1106 $name = tempnam('temporary://', "po_") . '.po';
1107 file_put_contents($name, $this->getPoFile());
1108 $this->drupalPost('admin/config/regional/translate/import', array(
1109 'langcode' => 'fr',
1110 'files[file]' => $name,
1111 ), t('Import'));
1112 drupal_unlink($name);
1113
1114 // Get the French translations.
1115 $this->drupalPost('admin/config/regional/translate/export', array(
1116 'langcode' => 'fr',
1117 ), t('Export'));
1118
1119 // Ensure we have a translation file.
1120 $this->assertRaw('# French translation of Drupal', t('Exported French translation file.'));
1121 // Ensure our imported translations exist in the file.
1122 $this->assertRaw('msgstr "lundi"', t('French translations present in exported file.'));
1123 }
1124
1125 /**
1126 * Test exportation of translation template file.
1127 */
1128 function testExportTranslationTemplateFile() {
1129 // Get the translation template file.
1130 // There are two 'Export' buttons on this page, but it somehow works. It'd
1131 // be better if we could use the submit button id like documented but that
1132 // doesn't work.
1133 $this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
1134 // Ensure we have a translation file.
1135 $this->assertRaw('# LANGUAGE translation of PROJECT', t('Exported translation template file.'));
1136 }
1137
1138 /**
1139 * Helper function that returns a proper .po file.
1140 */
1141 function getPoFile() {
1142 return <<< EOF
1143 msgid ""
1144 msgstr ""
1145 "Project-Id-Version: Drupal 6\\n"
1146 "MIME-Version: 1.0\\n"
1147 "Content-Type: text/plain; charset=UTF-8\\n"
1148 "Content-Transfer-Encoding: 8bit\\n"
1149 "Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1150
1151 msgid "Monday"
1152 msgstr "lundi"
1153 EOF;
1154 }
1155
1156 }
1157
1158 /**
1159 * Tests for the st() function.
1160 */
1161 class LocaleInstallTest extends DrupalWebTestCase {
1162 public static function getInfo() {
1163 return array(
1164 'name' => 'String translation using st()',
1165 'description' => 'Tests that st() works like t().',
1166 'group' => 'Locale',
1167 );
1168 }
1169
1170 function setUp() {
1171 parent::setUp('locale');
1172
1173 // st() lives in install.inc, so ensure that it is loaded for all tests.
1174 require_once DRUPAL_ROOT . '/core/includes/install.inc';
1175 }
1176
1177 /**
1178 * Verify that function signatures of t() and st() are equal.
1179 */
1180 function testFunctionSignatures() {
1181 $reflector_t = new ReflectionFunction('t');
1182 $reflector_st = new ReflectionFunction('st');
1183 $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), t('Function signatures of t() and st() are equal.'));
1184 }
1185 }
1186
1187 /**
1188 * Locale uninstall with English UI functional test.
1189 */
1190 class LocaleUninstallFunctionalTest extends DrupalWebTestCase {
1191 public static function getInfo() {
1192 return array(
1193 'name' => 'Locale uninstall (EN)',
1194 'description' => 'Tests the uninstall process using the built-in UI language.',
1195 'group' => 'Locale',
1196 );
1197 }
1198
1199 /**
1200 * The default language set for the UI before uninstall.
1201 */
1202 protected $language;
1203
1204 function setUp() {
1205 parent::setUp('locale');
1206 $this->langcode = 'en';
1207 }
1208
1209 /**
1210 * Check if the values of the Locale variables are correct after uninstall.
1211 */
1212 function testUninstallProcess() {
1213 $locale_module = array('locale', 'language');
1214
1215 // Add a new language and optionally set it as default.
1216 require_once DRUPAL_ROOT . '/core/includes/locale.inc';
1217
1218 $language = (object) array(
1219 'langcode' => 'fr',
1220 'name' => 'French',
1221 'default' => $this->langcode == 'fr',
1222 );
1223 language_save($language);
1224
1225 // Check the UI language.
1226 drupal_language_initialize();
1227 $this->assertEqual($GLOBALS['language']->langcode, $this->langcode, t('Current language: %lang', array('%lang' => $GLOBALS['language']->langcode)));
1228
1229 // Enable multilingual workflow option for articles.
1230 variable_set('node_type_language_article', 1);
1231
1232 // Change JavaScript translations directory.
1233 variable_set('locale_js_directory', 'js_translations');
1234
1235 // Build the JavaScript translation file for French.
1236 $user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
1237 $this->drupalLogin($user);
1238 $this->drupalGet('admin/config/regional/translate/translate');
1239 $string = db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE :location', array(
1240 ':location' => '%.js%',
1241 ))->fetchObject();
1242 $edit = array('translations[fr]' => 'french translation');
1243 $this->drupalPost('admin/config/regional/translate/edit/' . $string->lid, $edit, t('Save translations'));
1244 _locale_rebuild_js('fr');
1245 $locale_javascripts = variable_get('locale_translation_javascript', array());
1246 $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/fr_' . $locale_javascripts['fr'] . '.js';
1247 $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('none'))));
1248
1249 // Disable string caching.
1250 variable_set('locale_cache_strings', 0);
1251
1252 // Change language negotiation options.
1253 drupal_load('module', 'locale');
1254 variable_set('language_types', drupal_language_types() + array('language_custom' => TRUE));
1255 variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info());
1256 variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info());
1257 variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, locale_language_negotiation_info());
1258
1259 // Change language providers settings.
1260 variable_set('locale_language_negotiation_url_part', LANGUAGE_NEGOTIATION_URL_PREFIX);
1261 variable_set('locale_language_negotiation_session_param', TRUE);
1262
1263 // Uninstall Locale.
1264 module_disable($locale_module);
1265 drupal_uninstall_modules($locale_module);
1266
1267 // Visit the front page.
1268 $this->drupalGet('');
1269
1270 // Check the init language logic.
1271 drupal_language_initialize();
1272 $this->assertEqual($GLOBALS['language']->langcode, 'en', t('Language after uninstall: %lang', array('%lang' => $GLOBALS['language']->langcode)));
1273
1274 // Check JavaScript files deletion.
1275 $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found'))));
1276
1277 // Check language count.
1278 $language_count = variable_get('language_count', 1);
1279 $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count)));
1280
1281 // Check language negotiation.
1282 require_once DRUPAL_ROOT . '/core/includes/language.inc';
1283 $this->assertTrue(count(language_types()) == count(drupal_language_types()), t('Language types reset'));
1284 $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT;
1285 $this->assertTrue($language_negotiation, t('Interface language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set'))));
1286 $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT;
1287 $this->assertTrue($language_negotiation, t('Content language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set'))));
1288 $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT;
1289 $this->assertTrue($language_negotiation, t('URL language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set'))));
1290
1291 // Check language providers settings.
1292 $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE), t('URL language provider indicator settings cleared.'));
1293 $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE), t('Visit language provider settings cleared.'));
1294
1295 // Check JavaScript parsed.
1296 $javascript_parsed_count = count(variable_get('javascript_parsed', array()));
1297 $this->assertEqual($javascript_parsed_count, 0, t('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count)));
1298
1299 // Check JavaScript translations directory.
1300 $locale_js_directory = variable_get('locale_js_directory', 'languages');
1301 $this->assertEqual($locale_js_directory, 'languages', t('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory)));
1302
1303 // Check string caching.
1304 $locale_cache_strings = variable_get('locale_cache_strings', 1);
1305 $this->assertEqual($locale_cache_strings, 1, t('String caching: %status', array('%status' => t($locale_cache_strings ? 'enabled': 'disabled'))));
1306 }
1307 }
1308
1309 /**
1310 * Locale uninstall with French UI functional test.
1311 *
1312 * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new
1313 * test of its own. Rather, it switches the default UI language in setUp and then
1314 * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest)
1315 * to test with this new language.
1316 */
1317 class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest {
1318 public static function getInfo() {
1319 return array(
1320 'name' => 'Locale uninstall (FR)',
1321 'description' => 'Tests the uninstall process using French as interface language.',
1322 'group' => 'Locale',
1323 );
1324 }
1325
1326 function setUp() {
1327 parent::setUp();
1328 $this->langcode = 'fr';
1329 }
1330 }
1331
1332
1333 /**
1334 * Functional tests for the language switching feature.
1335 */
1336 class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase {
1337
1338 public static function getInfo() {
1339 return array(
1340 'name' => 'Language switching',
1341 'description' => 'Tests for the language switching feature.',
1342 'group' => 'Locale',
1343 );
1344 }
1345
1346 function setUp() {
1347 parent::setUp('locale');
1348
1349 // Create and login user.
1350 $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages'));
1351 $this->drupalLogin($admin_user);
1352 }
1353
1354 /**
1355 * Functional tests for the language switcher block.
1356 */
1357 function testLanguageBlock() {
1358 // Enable the language switching block.
1359 $language_type = LANGUAGE_TYPE_INTERFACE;
1360 $edit = array(
1361 "blocks[locale_{$language_type}][region]" => 'sidebar_first',
1362 );
1363 $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
1364
1365 // Add language.
1366 $edit = array(
1367 'predefined_langcode' => 'fr',
1368 );
1369 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1370
1371 // Enable URL language detection and selection.
1372 $edit = array('language[enabled][locale-url]' => '1');
1373 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
1374
1375 // Assert that the language switching block is displayed on the frontpage.
1376 $this->drupalGet('');
1377 $this->assertText(t('Languages'), t('Language switcher block found.'));
1378
1379 // Assert that only the current language is marked as active.
1380 list($language_switcher) = $this->xpath('//div[@id=:id]/div[@class="content"]', array(':id' => 'block-locale-' . $language_type));
1381 $links = array(
1382 'active' => array(),
1383 'inactive' => array(),
1384 );
1385 $anchors = array(
1386 'active' => array(),
1387 'inactive' => array(),
1388 );
1389 foreach ($language_switcher->ul->li as $link) {
1390 $classes = explode(" ", (string) $link['class']);
1391 list($langcode) = array_intersect($classes, array('en', 'fr'));
1392 if (in_array('active', $classes)) {
1393 $links['active'][] = $langcode;
1394 }
1395 else {
1396 $links['inactive'][] = $langcode;
1397 }
1398 $anchor_classes = explode(" ", (string) $link->a['class']);
1399 if (in_array('active', $anchor_classes)) {
1400 $anchors['active'][] = $langcode;
1401 }
1402 else {
1403 $anchors['inactive'][] = $langcode;
1404 }
1405 }
1406 $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language list item is marked as active on the language switcher block.'));
1407 $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), t('Only the current language anchor is marked as active on the language switcher block.'));
1408 }
1409 }
1410
1411 /**
1412 * Test browser language detection.
1413 */
1414 class LocaleBrowserDetectionTest extends DrupalUnitTestCase {
1415
1416 public static function getInfo() {
1417 return array(
1418 'name' => 'Browser language detection',
1419 'description' => 'Tests for the browser language detection.',
1420 'group' => 'Locale',
1421 );
1422 }
1423
1424 /**
1425 * Unit tests for the locale_language_from_browser() function.
1426 */
1427 function testLanguageFromBrowser() {
1428 // Load the required functions.
1429 require_once DRUPAL_ROOT . '/core/includes/locale.inc';
1430
1431 $languages = array(
1432 // In our test case, 'en' has priority over 'en-US'.
1433 'en' => (object) array(
1434 'langcode' => 'en',
1435 ),
1436 'en-US' => (object) array(
1437 'langcode' => 'en-US',
1438 ),
1439 // But 'fr-CA' has priority over 'fr'.
1440 'fr-CA' => (object) array(
1441 'langcode' => 'fr-CA',
1442 ),
1443 'fr' => (object) array(
1444 'langcode' => 'fr',
1445 ),
1446 // 'es-MX' is alone.
1447 'es-MX' => (object) array(
1448 'langcode' => 'es-MX',
1449 ),
1450 // 'pt' is alone.
1451 'pt' => (object) array(
1452 'langcode' => 'pt',
1453 ),
1454 // Language codes with more then one dash are actually valid.
1455 // eh-oh-laa-laa is the official language code of the Teletubbies.
1456 'eh-oh-laa-laa' => (object) array(
1457 'langcode' => 'eh-oh-laa-laa',
1458 ),
1459 );
1460
1461 $test_cases = array(
1462 // Equal qvalue for each language, choose the site prefered one.
1463 'en,en-US,fr-CA,fr,es-MX' => 'en',
1464 'en-US,en,fr-CA,fr,es-MX' => 'en',
1465 'fr,en' => 'en',
1466 'en,fr' => 'en',
1467 'en-US,fr' => 'en',
1468 'fr,en-US' => 'en',
1469 'fr,fr-CA' => 'fr-CA',
1470 'fr-CA,fr' => 'fr-CA',
1471 'fr' => 'fr-CA',
1472 'fr;q=1' => 'fr-CA',
1473 'fr,es-MX' => 'fr-CA',
1474 'fr,es' => 'fr-CA',
1475 'es,fr' => 'fr-CA',
1476 'es-MX,de' => 'es-MX',
1477 'de,es-MX' => 'es-MX',
1478
1479 // Different cases and whitespace.
1480 'en' => 'en',
1481 'En' => 'en',
1482 'EN' => 'en',
1483 ' en' => 'en',
1484 'en ' => 'en',
1485
1486 // A less specific language from the browser matches a more specific one
1487 // from the website, and the other way around for compatibility with
1488 // some versions of Internet Explorer.
1489 'es' => 'es-MX',
1490 'es-MX' => 'es-MX',
1491 'pt' => 'pt',
1492 'pt-PT' => 'pt',
1493 'pt-PT;q=0.5,pt-BR;q=1,en;q=0.7' => 'en',
1494 'pt-PT;q=1,pt-BR;q=0.5,en;q=0.7' => 'en',
1495 'pt-PT;q=0.4,pt-BR;q=0.1,en;q=0.7' => 'en',
1496 'pt-PT;q=0.1,pt-BR;q=0.4,en;q=0.7' => 'en',
1497
1498 // Language code with several dashes are valid. The less specific language
1499 // from the browser matches the more specific one from the website.
1500 'eh-oh-laa-laa' => 'eh-oh-laa-laa',
1501 'eh-oh-laa' => 'eh-oh-laa-laa',
1502 'eh-oh' => 'eh-oh-laa-laa',
1503 'eh' => 'eh-oh-laa-laa',
1504
1505 // Different qvalues.
1506 'en-US,en;q=0.5,fr;q=0.25' => 'en-US',
1507 'fr,en;q=0.5' => 'fr-CA',
1508 'fr,en;q=0.5,fr-CA;q=0.25' => 'fr',
1509
1510 // Silly wildcards are also valid.
1511 '*,fr-CA;q=0.5' => 'en',
1512 '*,en;q=0.25' => 'fr-CA',
1513 'en,en-US;q=0.5,fr;q=0.25' => 'en',
1514 'en-US,en;q=0.5,fr;q=0.25' => 'en-US',
1515
1516 // Unresolvable cases.
1517 '' => FALSE,
1518 'de,pl' => FALSE,
1519 $this->randomName(10) => FALSE,
1520 );
1521
1522 foreach ($test_cases as $accept_language => $expected_result) {
1523 $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $accept_language;
1524 $result = locale_language_from_browser($languages);
1525 $this->assertIdentical($result, $expected_result, t("Language selection '@accept-language' selects '@result', result = '@actual'", array('@accept-language' => $accept_language, '@result' => $expected_result, '@actual' => isset($result) ? $result : 'none')));
1526 }
1527 }
1528 }
1529
1530 /**
1531 * Functional tests for a user's ability to change their default language.
1532 */
1533 class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase {
1534 public static function getInfo() {
1535 return array(
1536 'name' => 'User language settings',
1537 'description' => "Tests user's ability to change their default language.",
1538 'group' => 'Locale',
1539 );
1540 }
1541
1542 function setUp() {
1543 parent::setUp('locale');
1544 }
1545
1546 /**
1547 * Test if user can change their default language.
1548 */
1549 function testUserLanguageConfiguration() {
1550 global $base_url;
1551
1552 // User to add and remove language.
1553 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
1554 // User to change their default language.
1555 $web_user = $this->drupalCreateUser();
1556
1557 // Add custom language.
1558 $this->drupalLogin($admin_user);
1559 // Code for the language.
1560 $langcode = 'xx';
1561 // The English name for the language.
1562 $name = $this->randomName(16);
1563 $edit = array(
1564 'predefined_langcode' => 'custom',
1565 'langcode' => $langcode,
1566 'name' => $name,
1567 'direction' => '0',
1568 );
1569 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1570
1571 // Add custom language and disable it.
1572 // Code for the language.
1573 $langcode_disabled = 'xx-yy';
1574 // The English name for the language. This will be translated.
1575 $name_disabled = $this->randomName(16);
1576 $edit = array(
1577 'predefined_langcode' => 'custom',
1578 'langcode' => $langcode_disabled,
1579 'name' => $name_disabled,
1580 'direction' => '0',
1581 );
1582 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1583 // Disable the language.
1584 $edit = array(
1585 'languages[' . $langcode_disabled . '][enabled]' => FALSE,
1586 );
1587 $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
1588 $this->drupalLogout();
1589
1590 // Login as normal user and edit account settings.
1591 $this->drupalLogin($web_user);
1592 $path = 'user/' . $web_user->uid . '/edit';
1593 $this->drupalGet($path);
1594 // Ensure language settings fieldset is available.
1595 $this->assertText(t('Language'), t('Language selector available.'));
1596 // Ensure custom language is present.
1597 $this->assertText($name, t('Language present on form.'));
1598 // Ensure disabled language isn't present.
1599 $this->assertNoText($name_disabled, t('Disabled language not present on form.'));
1600 // Switch to our custom language.
1601 $edit = array(
1602 'language' => $langcode,
1603 );
1604 $this->drupalPost($path, $edit, t('Save'));
1605 // Ensure form was submitted successfully.
1606 $this->assertText(t('The changes have been saved.'), t('Changes were saved.'));
1607 // Check if language was changed.
1608 $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-language-' . $langcode));
1609 $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language successfully updated.'));
1610
1611 $this->drupalLogout();
1612 }
1613 }
1614
1615 /**
1616 * Functional test for language handling during user creation.
1617 */
1618 class LocaleUserCreationTest extends DrupalWebTestCase {
1619
1620 public static function getInfo() {
1621 return array(
1622 'name' => 'User creation',
1623 'description' => 'Tests whether proper language is stored for new users and access to language selector.',
1624 'group' => 'Locale',
1625 );
1626 }
1627
1628 function setUp() {
1629 parent::setUp('locale');
1630 variable_set('user_register', USER_REGISTER_VISITORS);
1631 }
1632
1633 /**
1634 * Functional test for language handling during user creation.
1635 */
1636 function testLocalUserCreation() {
1637 // User to add and remove language and create new users.
1638 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer users'));
1639 $this->drupalLogin($admin_user);
1640
1641 // Add predefined language.
1642 $langcode = 'fr';
1643 $edit = array(
1644 'predefined_langcode' => 'fr',
1645 );
1646 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1647 $this->assertText($langcode, t('Language added successfully.'));
1648 $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), t('Correct page redirection.'));
1649
1650 // Set language negotiation.
1651 $edit = array(
1652 'language[enabled][locale-url]' => TRUE,
1653 );
1654 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
1655 $this->assertText(t('Language negotiation configuration saved.'), t('Set language negotiation.'));
1656
1657 // Check if the language selector is available on admin/people/create and
1658 // set to the currently active language.
1659 $this->drupalGet($langcode . '/admin/people/create');
1660 $this->assertFieldChecked("edit-language-$langcode", t('Global language set in the language selector.'));
1661
1662 // Create a user with the admin/people/create form and check if the correct
1663 // language is set.
1664 $username = $this->randomName(10);
1665 $edit = array(
1666 'name' => $username,
1667 'mail' => $this->randomName(4) . '@example.com',
1668 'pass[pass1]' => $username,
1669 'pass[pass2]' => $username,
1670 );
1671
1672 $this->drupalPost($langcode . '/admin/people/create', $edit, t('Create new account'));
1673
1674 $user = user_load_by_name($username);
1675 $this->assertEqual($user->language, $langcode, t('New user has correct language set.'));
1676
1677 // Register a new user and check if the language selector is hidden.
1678 $this->drupalLogout();
1679
1680 $this->drupalGet($langcode . '/user/register');
1681 $this->assertNoFieldByName('language[fr]', t('Language selector is not accessible.'));
1682
1683 $username = $this->randomName(10);
1684 $edit = array(
1685 'name' => $username,
1686 'mail' => $this->randomName(4) . '@example.com',
1687 );
1688
1689 $this->drupalPost($langcode . '/user/register', $edit, t('Create new account'));
1690
1691 $user = user_load_by_name($username);
1692 $this->assertEqual($user->language, $langcode, t('New user has correct language set.'));
1693
1694 // Test if the admin can use the language selector and if the
1695 // correct language is was saved.
1696 $user_edit = $langcode . '/user/' . $user->uid . '/edit';
1697
1698 $this->drupalLogin($admin_user);
1699 $this->drupalGet($user_edit);
1700 $this->assertFieldChecked("edit-language-$langcode", t('Language selector is accessible and correct language is selected.'));
1701
1702 // Set pass_raw so we can login the new user.
1703 $user->pass_raw = $this->randomName(10);
1704 $edit = array(
1705 'pass[pass1]' => $user->pass_raw,
1706 'pass[pass2]' => $user->pass_raw,
1707 );
1708
1709 $this->drupalPost($user_edit, $edit, t('Save'));
1710
1711 $this->drupalLogin($user);
1712 $this->drupalGet($user_edit);
1713 $this->assertFieldChecked("edit-language-$langcode", t('Language selector is accessible and correct language is selected.'));
1714 }
1715 }
1716
1717 /**
1718 * Functional tests for configuring a different path alias per language.
1719 */
1720 class LocalePathFunctionalTest extends DrupalWebTestCase {
1721 public static function getInfo() {
1722 return array(
1723 'name' => 'Path language settings',
1724 'description' => 'Checks you can configure a language for individual url aliases.',
1725 'group' => 'Locale',
1726 );
1727 }
1728
1729 function setUp() {
1730 parent::setUp('locale', 'path');
1731 }
1732
1733 /**
1734 * Test if a language can be associated with a path alias.
1735 */
1736 function testPathLanguageConfiguration() {
1737 global $base_url;
1738
1739 // User to add and remove language.
1740 $admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages'));
1741
1742 // Add custom language.
1743 $this->drupalLogin($admin_user);
1744 // Code for the language.
1745 $langcode = 'xx';
1746 // The English name for the language.
1747 $name = $this->randomName(16);
1748 // The domain prefix.
1749 $prefix = $langcode;
1750 $edit = array(
1751 'predefined_langcode' => 'custom',
1752 'langcode' => $langcode,
1753 'name' => $name,
1754 'direction' => '0',
1755 );
1756 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1757
1758 // Set path prefix.
1759 $edit = array( "prefix[$langcode]" => $prefix );
1760 $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
1761
1762 // Check that the "xx" front page is readily available because path prefix
1763 // negotiation is pre-configured.
1764 $this->drupalGet($prefix);
1765 $this->assertText(t('Welcome to Drupal'), t('The "xx" front page is readibly available.'));
1766
1767 // Create a node.
1768 $node = $this->drupalCreateNode(array('type' => 'page'));
1769
1770 // Create a path alias in default language (English).
1771 $path = 'admin/config/search/path/add';
1772 $english_path = $this->randomName(8);
1773 $edit = array(
1774 'source' => 'node/' . $node->nid,
1775 'alias' => $english_path,
1776 'langcode' => 'en',
1777 );
1778 $this->drupalPost($path, $edit, t('Save'));
1779
1780 // Create a path alias in new custom language.
1781 $custom_language_path = $this->randomName(8);
1782 $edit = array(
1783 'source' => 'node/' . $node->nid,
1784 'alias' => $custom_language_path,
1785 'langcode' => $langcode,
1786 );
1787 $this->drupalPost($path, $edit, t('Save'));
1788
1789 // Confirm English language path alias works.
1790 $this->drupalGet($english_path);
1791 $this->assertText($node->title, t('English alias works.'));
1792
1793 // Confirm custom language path alias works.
1794 $this->drupalGet($prefix . '/' . $custom_language_path);
1795 $this->assertText($node->title, t('Custom language alias works.'));
1796
1797 // Create a custom path.
1798 $custom_path = $this->randomName(8);
1799
1800 // Check priority of language for alias by source path.
1801 $edit = array(
1802 'source' => 'node/' . $node->nid,
1803 'alias' => $custom_path,
1804 'langcode' => LANGUAGE_NONE,
1805 );
1806 path_save($edit);
1807 $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en');
1808 $this->assertEqual($english_path, $lookup_path, t('English language alias has priority.'));
1809 // Same check for language 'xx'.
1810 $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix);
1811 $this->assertEqual($custom_language_path, $lookup_path, t('Custom language alias has priority.'));
1812 path_delete($edit);
1813
1814 // Create language nodes to check priority of aliases.
1815 $first_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
1816 $second_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
1817
1818 // Assign a custom path alias to the first node with the English language.
1819 $edit = array(
1820 'source' => 'node/' . $first_node->nid,
1821 'alias' => $custom_path,
1822 'langcode' => 'en',
1823 );
1824 path_save($edit);
1825
1826 // Assign a custom path alias to second node with LANGUAGE_NONE.
1827 $edit = array(
1828 'source' => 'node/' . $second_node->nid,
1829 'alias' => $custom_path,
1830 'langcode' => LANGUAGE_NONE,
1831 );
1832 path_save($edit);
1833
1834 // Test that both node titles link to our path alias.
1835 $this->drupalGet('<front>');
1836 $custom_path_url = base_path() . (variable_get('clean_url', 0) ? $custom_path : '?q=' . $custom_path);
1837 $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title));
1838 $this->assertTrue(!empty($elements), t('First node links to the path alias.'));
1839 $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title));
1840 $this->assertTrue(!empty($elements), t('Second node links to the path alias.'));
1841
1842 // Confirm that the custom path leads to the first node.
1843 $this->drupalGet($custom_path);
1844 $this->assertText($first_node->title, t('Custom alias returns first node.'));
1845
1846 // Confirm that the custom path with prefix leads to the second node.
1847 $this->drupalGet($prefix . '/' . $custom_path);
1848 $this->assertText($second_node->title, t('Custom alias with prefix returns second node.'));
1849
1850 }
1851 }
1852
1853 /**
1854 * Functional tests for multilingual support on nodes.
1855 */
1856 class LocaleContentFunctionalTest extends DrupalWebTestCase {
1857 public static function getInfo() {
1858 return array(
1859 'name' => 'Content language settings',
1860 'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.',
1861 'group' => 'Locale',
1862 );
1863 }
1864
1865 function setUp() {
1866 parent::setUp('locale');
1867 }
1868
1869 /**
1870 * Verifies that machine name fields are always LTR.
1871 */
1872 function testMachineNameLTR() {
1873 // User to add and remove language.
1874 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
1875
1876 // Log in as admin.
1877 $this->drupalLogin($admin_user);
1878
1879 // Verify that the machine name field is LTR for a new content type.
1880 $this->drupalGet('admin/structure/types/add');
1881 $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when no additional language is configured.');
1882
1883 // Install the Arabic language (which is RTL) and configure as the default.
1884 $edit = array();
1885 $edit['predefined_langcode'] = 'ar';
1886 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1887
1888 $edit = array();
1889 $edit['site_default'] = 'ar';
1890 $this->drupalPost(NULL, $edit, t('Save configuration'));
1891
1892 // Verify that the machine name field is still LTR for a new content type.
1893 $this->drupalGet('admin/structure/types/add');
1894 $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when the default language is RTL.');
1895 }
1896
1897 /**
1898 * Test if a content type can be set to multilingual and language is present.
1899 */
1900 function testContentTypeLanguageConfiguration() {
1901 global $base_url;
1902
1903 // User to add and remove language.
1904 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
1905 // User to create a node.
1906 $web_user = $this->drupalCreateUser(array('create article content', 'create page content', 'edit any page content'));
1907
1908 // Add custom language.
1909 $this->drupalLogin($admin_user);
1910 // Code for the language.
1911 $langcode = 'xx';
1912 // The English name for the language.
1913 $name = $this->randomName(16);
1914 $edit = array(
1915 'predefined_langcode' => 'custom',
1916 'langcode' => $langcode,
1917 'name' => $name,
1918 'direction' => '0',
1919 );
1920 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1921
1922 // Add disabled custom language.
1923 // Code for the language.
1924 $langcode_disabled = 'xx-yy';
1925 // The English name for the language.
1926 $name_disabled = $this->randomName(16);
1927 $edit = array(
1928 'predefined_langcode' => 'custom',
1929 'langcode' => $langcode_disabled,
1930 'name' => $name_disabled,
1931 'direction' => '0',
1932 );
1933 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1934 // Disable second custom language.
1935 $path = 'admin/config/regional/language';
1936 $edit = array(
1937 'languages[' . $langcode_disabled . '][enabled]' => FALSE,
1938 );
1939 $this->drupalPost($path, $edit, t('Save configuration'));
1940
1941 // Set "Basic page" content type to use multilingual support.
1942 $this->drupalGet('admin/structure/types/manage/page');
1943 $this->assertText(t('Multilingual support'), t('Multilingual support fieldset present on content type configuration form.'));
1944 $edit = array(
1945 'node_type_language' => 1,
1946 );
1947 $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
1948 $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
1949 $this->drupalLogout();
1950
1951 // Verify language selection is not present on add article form.
1952 $this->drupalLogin($web_user);
1953 $this->drupalGet('node/add/article');
1954 // Verify language select list is not present.
1955 $this->assertNoFieldByName('language', NULL, t('Language select not present on add article form.'));
1956
1957 // Verify language selection appears on add "Basic page" form.
1958 $this->drupalGet('node/add/page');
1959 // Verify language select list is present.
1960 $this->assertFieldByName('language', NULL, t('Language select present on add Basic page form.'));
1961 // Ensure enabled language appears.
1962 $this->assertText($name, t('Enabled language present.'));
1963 // Ensure disabled language doesn't appear.
1964 $this->assertNoText($name_disabled, t('Disabled language not present.'));
1965
1966 // Create "Basic page" content.
1967 $node_title = $this->randomName();
1968 $node_body = $this->randomName();
1969 $edit = array(
1970 'type' => 'page',
1971 'title' => $node_title,
1972 'body' => array($langcode => array(array('value' => $node_body))),
1973 'language' => $langcode,
1974 );
1975 $node = $this->drupalCreateNode($edit);
1976 // Edit the content and ensure correct language is selected.
1977 $path = 'node/' . $node->nid . '/edit';
1978 $this->drupalGet($path);
1979 $this->assertRaw('<option value="' . $langcode . '" selected="selected">' . $name . '</option>', t('Correct language selected.'));
1980 // Ensure we can change the node language.
1981 $edit = array(
1982 'language' => 'en',
1983 );
1984 $this->drupalPost($path, $edit, t('Save'));
1985 $this->assertRaw(t('%title has been updated.', array('%title' => $node_title)), t('Basic page content updated.'));
1986
1987 $this->drupalLogout();
1988 }
1989
1990 /**
1991 * Test if a dir and lang tags exist in node's attributes.
1992 */
1993 function testContentTypeDirLang() {
1994 // User to add and remove language.
1995 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
1996 // User to create a node.
1997 $web_user = $this->drupalCreateUser(array('create article content', 'edit own article content'));
1998
1999 // Login as admin.
2000 $this->drupalLogin($admin_user);
2001
2002 // Install Arabic language.
2003 $edit = array();
2004 $edit['predefined_langcode'] = 'ar';
2005 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2006
2007 // Install Spanish language.
2008 $edit = array();
2009 $edit['predefined_langcode'] = 'es';
2010 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2011
2012 // Set "Article" content type to use multilingual support.
2013 $this->drupalGet('admin/structure/types/manage/article');
2014 $edit = array(
2015 'node_type_language' => 1,
2016 );
2017 $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
2018 $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Article')), t('Article content type has been updated.'));
2019 $this->drupalLogout();
2020
2021 // Login as web user to add new article.
2022 $this->drupalLogin($web_user);
2023
2024 // Create three nodes: English, Arabic and Spanish.
2025 $node_en = $this->createNodeArticle('en');
2026 $node_ar = $this->createNodeArticle('ar');
2027 $node_es = $this->createNodeArticle('es');
2028
2029 $this->drupalGet('node');
2030
2031 // Check if English node does not have lang tag.
2032 $pattern = '|id="node-' . $node_en->nid . '"[^<>]*lang="en"|';
2033 $this->assertNoPattern($pattern, t('The lang tag has not been assigned to the English node.'));
2034
2035 // Check if English node does not have dir tag.
2036 $pattern = '|id="node-' . $node_en->nid . '"[^<>]*dir="ltr"|';
2037 $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the English node.'));
2038
2039 // Check if Arabic node has lang="ar" & dir="rtl" tags.
2040 $pattern = '|id="node-' . $node_ar->nid . '"[^<>]*lang="ar" dir="rtl"|';
2041 $this->assertPattern($pattern, t('The lang and dir tags have been assigned correctly to the Arabic node.'));
2042
2043 // Check if Spanish node has lang="es" tag.
2044 $pattern = '|id="node-' . $node_es->nid . '"[^<>]*lang="es"|';
2045 $this->assertPattern($pattern, t('The lang tag has been assigned correctly to the Spanish node.'));
2046
2047 // Check if Spanish node does not have dir="ltr" tag.
2048 $pattern = '|id="node-' . $node_es->nid . '"[^<>]*lang="es" dir="ltr"|';
2049 $this->assertNoPattern($pattern, t('The dir tag has not been assigned to the Spanish node.'));
2050
2051 $this->drupalLogout();
2052 }
2053
2054 /**
2055 * Create node in a specific language.
2056 */
2057 protected function createNodeArticle($langcode) {
2058 $this->drupalGet('node/add/article');
2059 $node_title = $this->randomName();
2060 $node_body = $this->randomName();
2061 $edit = array(
2062 'type' => 'article',
2063 'title' => $node_title,
2064 'body' => array($langcode => array(array('value' => $node_body))),
2065 'language' => $langcode,
2066 'promote' => 1,
2067 );
2068 return $this->drupalCreateNode($edit);
2069 }
2070 }
2071
2072 /**
2073 * Test UI language negotiation
2074 *
2075 * 1. URL (PATH) > DEFAULT
2076 * UI Language base on URL prefix, browser language preference has no
2077 * influence:
2078 * admin/config
2079 * UI in site default language
2080 * zh-hans/admin/config
2081 * UI in Chinese
2082 * blah-blah/admin/config
2083 * 404
2084 * 2. URL (PATH) > BROWSER > DEFAULT
2085 * admin/config
2086 * UI in user's browser language preference if the site has that
2087 * language enabled, if not, the default language
2088 * zh-hans/admin/config
2089 * UI in Chinese
2090 * blah-blah/admin/config
2091 * 404
2092 * 3. URL (DOMAIN) > DEFAULT
2093 * http://example.com/admin/config
2094 * UI language in site default
2095 * http://example.cn/admin/config
2096 * UI language in Chinese
2097 */
2098 class LocaleUILanguageNegotiationTest extends DrupalWebTestCase {
2099 public static function getInfo() {
2100 return array(
2101 'name' => 'UI language negotiation',
2102 'description' => 'Test UI language switching by url path prefix and domain.',
2103 'group' => 'Locale',
2104 );
2105 }
2106
2107 function setUp() {
2108 parent::setUp('locale', 'locale_test');
2109 require_once DRUPAL_ROOT . '/core/includes/language.inc';
2110 drupal_load('module', 'locale');
2111 $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'administer blocks'));
2112 $this->drupalLogin($admin_user);
2113 }
2114
2115 /**
2116 * Tests for language switching by URL path.
2117 */
2118 function testUILanguageNegotiation() {
2119 // A few languages to switch to.
2120 // This one is unknown, should get the default lang version.
2121 $langcode_unknown = 'blah-blah';
2122 // For testing browser lang preference.
2123 $langcode_browser_fallback = 'vi';
2124 // For testing path prefix.
2125 $langcode = 'zh-hans';
2126 // For setting browser language preference to 'vi'.
2127 $http_header_browser_fallback = array("Accept-Language: $langcode_browser_fallback;q=1");
2128 // For setting browser language preference to some unknown.
2129 $http_header_blah = array("Accept-Language: blah;q=1");
2130
2131 // This domain should switch the UI to Chinese.
2132 $language_domain = 'example.cn';
2133
2134 // Setup the site languages by installing two languages.
2135 require_once DRUPAL_ROOT . '/core/includes/locale.inc';
2136 $language = (object) array(
2137 'langcode' => $langcode_browser_fallback,
2138 );
2139 language_save($language);
2140 $language = (object) array(
2141 'langcode' => $langcode,
2142 );
2143 language_save($language);
2144
2145 // We will look for this string in the admin/config screen to see if the
2146 // corresponding translated string is shown.
2147 $default_string = 'Configure languages for content and the user interface';
2148
2149 // Set the default language in order for the translated string to be registered
2150 // into database when seen by t(). Without doing this, our target string
2151 // is for some reason not found when doing translate search. This might
2152 // be some bug.
2153 drupal_static_reset('language_list');
2154 $languages = language_list(TRUE);
2155 variable_set('language_default', $languages['vi']);
2156 // First visit this page to make sure our target string is searchable.
2157 $this->drupalGet('admin/config');
2158 // Now the t()'ed string is in db so switch the language back to default.
2159 variable_del('language_default');
2160
2161 // Translate the string.
2162 $language_browser_fallback_string = "In $langcode_browser_fallback In $langcode_browser_fallback In $langcode_browser_fallback";
2163 $language_string = "In $langcode In $langcode In $langcode";
2164 // Do a translate search of our target string.
2165 $edit = array( 'string' => $default_string);
2166 $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter'));
2167 // Should find the string and now click edit to post translated string.
2168 $this->clickLink('edit');
2169 $edit = array(
2170 "translations[$langcode_browser_fallback]" => $language_browser_fallback_string,
2171 "translations[$langcode]" => $language_string,
2172 );
2173 $this->drupalPost(NULL, $edit, t('Save translations'));
2174
2175 // Configure URL language rewrite.
2176 variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE);
2177
2178 $tests = array(
2179 // Default, browser preference should have no influence.
2180 array(
2181 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2182 'path' => 'admin/config',
2183 'expect' => $default_string,
2184 'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2185 'http_header' => $http_header_browser_fallback,
2186 'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.',
2187 ),
2188 // Language prefix.
2189 array(
2190 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2191 'path' => "$langcode/admin/config",
2192 'expect' => $language_string,
2193 'expected_provider' => LANGUAGE_NEGOTIATION_URL,
2194 'http_header' => $http_header_browser_fallback,
2195 'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
2196 ),
2197 // Default, go by browser preference.
2198 array(
2199 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_BROWSER),
2200 'path' => 'admin/config',
2201 'expect' => $language_browser_fallback_string,
2202 'expected_provider' => LANGUAGE_NEGOTIATION_BROWSER,
2203 'http_header' => $http_header_browser_fallback,
2204 'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
2205 ),
2206 // Prefix, switch to the language.
2207 array(
2208 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_BROWSER),
2209 'path' => "$langcode/admin/config",
2210 'expect' => $language_string,
2211 'expected_provider' => LANGUAGE_NEGOTIATION_URL,
2212 'http_header' => $http_header_browser_fallback,
2213 'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix',
2214 ),
2215 // Default, browser language preference is not one of site's lang.
2216 array(
2217 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT),
2218 'path' => 'admin/config',
2219 'expect' => $default_string,
2220 'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2221 'http_header' => $http_header_blah,
2222 'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
2223 ),
2224 );
2225
2226 foreach ($tests as $test) {
2227 $this->runTest($test);
2228 }
2229
2230 // Unknown language prefix should return 404.
2231 variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info());
2232 $this->drupalGet("$langcode_unknown/admin/config", array(), $http_header_browser_fallback);
2233 $this->assertResponse(404, "Unknown language path prefix should return 404");
2234
2235 // Setup for domain negotiation, first configure the language to have domain
2236 // URL. We use https and a port to make sure that only the domain name is used.
2237 $edit = array("domain[$langcode]" => "https://$language_domain:99");
2238 $this->drupalPost("admin/config/regional/language/detection/url", $edit, t('Save configuration'));
2239 // Set the site to use domain language negotiation.
2240
2241 $tests = array(
2242 // Default domain, browser preference should have no influence.
2243 array(
2244 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2245 'locale_language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN,
2246 'path' => 'admin/config',
2247 'expect' => $default_string,
2248 'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2249 'http_header' => $http_header_browser_fallback,
2250 'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language',
2251 ),
2252 // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in
2253 // locale_test.module hook_boot() to simulate this.
2254 array(
2255 'language_negotiation' => array(LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2256 'locale_language_negotiation_url_part' => LANGUAGE_NEGOTIATION_URL_DOMAIN,
2257 'locale_test_domain' => $language_domain,
2258 'path' => 'admin/config',
2259 'expect' => $language_string,
2260 'expected_provider' => LANGUAGE_NEGOTIATION_URL,
2261 'http_header' => $http_header_browser_fallback,
2262 'message' => 'URL (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese',
2263 ),
2264 );
2265
2266 foreach ($tests as $test) {
2267 $this->runTest($test);
2268 }
2269 }
2270
2271 protected function runTest($test) {
2272 if (!empty($test['language_negotiation'])) {
2273 $negotiation = array_flip($test['language_negotiation']);
2274 language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation);
2275 }
2276 if (!empty($test['locale_language_negotiation_url_part'])) {
2277 variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']);
2278 }
2279 if (!empty($test['locale_test_domain'])) {
2280 variable_set('locale_test_domain', $test['locale_test_domain']);
2281 }
2282 $this->drupalGet($test['path'], array(), $test['http_header']);
2283 $this->assertText($test['expect'], $test['message']);
2284 $this->assertText(t('Language negotiation provider: @name', array('@name' => $test['expected_provider'])));
2285 }
2286
2287 /**
2288 * Test URL language detection when the requested URL has no language.
2289 */
2290 function testUrlLanguageFallback() {
2291 // Add the Italian language.
2292 $langcode_browser_fallback = 'it';
2293 $language = (object) array(
2294 'langcode' => $langcode_browser_fallback,
2295 );
2296 language_save($language);
2297 $languages = language_list();
2298
2299 // Enable the path prefix for the default language: this way any unprefixed
2300 // URL must have a valid fallback value.
2301 $edit = array('prefix[en]' => 'en');
2302 $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
2303
2304 // Enable browser and URL language detection.
2305 $edit = array(
2306 'language[enabled][locale-browser]' => TRUE,
2307 'language[enabled][locale-url]' => TRUE,
2308 'language[weight][locale-browser]' => -8,
2309 'language[weight][locale-url]' => -10,
2310 );
2311 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2312 $this->drupalGet('admin/config/regional/language/detection');
2313
2314 // Enable the language switcher block.
2315 $edit = array('blocks[locale_language][region]' => 'sidebar_first');
2316 $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
2317
2318 // Access the front page without specifying any valid URL language prefix
2319 // and having as browser language preference a non-default language.
2320 $http_header = array("Accept-Language: $langcode_browser_fallback;q=1");
2321 $language = (object) array('langcode' => '');
2322 $this->drupalGet('', array('language' => $language), $http_header);
2323
2324 // Check that the language switcher active link matches the given browser
2325 // language.
2326 $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $langcode_browser_fallback : "?q=$langcode_browser_fallback"));
2327 $fields = $this->xpath('//div[@id="block-locale-language"]//a[@class="language-link active" and @href=:url]', $args);
2328 $this->assertTrue($fields[0] == $languages[$langcode_browser_fallback]->name, t('The browser language is the URL active language'));
2329
2330 // Check that URLs are rewritten using the given browser language.
2331 $fields = $this->xpath('//div[@id="site-name"]//a[@rel="home" and @href=:url]//span', $args);
2332 $this->assertTrue($fields[0] == 'Drupal', t('URLs are rewritten using the browser language.'));
2333 }
2334
2335 /**
2336 * Test if the url function returns the right url when using different domains for different languages.
2337 */
2338 function testLanguageDomain() {
2339 // Add the Italian language.
2340 $langcode = 'it';
2341 $language = (object) array(
2342 'langcode' => $langcode,
2343 );
2344 language_save($language);
2345 $languages = language_list();
2346
2347 // Enable browser and URL language detection.
2348 $edit = array(
2349 'language[enabled][locale-url]' => TRUE,
2350 'language[weight][locale-url]' => -10,
2351 );
2352 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2353
2354 // Change the domain for the Italian language.
2355 $edit = array(
2356 'locale_language_negotiation_url_part' => 1,
2357 'domain[it]' => 'it.example.com',
2358 );
2359 $this->drupalPost('admin/config/regional/language/detection/url', $edit, t('Save configuration'));
2360
2361 // Build the link we're going to test based on the clean url setting.
2362 $link = (!empty($GLOBALS['conf']['clean_url'])) ? 'it.example.com/admin' : 'it.example.com/?q=admin';
2363
2364 global $is_https;
2365 // Test URL in another language: http://it.example.com/?q=admin.
2366 // Base path gives problems on the testbot, so $correct_link is hard-coded.
2367 // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
2368 $italian_url = url('admin', array('language' => $languages['it']));
2369 $url_scheme = ($is_https) ? 'https://' : 'http://';
2370 $correct_link = $url_scheme . $link;
2371 $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (@url) in accordance with the chosen language', array('@url' => $italian_url)));
2372
2373 // Test https via options.
2374 variable_set('https', TRUE);
2375 $italian_url = url('admin', array('https' => TRUE, 'language' => $languages['it']));
2376 $correct_link = 'https://' . $link;
2377 $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right https url (via options) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
2378 variable_set('https', FALSE);
2379
2380 // Test https via current url scheme.
2381 $temp_https = $is_https;
2382 $is_https = TRUE;
2383 $italian_url = url('admin', array('language' => $languages['it']));
2384 $correct_link = 'https://' . $link;
2385 $this->assertTrue($italian_url == $correct_link, t('The url() function returns the right url (via current url scheme) (@url) in accordance with the chosen language', array('@url' => $italian_url)));
2386 $is_https = $temp_https;
2387 }
2388 }
2389
2390 /**
2391 * Test that URL rewriting works as expected.
2392 */
2393 class LocaleUrlRewritingTest extends DrupalWebTestCase {
2394 public static function getInfo() {
2395 return array(
2396 'name' => 'URL rewriting',
2397 'description' => 'Test that URL rewriting works as expected.',
2398 'group' => 'Locale',
2399 );
2400 }
2401
2402 function setUp() {
2403 parent::setUp('locale');
2404
2405 // Create and login user.
2406 $this->web_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
2407 $this->drupalLogin($this->web_user);
2408
2409 // Install French language.
2410 $edit = array();
2411 $edit['predefined_langcode'] = 'fr';
2412 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2413
2414 // Install Italian language.
2415 $edit = array();
2416 $edit['predefined_langcode'] = 'it';
2417 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2418
2419 // Disable Italian language.
2420 $edit = array('languages[it][enabled]' => FALSE);
2421 $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
2422
2423 // Enable URL language detection and selection.
2424 $edit = array('language[enabled][locale-url]' => 1);
2425 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2426
2427 // Reset static caching.
2428 drupal_static_reset('language_list');
2429 drupal_static_reset('locale_url_outbound_alter');
2430 drupal_static_reset('locale_language_url_rewrite_url');
2431 }
2432
2433 /**
2434 * Check that disabled or non-installed languages are not considered.
2435 */
2436 function testUrlRewritingEdgeCases() {
2437 // Check URL rewriting with a disabled language.
2438 $languages = language_list();
2439 $this->checkUrl($languages['it'], t('Path language is ignored if language is disabled.'), t('URL language negotiation does not work with disabled languages'));
2440
2441 // Check URL rewriting with a non-installed language.
2442 $non_existing = language_default();
2443 $non_existing->langcode = $this->randomName();
2444 $this->checkUrl($non_existing, t('Path language is ignored if language is not installed.'), t('URL language negotiation does not work with non-installed languages'));
2445 }
2446
2447 /**
2448 * Check URL rewriting for the given language.
2449 *
2450 * The test is performed with a fixed URL (the default front page) to simply
2451 * check that language prefixes are not added to it and that the prefixed URL
2452 * is actually not working.
2453 */
2454 private function checkUrl($language, $message1, $message2) {
2455 $options = array('language' => $language);
2456 $base_path = trim(base_path(), '/');
2457 $rewritten_path = trim(str_replace(array('?q=', $base_path), '', url('node', $options)), '/');
2458 $segments = explode('/', $rewritten_path, 2);
2459 $prefix = $segments[0];
2460 $path = isset($segments[1]) ? $segments[1] : $prefix;
2461
2462 // If the rewritten URL has not a language prefix we pick a random prefix so
2463 // we can always check the prefixed URL.
2464 $prefixes = locale_language_negotiation_url_prefixes();
2465 $stored_prefix = isset($prefixes[$language->langcode]) ? $prefixes[$language->langcode] : $this->randomName();
2466 if ($this->assertNotEqual($stored_prefix, $prefix, $message1)) {
2467 $prefix = $stored_prefix;
2468 }
2469
2470 $this->drupalGet("$prefix/$path");
2471 $this->assertResponse(404, $message2);
2472 }
2473 }
2474
2475 /**
2476 * Functional test for multilingual fields.
2477 */
2478 class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase {
2479 public static function getInfo() {
2480 return array(
2481 'name' => 'Multilingual fields',
2482 'description' => 'Test multilingual support for fields.',
2483 'group' => 'Locale',
2484 );
2485 }
2486
2487 function setUp() {
2488 parent::setUp('locale');
2489 // Setup users.
2490 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content'));
2491 $this->drupalLogin($admin_user);
2492
2493 // Add a new language.
2494 require_once DRUPAL_ROOT . '/core/includes/locale.inc';
2495 $language = (object) array(
2496 'langcode' => 'it',
2497 'name' => 'Italian',
2498 );
2499 language_save($language);
2500
2501 // Enable URL language detection and selection.
2502 $edit = array('language[enabled][locale-url]' => '1');
2503 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2504
2505 // Set "Basic page" content type to use multilingual support.
2506 $edit = array(
2507 'node_type_language' => 1,
2508 );
2509 $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
2510 $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), t('Basic page content type has been updated.'));
2511
2512 // Make node body translatable.
2513 $field = field_info_field('body');
2514 $field['translatable'] = TRUE;
2515 field_update_field($field);
2516 }
2517
2518 /**
2519 * Test if field languages are correctly set through the node form.
2520 */
2521 function testMultilingualNodeForm() {
2522 // Create "Basic page" content.
2523 $langcode = LANGUAGE_NONE;
2524 $title_key = "title";
2525 $title_value = $this->randomName(8);
2526 $body_key = "body[$langcode][0][value]";
2527 $body_value = $this->randomName(16);
2528
2529 // Create node to edit.
2530 $edit = array();
2531 $edit[$title_key] = $title_value;
2532 $edit[$body_key] = $body_value;
2533 $edit['language'] = 'en';
2534 $this->drupalPost('node/add/page', $edit, t('Save'));
2535
2536 // Check that the node exists in the database.
2537 $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2538 $this->assertTrue($node, t('Node found in database.'));
2539
2540 $assert = isset($node->body['en']) && !isset($node->body[LANGUAGE_NONE]) && $node->body['en'][0]['value'] == $body_value;
2541 $this->assertTrue($assert, t('Field language correctly set.'));
2542
2543 // Change node language.
2544 $this->drupalGet("node/$node->nid/edit");
2545 $edit = array(
2546 $title_key => $this->randomName(8),
2547 'language' => 'it'
2548 );
2549 $this->drupalPost(NULL, $edit, t('Save'));
2550 $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2551 $this->assertTrue($node, t('Node found in database.'));
2552
2553 $assert = isset($node->body['it']) && !isset($node->body['en']) && $node->body['it'][0]['value'] == $body_value;
2554 $this->assertTrue($assert, t('Field language correctly changed.'));
2555
2556 // Enable content language URL detection.
2557 language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LANGUAGE_NEGOTIATION_URL => 0));
2558
2559 // Test multilingual field language fallback logic.
2560 $this->drupalGet("it/node/$node->nid");
2561 $this->assertRaw($body_value, t('Body correctly displayed using Italian as requested language'));
2562
2563 $this->drupalGet("node/$node->nid");
2564 $this->assertRaw($body_value, t('Body correctly displayed using English as requested language'));
2565 }
2566
2567 /*
2568 * Test multilingual field display settings.
2569 */
2570 function testMultilingualDisplaySettings() {
2571 // Create "Basic page" content.
2572 $langcode = LANGUAGE_NONE;
2573 $title_key = "title";
2574 $title_value = $this->randomName(8);
2575 $body_key = "body[$langcode][0][value]";
2576 $body_value = $this->randomName(16);
2577
2578 // Create node to edit.
2579 $edit = array();
2580 $edit[$title_key] = $title_value;
2581 $edit[$body_key] = $body_value;
2582 $edit['language'] = 'en';
2583 $this->drupalPost('node/add/page', $edit, t('Save'));
2584
2585 // Check that the node exists in the database.
2586 $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2587 $this->assertTrue($node, t('Node found in database.'));
2588
2589 // Check if node body is showed.
2590 $this->drupalGet("node/$node->nid");
2591 $body = $this->xpath('//div[@id=:id]//div[@property="content:encoded"]/p', array(':id' => 'node-' . $node->nid));
2592 $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body is correctly showed.');
2593 }
2594 }
2595
2596 /**
2597 * Functional tests for comment language.
2598 */
2599 class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase {
2600
2601 public static function getInfo() {
2602 return array(
2603 'name' => 'Comment language',
2604 'description' => 'Tests for comment language.',
2605 'group' => 'Locale',
2606 );
2607 }
2608
2609 function setUp() {
2610 parent::setUp('locale', 'locale_test');
2611
2612 // Create and login user.
2613 $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'create article content'));
2614 $this->drupalLogin($admin_user);
2615
2616 // Add language.
2617 $edit = array('predefined_langcode' => 'fr');
2618 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2619
2620 // Set "Article" content type to use multilingual support.
2621 $edit = array('node_type_language' => 1);
2622 $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
2623
2624 // Enable content language negotiation UI.
2625 variable_set('locale_test_content_language_type', TRUE);
2626
2627 // Set interface language detection to user and content language detection
2628 // to URL. Disable inheritance from interface language to ensure content
2629 // language will fall back to the default language if no URL language can be
2630 // detected.
2631 $edit = array(
2632 'language[enabled][locale-user]' => TRUE,
2633 'language_content[enabled][locale-url]' => TRUE,
2634 'language_content[enabled][locale-interface]' => FALSE,
2635 );
2636 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2637
2638 // Change user language preference, this way interface language is always
2639 // French no matter what path prefix the URLs have.
2640 $edit = array('language' => 'fr');
2641 $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save'));
2642 }
2643
2644 /**
2645 * Test that comment language is properly set.
2646 */
2647 function testCommentLanguage() {
2648 drupal_static_reset('language_list');
2649
2650 // Create two nodes, one for english and one for french, and comment each
2651 // node using both english and french as content language by changing URL
2652 // language prefixes. Meanwhile interface language is always French, which
2653 // is the user language preference. This way we can ensure that node
2654 // language and interface language do not influence comment language, as
2655 // only content language has to.
2656 foreach (language_list() as $node_langcode => $node_language) {
2657 $langcode_none = LANGUAGE_NONE;
2658
2659 // Create "Article" content.
2660 $title = $this->randomName();
2661 $edit = array(
2662 "title" => $title,
2663 "body[$langcode_none][0][value]" => $this->randomName(),
2664 "language" => $node_langcode,
2665 );
2666 $this->drupalPost("node/add/article", $edit, t('Save'));
2667 $node = $this->drupalGetNodeByTitle($title);
2668
2669 $prefixes = locale_language_negotiation_url_prefixes();
2670 foreach (language_list() as $langcode => $language) {
2671 // Post a comment with content language $langcode.
2672 $prefix = empty($prefixes[$langcode]) ? '' : $prefixes[$langcode] . '/';
2673 $edit = array("comment_body[$langcode_none][0][value]" => $this->randomName());
2674 $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Save'));
2675
2676 // Check that comment language matches the current content language.
2677 $comment = db_select('comment', 'c')
2678 ->fields('c')
2679 ->condition('nid', $node->nid)
2680 ->orderBy('cid', 'DESC')
2681 ->execute()
2682 ->fetchObject();
2683 $args = array('%node_language' => $node_langcode, '%comment_language' => $comment->language, '%langcode' => $langcode);
2684 $this->assertEqual($comment->language, $langcode, t('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args));
2685 }
2686 }
2687 }
2688 }
2689
2690 /**
2691 * Functional tests for localizing date formats.
2692 */
2693 class LocaleDateFormatsFunctionalTest extends DrupalWebTestCase {
2694
2695 public static function getInfo() {
2696 return array(
2697 'name' => 'Localize date formats',
2698 'description' => 'Tests for the localization of date formats.',
2699 'group' => 'Locale',
2700 );
2701 }
2702
2703 function setUp() {
2704 parent::setUp('locale');
2705
2706 // Create and login user.
2707 $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content'));
2708 $this->drupalLogin($admin_user);
2709 }
2710
2711 /**
2712 * Functional tests for localizing date formats.
2713 */
2714 function testLocalizeDateFormats() {
2715 // Add language.
2716 $edit = array(
2717 'predefined_langcode' => 'fr',
2718 );
2719 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2720
2721 // Set language negotiation.
2722 $language_type = LANGUAGE_TYPE_INTERFACE;
2723 $edit = array(
2724 "{$language_type}[enabled][locale-url]" => TRUE,
2725 );
2726 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2727
2728 // Configure date formats.
2729 $this->drupalGet('admin/config/regional/date-time/locale');
2730 $this->assertText('French', 'Configured languages appear.');
2731 $edit = array(
2732 'date_format_long' => 'd.m.Y - H:i',
2733 'date_format_medium' => 'd.m.Y - H:i',
2734 'date_format_short' => 'd.m.Y - H:i',
2735 );
2736 $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration'));
2737 $this->assertText(t('Configuration saved.'), 'French date formats updated.');
2738 $edit = array(
2739 'date_format_long' => 'j M Y - g:ia',
2740 'date_format_medium' => 'j M Y - g:ia',
2741 'date_format_short' => 'j M Y - g:ia',
2742 );
2743 $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration'));
2744 $this->assertText(t('Configuration saved.'), 'English date formats updated.');
2745
2746 // Create node content.
2747 $node = $this->drupalCreateNode(array('type' => 'article'));
2748
2749 // Configure format for the node posted date changes with the language.
2750 $this->drupalGet('node/' . $node->nid);
2751 $english_date = format_date($node->created, 'custom', 'j M Y');
2752 $this->assertText($english_date, t('English date format appears'));
2753 $this->drupalGet('fr/node/' . $node->nid);
2754 $french_date = format_date($node->created, 'custom', 'd.m.Y');
2755 $this->assertText($french_date, t('French date format appears'));
2756 }
2757 }
2758
2759 /**
2760 * Functional test for language types/negotiation info.
2761 */
2762 class LocaleLanguageNegotiationInfoFunctionalTest extends DrupalWebTestCase {
2763
2764 public static function getInfo() {
2765 return array(
2766 'name' => 'Language negotiation info',
2767 'description' => 'Tests alterations to language types/negotiation info.',
2768 'group' => 'Locale',
2769 );
2770 }
2771
2772 function setUp() {
2773 parent::setUp('locale');
2774 require_once DRUPAL_ROOT .'/core/includes/language.inc';
2775 $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
2776 $this->drupalLogin($admin_user);
2777 $this->drupalPost('admin/config/regional/language/add', array('predefined_langcode' => 'it'), t('Add language'));
2778 }
2779
2780 /**
2781 * Tests alterations to language types/negotiation info.
2782 */
2783 function testInfoAlterations() {
2784 // Enable language type/negotiation info alterations.
2785 variable_set('locale_test_language_types', TRUE);
2786 variable_set('locale_test_language_negotiation_info', TRUE);
2787 $this->languageNegotiationUpdate();
2788
2789 // Check that fixed language types are properly configured without the need
2790 // of saving the language negotiation settings.
2791 $this->checkFixedLanguageTypes();
2792
2793 // Make the content language type configurable by updating the language
2794 // negotiation settings with the proper flag enabled.
2795 variable_set('locale_test_content_language_type', TRUE);
2796 $this->languageNegotiationUpdate();
2797 $type = LANGUAGE_TYPE_CONTENT;
2798 $language_types = variable_get('language_types', drupal_language_types());
2799 $this->assertTrue($language_types[$type], t('Content language type is configurable.'));
2800
2801 // Enable some core and custom language providers. The test language type is
2802 // supposed to be configurable.
2803 $test_type = 'test_language_type';
2804 $provider = LANGUAGE_NEGOTIATION_INTERFACE;
2805 $test_provider = 'test_language_provider';
2806 $form_field = $type . '[enabled]['. $provider .']';
2807 $edit = array(
2808 $form_field => TRUE,
2809 $type . '[enabled][' . $test_provider . ']' => TRUE,
2810 $test_type . '[enabled][' . $test_provider . ']' => TRUE,
2811 );
2812 $this->drupalPost('admin/config/regional/language/detection', $edit, t('Save settings'));
2813
2814 // Remove the interface language provider by updating the language
2815 // negotiation settings with the proper flag enabled.
2816 variable_set('locale_test_language_negotiation_info_alter', TRUE);
2817 $this->languageNegotiationUpdate();
2818 $negotiation = variable_get("language_negotiation_$type", array());
2819 $this->assertFalse(isset($negotiation[$provider]), t('Interface language provider removed from the stored settings.'));
2820 $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, t('Interface language provider unavailable.'));
2821
2822 // Check that type-specific language providers can be assigned only to the
2823 // corresponding language types.
2824 foreach (language_types_configurable() as $type) {
2825 $form_field = $type . '[enabled][test_language_provider_ts]';
2826 if ($type == $test_type) {
2827 $this->assertFieldByXPath("//input[@name=\"$form_field\"]", NULL, t('Type-specific test language provider available for %type.', array('%type' => $type)));
2828 }
2829 else {
2830 $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, t('Type-specific test language provider unavailable for %type.', array('%type' => $type)));
2831 }
2832 }
2833
2834 // Check language negotiation results.
2835 $this->drupalGet('');
2836 $last = variable_get('locale_test_language_negotiation_last', array());
2837 foreach (language_types() as $type) {
2838 $langcode = $last[$type];
2839 $value = $type == LANGUAGE_TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en';
2840 $this->assertEqual($langcode, $value, t('The negotiated language for %type is %language', array('%type' => $type, '%language' => $langcode)));
2841 }
2842
2843 // Disable locale_test and check that everything is set back to the original
2844 // status.
2845 $this->languageNegotiationUpdate('disable');
2846
2847 // Check that only the core language types are available.
2848 foreach (language_types() as $type) {
2849 $this->assertTrue(strpos($type, 'test') === FALSE, t('The %type language is still available', array('%type' => $type)));
2850 }
2851
2852 // Check that fixed language types are properly configured, even those
2853 // previously set to configurable.
2854 $this->checkFixedLanguageTypes();
2855
2856 // Check that unavailable language providers are not present in the
2857 // negotiation settings.
2858 $negotiation = variable_get("language_negotiation_$type", array());
2859 $this->assertFalse(isset($negotiation[$test_provider]), t('The disabled test language provider is not part of the content language negotiation settings.'));
2860
2861 // Check that configuration page presents the correct options and settings.
2862 $this->assertNoRaw(t('Test language detection'), t('No test language type configuration available.'));
2863 $this->assertNoRaw(t('This is a test language provider'), t('No test language provider available.'));
2864 }
2865
2866 /**
2867 * Update language types/negotiation information.
2868 *
2869 * Manually invoke locale_modules_enabled()/locale_modules_disabled() since
2870 * they would not be invoked after enabling/disabling locale_test the first
2871 * time.
2872 */
2873 protected function languageNegotiationUpdate($op = 'enable') {
2874 static $last_op = NULL;
2875 $modules = array('locale_test');
2876
2877 // Enable/disable locale_test only if we did not already before.
2878 if ($last_op != $op) {
2879 $function = "module_{$op}";
2880 $function($modules);
2881 // Reset hook implementation cache.
2882 module_implements_reset();
2883 }
2884
2885 drupal_static_reset('language_types_info');
2886 drupal_static_reset('language_negotiation_info');
2887 $function = "locale_modules_{$op}d";
2888 if (function_exists($function)) {
2889 $function($modules);
2890 }
2891
2892 $this->drupalGet('admin/config/regional/language/detection');
2893 }
2894
2895 /**
2896 * Check that language negotiation for fixed types matches the stored one.
2897 */
2898 protected function checkFixedLanguageTypes() {
2899 drupal_static_reset('language_types_info');
2900 foreach (language_types_info() as $type => $info) {
2901 if (isset($info['fixed'])) {
2902 $negotiation = variable_get("language_negotiation_$type", array());
2903 $equal = count($info['fixed']) == count($negotiation);
2904 while ($equal && list($id) = each($negotiation)) {
2905 list(, $info_id) = each($info['fixed']);
2906 $equal = $info_id == $id;
2907 }
2908 $this->assertTrue($equal, t('language negotiation for %type is properly set up', array('%type' => $type)));
2909 }
2910 }
2911 }
2912 }