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