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