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 */ 27class 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 */ 187class 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 */ 215class 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 */ 304class 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 */ 750class 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 = drupal_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 834msgid "" 835msgstr "" 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 842msgid "One sheep" 843msgid_plural "@count sheep" 844msgstr[0] "un mouton" 845msgstr[1] "@count moutons" 846 847msgid "Monday" 848msgstr "lundi" 849EOF; 850 } 851 852 /** 853 * Returns a .po file with a complex plural formula. 854 */ 855 function getPoFileWithComplexPlural() { 856 return <<< EOF 857msgid "" 858msgstr "" 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 865msgid "1 hour" 866msgid_plural "@count hours" 867msgstr[0] "@count sat" 868msgstr[1] "@count sata" 869msgstr[2] "@count sati" 870 871msgid "Monday" 872msgstr "Ponedjeljak" 873EOF; 874 } 875} 876 877/** 878 * Functional tests for the import of translation files. 879 */ 880class 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 = drupal_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 1128msgid "" 1129msgstr "" 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 1136msgid "One sheep" 1137msgid_plural "@count sheep" 1138msgstr[0] "un mouton" 1139msgstr[1] "@count moutons" 1140 1141msgid "Monday" 1142msgstr "lundi" 1143 1144msgid "Tuesday" 1145msgstr "mardi" 1146 1147msgid "Wednesday" 1148msgstr "mercredi" 1149 1150msgid "Thursday" 1151msgstr "jeudi" 1152 1153msgid "Friday" 1154msgstr "vendredi" 1155 1156msgid "Saturday" 1157msgstr "samedi" 1158 1159msgid "Sunday" 1160msgstr "dimanche" 1161EOF; 1162 } 1163 1164 /** 1165 * Helper function that returns a bad .po file. 1166 */ 1167 function getBadPoFile() { 1168 return <<< EOF 1169msgid "" 1170msgstr "" 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 1177msgid "Save configuration" 1178msgstr "Enregistrer la configuration" 1179 1180msgid "edit" 1181msgstr "modifier<img SRC="javascript:alert(\'xss\');">" 1182 1183msgid "delete" 1184msgstr "supprimer<script>alert('xss');</script>" 1185 1186EOF; 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 1195msgid "" 1196msgstr "" 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 1203msgid "Monday" 1204msgstr "Montag" 1205 1206msgid "Day" 1207msgstr "Jour" 1208EOF; 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 1218msgid "" 1219msgstr "" 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 1226msgctxt "Long month name" 1227msgid "May" 1228msgstr "Svibanj" 1229 1230msgid "May" 1231msgstr "Svi." 1232EOF; 1233 } 1234 1235 /** 1236 * Helper function that returns a .po file with an empty last item. 1237 */ 1238 function getPoFileWithEmptyMsgstr() { 1239 return <<< EOF 1240msgid "" 1241msgstr "" 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 1248msgid "Operations" 1249msgstr "" 1250 1251EOF; 1252 } 1253 /** 1254 * Helper function that returns a .po file with an empty last item. 1255 */ 1256 function getPoFileWithMsgstr() { 1257 return <<< EOF 1258msgid "" 1259msgstr "" 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 1266msgid "Operations" 1267msgstr "Műveletek" 1268 1269msgid "Will not appear in Drupal core, so we can ensure the test passes" 1270msgstr "" 1271 1272EOF; 1273 } 1274 1275 1276 /** 1277 * Returns a .po file with a missing plural formula. 1278 */ 1279 function getPoFileWithMissingPlural() { 1280 return <<< EOF 1281msgid "" 1282msgstr "" 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 1288msgid "Monday" 1289msgstr "Ponedjeljak" 1290EOF; 1291 } 1292 1293 /** 1294 * Returns a .po file with a broken plural formula. 1295 */ 1296 function getPoFileWithBrokenPlural() { 1297 return <<< EOF 1298msgid "" 1299msgstr "" 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 1306msgid "Monday" 1307msgstr "lundi" 1308EOF; 1309 } 1310 1311} 1312 1313/** 1314 * Functional tests for the export of translation files. 1315 */ 1316class 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 = drupal_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 1380msgid "" 1381msgstr "" 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 1388msgid "Monday" 1389msgstr "lundi" 1390EOF; 1391 } 1392 1393} 1394 1395/** 1396 * Tests for the st() function. 1397 */ 1398class 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 */ 1427class 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 */ 1554class 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 */ 1572class 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 */ 1650class 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 */ 1770class 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 */ 1865class 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 */ 1967class 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 */ 2106class 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 */ 2308class 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 */ 2589class 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 */ 2740class 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 */ 2857class 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 */ 2981class 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 */ 3050class 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 = array_keys($negotiation) === array_values($info['fixed']); 3192 $this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type))); 3193 } 3194 } 3195 } 3196} 3197 3198/** 3199 * Functional tests for CSS alter functions. 3200 */ 3201class LocaleCSSAlterTest extends DrupalWebTestCase { 3202 public static function getInfo() { 3203 return array( 3204 'name' => 'CSS altering', 3205 'description' => 'Test CSS alter functions.', 3206 'group' => 'Locale', 3207 ); 3208 } 3209 3210 function setUp() { 3211 parent::setUp('locale'); 3212 } 3213 3214 /** 3215 * Verifies that -rtl.css file is added directly after LTR .css file. 3216 */ 3217 function testCSSFilesOrderInRTLMode() { 3218 global $base_url; 3219 3220 // User to add and remove language. 3221 $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages')); 3222 3223 // Log in as admin. 3224 $this->drupalLogin($admin_user); 3225 3226 // Install the Arabic language (which is RTL) and configure as the default. 3227 $edit = array(); 3228 $edit['langcode'] = 'ar'; 3229 $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language')); 3230 3231 $edit = array(); 3232 $edit['site_default'] = 'ar'; 3233 $this->drupalPost(NULL, $edit, t('Save configuration')); 3234 3235 // Verify that the -rtl.css file is added directly after LTR file. 3236 $this->drupalGet(''); 3237 $query_string = '?' . variable_get('css_js_query_string', '0'); 3238 $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.'); 3239 $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.'); 3240 $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.'); 3241 } 3242} 3243 3244/** 3245 * Tests locale translation safe string handling. 3246 */ 3247class LocaleStringIsSafeTest extends DrupalWebTestCase { 3248 public static function getInfo() { 3249 return array( 3250 'name' => 'Test if a string is safe', 3251 'description' => 'Tests locale translation safe string handling.', 3252 'group' => 'Locale', 3253 ); 3254 } 3255 3256 function setUp() { 3257 parent::setUp('locale'); 3258 } 3259 3260 /** 3261 * Tests for locale_string_is_safe(). 3262 */ 3263 public function testLocaleStringIsSafe() { 3264 // Check a translatable string without HTML. 3265 $string = 'Hello world!'; 3266 $result = locale_string_is_safe($string); 3267 $this->assertTrue($result); 3268 3269 // Check a translatable string which includes trustable HTML. 3270 $string = 'Hello <strong>world</strong>!'; 3271 $result = locale_string_is_safe($string); 3272 $this->assertTrue($result); 3273 3274 // Check an untranslatable string which includes untrustable HTML (according 3275 // to the locale_string_is_safe() function definition). 3276 $string = 'Hello <img src="world.png" alt="world" />!'; 3277 $result = locale_string_is_safe($string); 3278 $this->assertFalse($result); 3279 3280 // Check a translatable string which includes a token in an href attribute. 3281 $string = 'Hi <a href="[current-user:url]">user</a>'; 3282 $result = locale_string_is_safe($string); 3283 $this->assertTrue($result); 3284 } 3285} 3286