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