Drupal PHP Cross Reference Content Management Systems

Source: /modules/locale/locale.test - 3061 lines - 120550 bytes - Summary - Text - Print

   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

title

Description

title

Description

title

title

Body