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