Project

General

Profile

Paste
Download (126 KB) Statistics
| Branch: | Revision:

root / drupal7 / modules / locale / locale.test @ b0dc3a2e

1
<?php
2

    
3
/**
4
 * @file
5
 * Tests for locale.module.
6
 *
7
 * The test file includes:
8
 *  - a functional test for the language configuration forms;
9
 *  - functional tests for the translation functionalities, including searching;
10
 *  - a functional test for the PO files import feature, including validation;
11
 *  - functional tests for translations and templates export feature;
12
 *  - functional tests for the uninstall process;
13
 *  - a functional test for the language switching feature;
14
 *  - a functional test for a user's ability to change their default language;
15
 *  - a functional test for configuring a different path alias per language;
16
 *  - a functional test for configuring a different path alias per language;
17
 *  - a functional test for multilingual support by content type and on nodes.
18
 *  - a functional test for multilingual fields.
19
 *  - a functional test for comment language.
20
 *  - a functional test fot language types/negotiation info.
21
 */
22

    
23

    
24
/**
25
 * Functional tests for the language configuration forms.
26
 */
27
class LocaleConfigurationTest extends DrupalWebTestCase {
28
  public static function getInfo() {
29
    return array(
30
      'name' => 'Language configuration',
31
      'description' => 'Adds a new locale and tests changing its status and the default language.',
32
      'group' => 'Locale',
33
    );
34
  }
35

    
36
  function setUp() {
37
    parent::setUp('locale');
38
  }
39

    
40
  /**
41
   * Functional tests for adding, editing and deleting languages.
42
   */
43
  function testLanguageConfiguration() {
44
    global $base_url;
45

    
46
    // User to add and remove language.
47
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
48
    $this->drupalLogin($admin_user);
49

    
50
    // Add predefined language.
51
    $edit = array(
52
      'langcode' => 'fr',
53
    );
54
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
55
    $this->assertText('fr', 'Language added successfully.');
56
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
57

    
58
    // Add custom language.
59
    // Code for the language.
60
    $langcode = 'xx';
61
    // The English name for the language.
62
    $name = $this->randomName(16);
63
    // The native name for the language.
64
    $native = $this->randomName(16);
65
    // The domain prefix.
66
    $prefix = $langcode;
67
    $edit = array(
68
      'langcode' => $langcode,
69
      'name' => $name,
70
      'native' => $native,
71
      'prefix' => $prefix,
72
      'direction' => '0',
73
    );
74
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
75
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
76
    $this->assertText($langcode, 'Language code found.');
77
    $this->assertText($name, 'Name found.');
78
    $this->assertText($native, 'Native found.');
79
    $this->assertText($native, 'Test language added.');
80

    
81
    // Check if we can change the default language.
82
    $path = 'admin/config/regional/language';
83
    $this->drupalGet($path);
84
    $this->assertFieldChecked('edit-site-default-en', 'English is the default language.');
85
    // Change the default language.
86
    $edit = array(
87
      'site_default' => $langcode,
88
    );
89
    $this->drupalPost(NULL, $edit, t('Save configuration'));
90
    $this->assertNoFieldChecked('edit-site-default-en', 'Default language updated.');
91
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
92

    
93
    // Check if a valid language prefix is added after changing the default
94
    // language.
95
    $this->drupalGet('admin/config/regional/language/edit/en');
96
    $this->assertFieldByXPath('//input[@name="prefix"]', 'en', 'A valid path prefix has been added to the previous default language.');
97

    
98
    // Ensure we can't delete the default language.
99
    $this->drupalGet('admin/config/regional/language/delete/' . $langcode);
100
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
101
    $this->assertText(t('The default language cannot be deleted.'), 'Failed to delete the default language.');
102

    
103
    // Check if we can disable a language.
104
    $edit = array(
105
      'enabled[en]' => FALSE,
106
    );
107
    $this->drupalPost($path, $edit, t('Save configuration'));
108
    $this->assertNoFieldChecked('edit-enabled-en', 'Language disabled.');
109

    
110
    // Set disabled language to be the default and ensure it is re-enabled.
111
    $edit = array(
112
      'site_default' => 'en',
113
    );
114
    $this->drupalPost(NULL, $edit, t('Save configuration'));
115
    $this->assertFieldChecked('edit-enabled-en', 'Default language re-enabled.');
116

    
117
    // Ensure 'edit' link works.
118
    $this->clickLink(t('edit'));
119
    $this->assertTitle(t('Edit language | Drupal'), 'Page title is "Edit language".');
120
    // Edit a language.
121
    $name = $this->randomName(16);
122
    $edit = array(
123
      'name' => $name,
124
    );
125
    $this->drupalPost('admin/config/regional/language/edit/' . $langcode, $edit, t('Save language'));
126
    $this->assertRaw($name, 'The language has been updated.');
127
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
128

    
129
    // Ensure 'delete' link works.
130
    $this->drupalGet('admin/config/regional/language');
131
    $this->clickLink(t('delete'));
132
    $this->assertText(t('Are you sure you want to delete the language'), '"delete" link is correct.');
133
    // Delete an enabled language.
134
    $this->drupalGet('admin/config/regional/language/delete/' . $langcode);
135
    // First test the 'cancel' link.
136
    $this->clickLink(t('Cancel'));
137
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
138
    $this->assertRaw($name, 'The language was not deleted.');
139
    // Delete the language for real. This a confirm form, we do not need any
140
    // fields changed.
141
    $this->drupalPost('admin/config/regional/language/delete/' . $langcode, array(), t('Delete'));
142
    // We need raw here because %locale will add HTML.
143
    $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), 'The test language has been removed.');
144
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
145
    // Verify that language is no longer found.
146
    $this->drupalGet('admin/config/regional/language/delete/' . $langcode);
147
    $this->assertResponse(404, 'Language no longer found.');
148
    // Make sure the "language_count" variable has been updated correctly.
149
    drupal_static_reset('language_list');
150
    $enabled = language_list('enabled');
151
    $this->assertEqual(variable_get('language_count', 1), count($enabled[1]), 'Language count is correct.');
152
    // Delete a disabled language.
153
    // Disable an enabled language.
154
    $edit = array(
155
      'enabled[fr]' => FALSE,
156
    );
157
    $this->drupalPost($path, $edit, t('Save configuration'));
158
    $this->assertNoFieldChecked('edit-enabled-fr', 'French language disabled.');
159
    // Get the count of enabled languages.
160
    drupal_static_reset('language_list');
161
    $enabled = language_list('enabled');
162
    // Delete the disabled language.
163
    $this->drupalPost('admin/config/regional/language/delete/fr', array(), t('Delete'));
164
    // We need raw here because %locale will add HTML.
165
    $this->assertRaw(t('The language %locale has been removed.', array('%locale' => 'French')), 'Disabled language has been removed.');
166
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
167
    // Verify that language is no longer found.
168
    $this->drupalGet('admin/config/regional/language/delete/fr');
169
    $this->assertResponse(404, 'Language no longer found.');
170
    // Make sure the "language_count" variable has not changed.
171
    $this->assertEqual(variable_get('language_count', 1), count($enabled[1]), 'Language count is correct.');
172

    
173

    
174
    // Ensure we can't delete the English language.
175
    $this->drupalGet('admin/config/regional/language/delete/en');
176
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
177
    $this->assertText(t('The English language cannot be deleted.'), 'Failed to delete English language.');
178
  }
179

    
180
}
181

    
182
/**
183
 * Tests localization of the JavaScript libraries.
184
 *
185
 * Currently, only the jQuery datepicker is localized using Drupal translations.
186
 */
187
class LocaleLibraryInfoAlterTest extends DrupalWebTestCase {
188
  public static function getInfo() {
189
    return array(
190
      'name' => 'Javascript library localisation',
191
      'description' => 'Tests the localisation of JavaScript libraries.',
192
      'group' => 'Locale',
193
    );
194
  }
195

    
196
  function setUp() {
197
    parent::setUp('locale', 'locale_test');
198
  }
199

    
200
  /**
201
   * Verifies that the datepicker can be localized.
202
   *
203
   * @see locale_library_info_alter()
204
   */
205
  public function testLibraryInfoAlter() {
206
    drupal_add_library('system', 'ui.datepicker');
207
    $scripts = drupal_get_js();
208
    $this->assertTrue(strpos($scripts, 'locale.datepicker.js'), 'locale.datepicker.js added to scripts.');
209
  }
210
}
211

    
212
/**
213
 * Functional tests for JavaScript parsing for translatable strings.
214
 */
215
class LocaleJavascriptTranslationTest extends DrupalWebTestCase {
216
  public static function getInfo() {
217
    return array(
218
      'name' => 'Javascript translation',
219
      'description' => 'Tests parsing js files for translatable strings',
220
      'group' => 'Locale',
221
    );
222
  }
223

    
224
  function setUp() {
225
    parent::setUp('locale', 'locale_test');
226
  }
227

    
228
  function testFileParsing() {
229

    
230
    $filename = drupal_get_path('module', 'locale_test') . '/locale_test.js';
231

    
232
    // Parse the file to look for source strings.
233
    _locale_parse_js_file($filename);
234

    
235
    // Get all of the source strings that were found.
236
    $source_strings = db_select('locales_source', 's')
237
      ->fields('s', array('source', 'context'))
238
      ->condition('s.location', $filename)
239
      ->execute()
240
      ->fetchAllKeyed();
241

    
242
    // List of all strings that should be in the file.
243
    $test_strings = array(
244
      "Standard Call t" => '',
245
      "Whitespace Call t" => '',
246

    
247
      "Single Quote t" => '',
248
      "Single Quote \\'Escaped\\' t" => '',
249
      "Single Quote Concat strings t" => '',
250

    
251
      "Double Quote t" => '',
252
      "Double Quote \\\"Escaped\\\" t" => '',
253
      "Double Quote Concat strings t" => '',
254

    
255
      "Context !key Args t" => "Context string",
256

    
257
      "Context Unquoted t" => "Context string unquoted",
258
      "Context Single Quoted t" => "Context string single quoted",
259
      "Context Double Quoted t" => "Context string double quoted",
260

    
261
      "Standard Call plural" => '',
262
      "Standard Call @count plural" => '',
263
      "Whitespace Call plural" => '',
264
      "Whitespace Call @count plural" => '',
265

    
266
      "Single Quote plural" => '',
267
      "Single Quote @count plural" => '',
268
      "Single Quote \\'Escaped\\' plural" => '',
269
      "Single Quote \\'Escaped\\' @count plural" => '',
270

    
271
      "Double Quote plural" => '',
272
      "Double Quote @count plural" => '',
273
      "Double Quote \\\"Escaped\\\" plural" => '',
274
      "Double Quote \\\"Escaped\\\" @count plural" => '',
275

    
276
      "Context !key Args plural" => "Context string",
277
      "Context !key Args @count plural" => "Context string",
278

    
279
      "Context Unquoted plural" => "Context string unquoted",
280
      "Context Unquoted @count plural" => "Context string unquoted",
281
      "Context Single Quoted plural" => "Context string single quoted",
282
      "Context Single Quoted @count plural" => "Context string single quoted",
283
      "Context Double Quoted plural" => "Context string double quoted",
284
      "Context Double Quoted @count plural" => "Context string double quoted",
285
    );
286

    
287
    // Assert that all strings were found properly.
288
    foreach ($test_strings as $str => $context) {
289
      $args = array('%source' => $str, '%context' => $context);
290

    
291
      // Make sure that the string was found in the file.
292
      $this->assertTrue(isset($source_strings[$str]), format_string('Found source string: %source', $args));
293

    
294
      // Make sure that the proper context was matched.
295
      $this->assertTrue(isset($source_strings[$str]) && $source_strings[$str] === $context, strlen($context) > 0 ? format_string('Context for %source is %context', $args) : format_string('Context for %source is blank', $args));
296
    }
297

    
298
    $this->assertEqual(count($source_strings), count($test_strings), 'Found correct number of source strings.');
299
  }
300
}
301
/**
302
 * Functional test for string translation and validation.
303
 */
304
class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
305
  public static function getInfo() {
306
    return array(
307
      'name' => 'String translate, search and validate',
308
      'description' => 'Adds a new locale and translates its name. Checks the validation of translation strings and search results.',
309
      'group' => 'Locale',
310
    );
311
  }
312

    
313
  function setUp() {
314
    parent::setUp('locale');
315
  }
316

    
317
  /**
318
   * Adds a language and tests string translation by users with the appropriate permissions.
319
   */
320
  function testStringTranslation() {
321
    global $base_url;
322

    
323
    // User to add and remove language.
324
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
325
    // User to translate and delete string.
326
    $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
327
    // Code for the language.
328
    $langcode = 'xx';
329
    // The English name for the language. This will be translated.
330
    $name = $this->randomName(16);
331
    // The native name for the language.
332
    $native = $this->randomName(16);
333
    // The domain prefix.
334
    $prefix = $langcode;
335
    // This is the language indicator on the translation search screen for
336
    // untranslated strings. Copied straight from locale.inc.
337
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
338
    // This will be the translation of $name.
339
    $translation = $this->randomName(16);
340

    
341
    // Add custom language.
342
    $this->drupalLogin($admin_user);
343
    $edit = array(
344
      'langcode' => $langcode,
345
      'name' => $name,
346
      'native' => $native,
347
      'prefix' => $prefix,
348
      'direction' => '0',
349
    );
350
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
351
    // Add string.
352
    t($name, array(), array('langcode' => $langcode));
353
    // Reset locale cache.
354
    locale_reset();
355
    $this->assertText($langcode, 'Language code found.');
356
    $this->assertText($name, 'Name found.');
357
    $this->assertText($native, 'Native found.');
358
    // No t() here, we do not want to add this string to the database and it's
359
    // surely not translated yet.
360
    $this->assertText($native, 'Test language added.');
361
    $this->drupalLogout();
362

    
363
    // Search for the name and translate it.
364
    $this->drupalLogin($translate_user);
365
    $search = array(
366
      'string' => $name,
367
      'language' => 'all',
368
      'translation' => 'all',
369
      'group' => 'all',
370
    );
371
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
372
    // assertText() seems to remove the input field where $name always could be
373
    // found, so this is not a false assert. See how assertNoText succeeds
374
    // later.
375
    $this->assertText($name, 'Search found the name.');
376
    $this->assertRaw($language_indicator, 'Name is untranslated.');
377
    // Assume this is the only result, given the random name.
378
    $this->clickLink(t('edit'));
379
    // We save the lid from the path.
380
    $matches = array();
381
    preg_match('!admin/config/regional/translate/edit/(\d+)!', $this->getUrl(), $matches);
382
    $lid = $matches[1];
383
    // No t() here, it's surely not translated yet.
384
    $this->assertText($name, 'name found on edit screen.');
385
    $edit = array(
386
      "translations[$langcode]" => $translation,
387
    );
388
    $this->drupalPost(NULL, $edit, t('Save translations'));
389
    $this->assertText(t('The string has been saved.'), 'The string has been saved.');
390
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
391
    $this->assertTrue($name != $translation && t($name, array(), array('langcode' => $langcode)) == $translation, 't() works.');
392
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
393
    // The indicator should not be here.
394
    $this->assertNoRaw($language_indicator, 'String is translated.');
395

    
396
    // Verify that a translation set which has an empty target string can be
397
    // updated without any database error.
398
    db_update('locales_target')
399
      ->fields(array('translation' => ''))
400
      ->condition('language', $langcode, '=')
401
      ->condition('lid', $lid, '=')
402
      ->execute();
403
    $this->drupalPost('admin/config/regional/translate/edit/' . $lid, $edit, t('Save translations'));
404
    $this->assertText(t('The string has been saved.'), 'The string has been saved.');
405

    
406
    // Try to edit a non-existent string and ensure we're redirected correctly.
407
    // Assuming we don't have 999,999 strings already.
408
    $random_lid = 999999;
409
    $this->drupalGet('admin/config/regional/translate/edit/' . $random_lid);
410
    $this->assertText(t('String not found'), 'String not found.');
411
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
412
    $this->drupalLogout();
413

    
414
    // Delete the language.
415
    $this->drupalLogin($admin_user);
416
    $path = 'admin/config/regional/language/delete/' . $langcode;
417
    // This a confirm form, we do not need any fields changed.
418
    $this->drupalPost($path, array(), t('Delete'));
419
    // We need raw here because %locale will add HTML.
420
    $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), 'The test language has been removed.');
421
    // Reload to remove $name.
422
    $this->drupalGet($path);
423
    // Verify that language is no longer found.
424
    $this->assertResponse(404, 'Language no longer found.');
425
    $this->drupalLogout();
426

    
427
    // Delete the string.
428
    $this->drupalLogin($translate_user);
429
    $search = array(
430
      'string' => $name,
431
      'language' => 'all',
432
      'translation' => 'all',
433
      'group' => 'all',
434
    );
435
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
436
    // Assume this is the only result, given the random name.
437
    $this->clickLink(t('delete'));
438
    $this->assertText(t('Are you sure you want to delete the string'), '"delete" link is correct.');
439
    // Delete the string.
440
    $path = 'admin/config/regional/translate/delete/' . $lid;
441
    $this->drupalGet($path);
442
    // First test the 'cancel' link.
443
    $this->clickLink(t('Cancel'));
444
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
445
    $this->assertRaw($name, 'The string was not deleted.');
446
    // Delete the name string.
447
    $this->drupalPost('admin/config/regional/translate/delete/' . $lid, array(), t('Delete'));
448
    $this->assertText(t('The string has been removed.'), 'The string has been removed message.');
449
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
450
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
451
    $this->assertNoText($name, 'Search now can not find the name.');
452
  }
453

    
454
  /*
455
   * Adds a language and checks that the JavaScript translation files are
456
   * properly created and rebuilt on deletion.
457
   */
458
  function testJavaScriptTranslation() {
459
    $user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
460
    $this->drupalLogin($user);
461

    
462
    $langcode = 'xx';
463
    // The English name for the language. This will be translated.
464
    $name = $this->randomName(16);
465
    // The native name for the language.
466
    $native = $this->randomName(16);
467
    // The domain prefix.
468
    $prefix = $langcode;
469

    
470
    // Add custom language.
471
    $edit = array(
472
      'langcode' => $langcode,
473
      'name' => $name,
474
      'native' => $native,
475
      'prefix' => $prefix,
476
      'direction' => '0',
477
    );
478
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
479
    drupal_static_reset('language_list');
480

    
481
    // Build the JavaScript translation file.
482
    $this->drupalGet('admin/config/regional/translate/translate');
483

    
484
    // Retrieve the id of the first string available in the {locales_source}
485
    // table and translate it.
486
    $query = db_select('locales_source', 'l');
487
    $query->addExpression('min(l.lid)', 'lid');
488
    $result = $query->condition('l.location', '%.js%', 'LIKE')
489
      ->condition('l.textgroup', 'default')
490
      ->execute();
491
    $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid;
492
    $edit = array('translations['. $langcode .']' => $this->randomName());
493
    $this->drupalPost($url, $edit, t('Save translations'));
494

    
495
    // Trigger JavaScript translation parsing and building.
496
    require_once DRUPAL_ROOT . '/includes/locale.inc';
497
    _locale_rebuild_js($langcode);
498

    
499
    // Retrieve the JavaScript translation hash code for the custom language to
500
    // check that the translation file has been properly built.
501
    $file = db_select('languages', 'l')
502
      ->fields('l', array('javascript'))
503
      ->condition('language', $langcode)
504
      ->execute()
505
      ->fetchObject();
506
    $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $file->javascript . '.js';
507
    $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file created: %file', array('%file' => $result ? $js_file : 'not found')));
508

    
509
    // Test JavaScript translation rebuilding.
510
    file_unmanaged_delete($js_file);
511
    $this->assertTrue($result = !file_exists($js_file), format_string('JavaScript file deleted: %file', array('%file' => $result ? $js_file : 'found')));
512
    cache_clear_all();
513
    _locale_rebuild_js($langcode);
514
    $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : 'not found')));
515
  }
516

    
517
  /**
518
   * Tests the validation of the translation input.
519
   */
520
  function testStringValidation() {
521
    global $base_url;
522

    
523
    // User to add language and strings.
524
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
525
    $this->drupalLogin($admin_user);
526
    $langcode = 'xx';
527
    // The English name for the language. This will be translated.
528
    $name = $this->randomName(16);
529
    // The native name for the language.
530
    $native = $this->randomName(16);
531
    // The domain prefix.
532
    $prefix = $langcode;
533
    // This is the language indicator on the translation search screen for
534
    // untranslated strings. Copied straight from locale.inc.
535
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
536
    // These will be the invalid translations of $name.
537
    $key = $this->randomName(16);
538
    $bad_translations[$key] = "<script>alert('xss');</script>" . $key;
539
    $key = $this->randomName(16);
540
    $bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
541
    $key = $this->randomName(16);
542
    $bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
543
    $key = $this->randomName(16);
544
    $bad_translations[$key] ="<BODY ONLOAD=alert('xss')>" . $key;
545

    
546
    // Add custom language.
547
    $edit = array(
548
      'langcode' => $langcode,
549
      'name' => $name,
550
      'native' => $native,
551
      'prefix' => $prefix,
552
      'direction' => '0',
553
    );
554
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
555
    // Add string.
556
    t($name, array(), array('langcode' => $langcode));
557
    // Reset locale cache.
558
    $search = array(
559
      'string' => $name,
560
      'language' => 'all',
561
      'translation' => 'all',
562
      'group' => 'all',
563
    );
564
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
565
    // Find the edit path.
566
    $content = $this->drupalGetContent();
567
    $this->assertTrue(preg_match('@(admin/config/regional/translate/edit/[0-9]+)@', $content, $matches), 'Found the edit path.');
568
    $path = $matches[0];
569
    foreach ($bad_translations as $key => $translation) {
570
      $edit = array(
571
        "translations[$langcode]" => $translation,
572
      );
573
      $this->drupalPost($path, $edit, t('Save translations'));
574
      // Check for a form error on the textarea.
575
      $form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
576
      $this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), 'The string was rejected as unsafe.');
577
      $this->assertNoText(t('The string has been saved.'), 'The string was not saved.');
578
    }
579
  }
580

    
581
  /**
582
   * Tests translation search form.
583
   */
584
  function testStringSearch() {
585
    global $base_url;
586

    
587
    // User to add and remove language.
588
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
589
    // User to translate and delete string.
590
    $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
591

    
592
    // Code for the language.
593
    $langcode = 'xx';
594
    // The English name for the language. This will be translated.
595
    $name = $this->randomName(16);
596
    // The native name for the language.
597
    $native = $this->randomName(16);
598
    // The domain prefix.
599
    $prefix = $langcode;
600
    // This is the language indicator on the translation search screen for
601
    // untranslated strings. Copied straight from locale.inc.
602
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
603
    // This will be the translation of $name.
604
    $translation = $this->randomName(16);
605

    
606
    // Add custom language.
607
    $this->drupalLogin($admin_user);
608
    $edit = array(
609
      'langcode' => $langcode,
610
      'name' => $name,
611
      'native' => $native,
612
      'prefix' => $prefix,
613
      'direction' => '0',
614
    );
615
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
616
    // Add string.
617
    t($name, array(), array('langcode' => $langcode));
618
    // Reset locale cache.
619
    locale_reset();
620
    $this->drupalLogout();
621

    
622
    // Search for the name.
623
    $this->drupalLogin($translate_user);
624
    $search = array(
625
      'string' => $name,
626
      'language' => 'all',
627
      'translation' => 'all',
628
      'group' => 'all',
629
    );
630
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
631
    // assertText() seems to remove the input field where $name always could be
632
    // found, so this is not a false assert. See how assertNoText succeeds
633
    // later.
634
    $this->assertText($name, 'Search found the string.');
635

    
636
    // Ensure untranslated string doesn't appear if searching on 'only
637
    // translated strings'.
638
    $search = array(
639
      'string' => $name,
640
      'language' => 'all',
641
      'translation' => 'translated',
642
      'group' => 'all',
643
    );
644
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
645
    $this->assertText(t('No strings available.'), "Search didn't find the string.");
646

    
647
    // Ensure untranslated string appears if searching on 'only untranslated
648
    // strings' in "all" (hasn't been translated to any language).
649
    $search = array(
650
      'string' => $name,
651
      'language' => 'all',
652
      'translation' => 'untranslated',
653
      'group' => 'all',
654
    );
655
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
656
    $this->assertNoText(t('No strings available.'), 'Search found the string.');
657

    
658
    // Ensure untranslated string appears if searching on 'only untranslated
659
    // strings' in the custom language (hasn't been translated to that specific language).
660
    $search = array(
661
      'string' => $name,
662
      'language' => $langcode,
663
      'translation' => 'untranslated',
664
      'group' => 'all',
665
    );
666
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
667
    $this->assertNoText(t('No strings available.'), 'Search found the string.');
668

    
669
    // Add translation.
670
    // Assume this is the only result, given the random name.
671
    $this->clickLink(t('edit'));
672
    // We save the lid from the path.
673
    $matches = array();
674
    preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches);
675
    $lid = $matches[1];
676
    $edit = array(
677
      "translations[$langcode]" => $translation,
678
    );
679
    $this->drupalPost(NULL, $edit, t('Save translations'));
680

    
681
    // Ensure translated string does appear if searching on 'only
682
    // translated strings'.
683
    $search = array(
684
      'string' => $translation,
685
      'language' => 'all',
686
      'translation' => 'translated',
687
      'group' => 'all',
688
    );
689
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
690
    $this->assertNoText(t('No strings available.'), 'Search found the translation.');
691

    
692
    // Ensure translated source string doesn't appear if searching on 'only
693
    // untranslated strings'.
694
    $search = array(
695
      'string' => $name,
696
      'language' => 'all',
697
      'translation' => 'untranslated',
698
      'group' => 'all',
699
    );
700
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
701
    $this->assertText(t('No strings available.'), "Search didn't find the source string.");
702

    
703
    // Ensure translated string doesn't appear if searching on 'only
704
    // untranslated strings'.
705
    $search = array(
706
      'string' => $translation,
707
      'language' => 'all',
708
      'translation' => 'untranslated',
709
      'group' => 'all',
710
    );
711
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
712
    $this->assertText(t('No strings available.'), "Search didn't find the translation.");
713

    
714
    // Ensure translated string does appear if searching on the custom language.
715
    $search = array(
716
      'string' => $translation,
717
      'language' => $langcode,
718
      'translation' => 'all',
719
      'group' => 'all',
720
    );
721
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
722
    $this->assertNoText(t('No strings available.'), 'Search found the translation.');
723

    
724
    // Ensure translated string doesn't appear if searching on English.
725
    $search = array(
726
      'string' => $translation,
727
      'language' => 'en',
728
      'translation' => 'all',
729
      'group' => 'all',
730
    );
731
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
732
    $this->assertText(t('No strings available.'), "Search didn't find the translation.");
733

    
734
    // Search for a string that isn't in the system.
735
    $unavailable_string = $this->randomName(16);
736
    $search = array(
737
      'string' => $unavailable_string,
738
      'language' => 'all',
739
      'translation' => 'all',
740
      'group' => 'all',
741
    );
742
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
743
    $this->assertText(t('No strings available.'), "Search didn't find the invalid string.");
744
  }
745
}
746

    
747
/**
748
 * Tests plural index computation functionality.
749
 */
750
class LocalePluralFormatTest extends DrupalWebTestCase {
751
  public static function getInfo() {
752
    return array(
753
      'name' => 'Plural formula evaluation',
754
      'description' => 'Tests plural formula evaluation for various languages.',
755
      'group' => 'Locale',
756
    );
757
  }
758

    
759
  function setUp() {
760
    parent::setUp('locale', 'locale_test');
761

    
762
    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
763
    $this->drupalLogin($admin_user);
764

    
765
    // Import some .po files with formulas to set up the environment.
766
    // These will also add the languages to the system and enable them.
767
    $this->importPoFile($this->getPoFileWithSimplePlural(), array(
768
      'langcode' => 'fr',
769
    ));
770
    $this->importPoFile($this->getPoFileWithComplexPlural(), array(
771
      'langcode' => 'hr',
772
    ));
773
  }
774

    
775
  /**
776
   * Tests locale_get_plural() functionality.
777
   */
778
  function testGetPluralFormat() {
779
    $this->drupalGet('locale_test_plural_format_page');
780
    $tests = _locale_test_plural_format_tests();
781
    $result = array();
782
    foreach ($tests as $test) {
783
      $this->assertPluralFormat($test['count'], $test['language'], $test['expected-result']);
784
    }
785
  }
786

    
787
  /**
788
   * Helper assert to test locale_get_plural page.
789
   *
790
   * @param $count
791
   *  Number for testing.
792
   * @param $lang
793
   *  Language for testing
794
   * @param $expected_result
795
   *   Expected result.
796
   * @param $message
797
   */
798
  function assertPluralFormat($count, $lang, $expected_result) {
799
    $message_param =  array(
800
      '@lang' => $lang,
801
      '@count' => $count,
802
      '@expected_result' => $expected_result,
803
    );
804
    $message = t("Computed plural index for '@lang' with count @count is @expected_result.", $message_param);
805

    
806
    $message_param = array(
807
      '@lang' => $lang,
808
      '@expected_result' => $expected_result,
809
    );
810
    $this->assertText(format_string('Language: @lang, locale_get_plural: @expected_result.', $message_param, $message));
811
  }
812

    
813
  /**
814
   * Imports a standalone .po file in a given language.
815
   *
816
   * @param $contents
817
   *   Contents of the .po file to import.
818
   * @param $options
819
   *   Additional options to pass to the translation import form.
820
   */
821
  function importPoFile($contents, array $options = array()) {
822
    $name = tempnam('temporary://', "po_") . '.po';
823
    file_put_contents($name, $contents);
824
    $options['files[file]'] = $name;
825
    $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
826
    drupal_unlink($name);
827
  }
828

    
829
  /**
830
   * Returns a .po file with a simple plural formula.
831
   */
832
  function getPoFileWithSimplePlural() {
833
    return <<< EOF
834
msgid ""
835
msgstr ""
836
"Project-Id-Version: Drupal 7\\n"
837
"MIME-Version: 1.0\\n"
838
"Content-Type: text/plain; charset=UTF-8\\n"
839
"Content-Transfer-Encoding: 8bit\\n"
840
"Plural-Forms: nplurals=2; plural=(n!=1);\\n"
841

    
842
msgid "One sheep"
843
msgid_plural "@count sheep"
844
msgstr[0] "un mouton"
845
msgstr[1] "@count moutons"
846

    
847
msgid "Monday"
848
msgstr "lundi"
849
EOF;
850
  }
851

    
852
  /**
853
   * Returns a .po file with a complex plural formula.
854
   */
855
  function getPoFileWithComplexPlural() {
856
    return <<< EOF
857
msgid ""
858
msgstr ""
859
"Project-Id-Version: Drupal 7\\n"
860
"MIME-Version: 1.0\\n"
861
"Content-Type: text/plain; charset=UTF-8\\n"
862
"Content-Transfer-Encoding: 8bit\\n"
863
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
864

    
865
msgid "1 hour"
866
msgid_plural "@count hours"
867
msgstr[0] "@count sat"
868
msgstr[1] "@count sata"
869
msgstr[2] "@count sati"
870

    
871
msgid "Monday"
872
msgstr "Ponedjeljak"
873
EOF;
874
  }
875
}
876

    
877
/**
878
 * Functional tests for the import of translation files.
879
 */
880
class LocaleImportFunctionalTest extends DrupalWebTestCase {
881
  public static function getInfo() {
882
    return array(
883
      'name' => 'Translation import',
884
      'description' => 'Tests the import of locale files.',
885
      'group' => 'Locale',
886
    );
887
  }
888

    
889
  /**
890
   * A user able to create languages and import translations.
891
   */
892
  protected $admin_user = NULL;
893

    
894
  function setUp() {
895
    parent::setUp('locale', 'locale_test');
896

    
897
    $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
898
    $this->drupalLogin($this->admin_user);
899
  }
900

    
901
  /**
902
   * Test import of standalone .po files.
903
   */
904
  function testStandalonePoFile() {
905
    // Try importing a .po file.
906
    $this->importPoFile($this->getPoFile(), array(
907
      'langcode' => 'fr',
908
    ));
909

    
910
    // The import should automatically create the corresponding language.
911
    $this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), 'The language has been automatically created.');
912

    
913
    // The import should have created 7 strings.
914
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 9, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
915

    
916
    // This import should have saved plural forms to have 2 variants.
917
    $this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 2, 'Plural number initialized.');
918

    
919
    // Ensure we were redirected correctly.
920
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate', array('absolute' => TRUE)), 'Correct page redirection.');
921

    
922

    
923
    // Try importing a .po file with invalid tags in the default text group.
924
    $this->importPoFile($this->getBadPoFile(), array(
925
      'langcode' => 'fr',
926
    ));
927

    
928
    // The import should have created 1 string and rejected 2.
929
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
930
    $skip_message = format_plural(2, 'One translation string was skipped because it contains disallowed HTML.', '@count translation strings were skipped because they contain disallowed HTML.');
931
    $this->assertRaw($skip_message, 'Unsafe strings were skipped.');
932

    
933

    
934
    // Try importing a .po file with invalid tags in a non default text group.
935
    $this->importPoFile($this->getBadPoFile(), array(
936
      'langcode' => 'fr',
937
      'group' => 'custom',
938
    ));
939

    
940
    // The import should have created 3 strings.
941
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 3, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
942

    
943

    
944
    // Try importing a .po file which doesn't exist.
945
    $name = $this->randomName(16);
946
    $this->drupalPost('admin/config/regional/translate/import', array(
947
      'langcode' => 'fr',
948
      'files[file]' => $name,
949
      'group' => 'custom',
950
    ), t('Import'));
951
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), 'Correct page redirection.');
952
    $this->assertText(t('File to import not found.'), 'File to import not found message.');
953

    
954

    
955
    // Try importing a .po file with overriding strings, and ensure existing
956
    // strings are kept.
957
    $this->importPoFile($this->getOverwritePoFile(), array(
958
      'langcode' => 'fr',
959
      'mode' => 1, // Existing strings are kept, only new strings are added.
960
    ));
961

    
962
    // The import should have created 1 string.
963
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
964
    // Ensure string wasn't overwritten.
965
    $search = array(
966
      'string' => 'Montag',
967
      'language' => 'fr',
968
      'translation' => 'translated',
969
      'group' => 'all',
970
    );
971
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
972
    $this->assertText(t('No strings available.'), 'String not overwritten by imported string.');
973

    
974
    // This import should not have changed number of plural forms.
975
    $this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 2, 'Plural numbers untouched.');
976

    
977
    $this->importPoFile($this->getPoFileWithBrokenPlural(), array(
978
      'langcode' => 'fr',
979
      'mode' => 1, // Existing strings are kept, only new strings are added.
980
    ));
981

    
982
    // Attempt to import broken .po file as well to prove that this
983
    // will not overwrite the proper plural formula imported above.
984
    $this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 2, 'Broken plurals: plural numbers untouched.');
985

    
986
    $this->importPoFile($this->getPoFileWithMissingPlural(), array(
987
      'langcode' => 'fr',
988
      'mode' => 1, // Existing strings are kept, only new strings are added.
989
    ));
990

    
991
    // Attempt to import .po file which has no plurals and prove that this
992
    // will not overwrite the proper plural formula imported above.
993
    $this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 2, 'No plurals: plural numbers untouched.');
994

    
995

    
996
    // Try importing a .po file with overriding strings, and ensure existing
997
    // strings are overwritten.
998
    $this->importPoFile($this->getOverwritePoFile(), array(
999
      'langcode' => 'fr',
1000
      'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
1001
    ));
1002

    
1003
    // The import should have updated 2 strings.
1004
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 2, '%delete' => 0)), 'The translation file was successfully imported.');
1005
    // Ensure string was overwritten.
1006
    $search = array(
1007
      'string' => 'Montag',
1008
      'language' => 'fr',
1009
      'translation' => 'translated',
1010
      'group' => 'all',
1011
    );
1012
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
1013
    $this->assertNoText(t('No strings available.'), 'String overwritten by imported string.');
1014
    // This import should have changed number of plural forms.
1015
    $this->assert(db_query("SELECT plurals FROM {languages} WHERE language = 'fr'")->fetchField() == 3, 'Plural numbers changed.');
1016
  }
1017

    
1018
  /**
1019
   * Test automatic import of a module's translation files when a language is
1020
   * enabled.
1021
   */
1022
  function testAutomaticModuleTranslationImportLanguageEnable() {
1023
    // Code for the language - manually set to match the test translation file.
1024
    $langcode = 'xx';
1025
    // The English name for the language.
1026
    $name = $this->randomName(16);
1027
    // The native name for the language.
1028
    $native = $this->randomName(16);
1029
    // The domain prefix.
1030
    $prefix = $langcode;
1031

    
1032
    // Create a custom language.
1033
    $edit = array(
1034
      'langcode' => $langcode,
1035
      'name' => $name,
1036
      'native' => $native,
1037
      'prefix' => $prefix,
1038
      'direction' => '0',
1039
    );
1040
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1041

    
1042
    // Ensure the translation file was automatically imported when language was
1043
    // added.
1044
    $this->assertText(t('One translation file imported for the enabled modules.'), 'Language file automatically imported.');
1045

    
1046
    // Ensure strings were successfully imported.
1047
    $search = array(
1048
      'string' => 'lundi',
1049
      'language' => $langcode,
1050
      'translation' => 'translated',
1051
      'group' => 'all',
1052
    );
1053
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
1054
    $this->assertNoText(t('No strings available.'), 'String successfully imported.');
1055
  }
1056

    
1057
  /**
1058
   * Test msgctxt context support.
1059
   */
1060
  function testLanguageContext() {
1061
    // Try importing a .po file.
1062
    $this->importPoFile($this->getPoFileWithContext(), array(
1063
      'langcode' => 'hr',
1064
    ));
1065

    
1066
    $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', 'Long month name context is working.');
1067
    $this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', 'Default context is working.');
1068
  }
1069

    
1070
  /**
1071
   * Test empty msgstr at end of .po file see #611786.
1072
   */
1073
  function testEmptyMsgstr() {
1074
    $langcode = 'hu';
1075

    
1076
    // Try importing a .po file.
1077
    $this->importPoFile($this->getPoFileWithMsgstr(), array(
1078
      'langcode' => $langcode,
1079
    ));
1080

    
1081
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
1082
    $this->assertIdentical(t('Operations', array(), array('langcode' => $langcode)), 'Műveletek', 'String imported and translated.');
1083

    
1084
    // Try importing a .po file.
1085
    $this->importPoFile($this->getPoFileWithEmptyMsgstr(), array(
1086
      'langcode' => $langcode,
1087
      'mode' => 0,
1088
    ));
1089
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 0, '%update' => 0, '%delete' => 1)), 'The translation file was successfully imported.');
1090
    // This is the language indicator on the translation search screen for
1091
    // untranslated strings. Copied straight from locale.inc.
1092
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
1093
    $str = "Operations";
1094
    $search = array(
1095
      'string' => $str,
1096
      'language' => 'all',
1097
      'translation' => 'all',
1098
      'group' => 'all',
1099
    );
1100
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
1101
    // assertText() seems to remove the input field where $str always could be
1102
    // found, so this is not a false assert.
1103
    $this->assertText($str, 'Search found the string.');
1104
    $this->assertRaw($language_indicator, 'String is untranslated again.');
1105
  }
1106

    
1107
  /**
1108
   * Helper function: import a standalone .po file in a given language.
1109
   *
1110
   * @param $contents
1111
   *   Contents of the .po file to import.
1112
   * @param $options
1113
   *   Additional options to pass to the translation import form.
1114
   */
1115
  function importPoFile($contents, array $options = array()) {
1116
    $name = tempnam('temporary://', "po_") . '.po';
1117
    file_put_contents($name, $contents);
1118
    $options['files[file]'] = $name;
1119
    $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
1120
    drupal_unlink($name);
1121
  }
1122

    
1123
  /**
1124
   * Helper function that returns a proper .po file.
1125
   */
1126
  function getPoFile() {
1127
    return <<< EOF
1128
msgid ""
1129
msgstr ""
1130
"Project-Id-Version: Drupal 7\\n"
1131
"MIME-Version: 1.0\\n"
1132
"Content-Type: text/plain; charset=UTF-8\\n"
1133
"Content-Transfer-Encoding: 8bit\\n"
1134
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1135

    
1136
msgid "One sheep"
1137
msgid_plural "@count sheep"
1138
msgstr[0] "un mouton"
1139
msgstr[1] "@count moutons"
1140

    
1141
msgid "Monday"
1142
msgstr "lundi"
1143

    
1144
msgid "Tuesday"
1145
msgstr "mardi"
1146

    
1147
msgid "Wednesday"
1148
msgstr "mercredi"
1149

    
1150
msgid "Thursday"
1151
msgstr "jeudi"
1152

    
1153
msgid "Friday"
1154
msgstr "vendredi"
1155

    
1156
msgid "Saturday"
1157
msgstr "samedi"
1158

    
1159
msgid "Sunday"
1160
msgstr "dimanche"
1161
EOF;
1162
  }
1163

    
1164
  /**
1165
   * Helper function that returns a bad .po file.
1166
   */
1167
  function getBadPoFile() {
1168
    return <<< EOF
1169
msgid ""
1170
msgstr ""
1171
"Project-Id-Version: Drupal 7\\n"
1172
"MIME-Version: 1.0\\n"
1173
"Content-Type: text/plain; charset=UTF-8\\n"
1174
"Content-Transfer-Encoding: 8bit\\n"
1175
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1176

    
1177
msgid "Save configuration"
1178
msgstr "Enregistrer la configuration"
1179

    
1180
msgid "edit"
1181
msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
1182

    
1183
msgid "delete"
1184
msgstr "supprimer<script>alert('xss');</script>"
1185

    
1186
EOF;
1187
  }
1188

    
1189
  /**
1190
   * Helper function that returns a proper .po file, for testing overwriting
1191
   * existing translations.
1192
   */
1193
  function getOverwritePoFile() {
1194
    return <<< EOF
1195
msgid ""
1196
msgstr ""
1197
"Project-Id-Version: Drupal 7\\n"
1198
"MIME-Version: 1.0\\n"
1199
"Content-Type: text/plain; charset=UTF-8\\n"
1200
"Content-Transfer-Encoding: 8bit\\n"
1201
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
1202

    
1203
msgid "Monday"
1204
msgstr "Montag"
1205

    
1206
msgid "Day"
1207
msgstr "Jour"
1208
EOF;
1209
  }
1210

    
1211
  /**
1212
   * Helper function that returns a .po file with context.
1213
   */
1214
  function getPoFileWithContext() {
1215
    // Croatian (code hr) is one of the languages that have a different
1216
    // form for the full name and the abbreviated name for the month May.
1217
    return <<< EOF
1218
msgid ""
1219
msgstr ""
1220
"Project-Id-Version: Drupal 7\\n"
1221
"MIME-Version: 1.0\\n"
1222
"Content-Type: text/plain; charset=UTF-8\\n"
1223
"Content-Transfer-Encoding: 8bit\\n"
1224
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
1225

    
1226
msgctxt "Long month name"
1227
msgid "May"
1228
msgstr "Svibanj"
1229

    
1230
msgid "May"
1231
msgstr "Svi."
1232
EOF;
1233
  }
1234

    
1235
  /**
1236
   * Helper function that returns a .po file with an empty last item.
1237
   */
1238
  function getPoFileWithEmptyMsgstr() {
1239
    return <<< EOF
1240
msgid ""
1241
msgstr ""
1242
"Project-Id-Version: Drupal 7\\n"
1243
"MIME-Version: 1.0\\n"
1244
"Content-Type: text/plain; charset=UTF-8\\n"
1245
"Content-Transfer-Encoding: 8bit\\n"
1246
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1247

    
1248
msgid "Operations"
1249
msgstr ""
1250

    
1251
EOF;
1252
  }
1253
  /**
1254
   * Helper function that returns a .po file with an empty last item.
1255
   */
1256
  function getPoFileWithMsgstr() {
1257
    return <<< EOF
1258
msgid ""
1259
msgstr ""
1260
"Project-Id-Version: Drupal 7\\n"
1261
"MIME-Version: 1.0\\n"
1262
"Content-Type: text/plain; charset=UTF-8\\n"
1263
"Content-Transfer-Encoding: 8bit\\n"
1264
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1265

    
1266
msgid "Operations"
1267
msgstr "Műveletek"
1268

    
1269
msgid "Will not appear in Drupal core, so we can ensure the test passes"
1270
msgstr ""
1271

    
1272
EOF;
1273
  }
1274

    
1275

    
1276
  /**
1277
   * Returns a .po file with a missing plural formula.
1278
   */
1279
  function getPoFileWithMissingPlural() {
1280
    return <<< EOF
1281
msgid ""
1282
msgstr ""
1283
"Project-Id-Version: Drupal 7\\n"
1284
"MIME-Version: 1.0\\n"
1285
"Content-Type: text/plain; charset=UTF-8\\n"
1286
"Content-Transfer-Encoding: 8bit\\n"
1287

    
1288
msgid "Monday"
1289
msgstr "Ponedjeljak"
1290
EOF;
1291
  }
1292

    
1293
  /**
1294
   * Returns a .po file with a broken plural formula.
1295
   */
1296
  function getPoFileWithBrokenPlural() {
1297
    return <<< EOF
1298
msgid ""
1299
msgstr ""
1300
"Project-Id-Version: Drupal 7\\n"
1301
"MIME-Version: 1.0\\n"
1302
"Content-Type: text/plain; charset=UTF-8\\n"
1303
"Content-Transfer-Encoding: 8bit\\n"
1304
"Plural-Forms: broken, will not parse\\n"
1305

    
1306
msgid "Monday"
1307
msgstr "lundi"
1308
EOF;
1309
  }
1310

    
1311
}
1312

    
1313
/**
1314
 * Functional tests for the export of translation files.
1315
 */
1316
class LocaleExportFunctionalTest extends DrupalWebTestCase {
1317
  public static function getInfo() {
1318
    return array(
1319
      'name' => 'Translation export',
1320
      'description' => 'Tests the exportation of locale files.',
1321
      'group' => 'Locale',
1322
    );
1323
  }
1324

    
1325
  /**
1326
   * A user able to create languages and export translations.
1327
   */
1328
  protected $admin_user = NULL;
1329

    
1330
  function setUp() {
1331
    parent::setUp('locale', 'locale_test');
1332

    
1333
    $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
1334
    $this->drupalLogin($this->admin_user);
1335
  }
1336

    
1337
  /**
1338
   * Test exportation of translations.
1339
   */
1340
  function testExportTranslation() {
1341
    // First import some known translations.
1342
    // This will also automatically enable the 'fr' language.
1343
    $name = tempnam('temporary://', "po_") . '.po';
1344
    file_put_contents($name, $this->getPoFile());
1345
    $this->drupalPost('admin/config/regional/translate/import', array(
1346
      'langcode' => 'fr',
1347
      'files[file]' => $name,
1348
    ), t('Import'));
1349
    drupal_unlink($name);
1350

    
1351
    // Get the French translations.
1352
    $this->drupalPost('admin/config/regional/translate/export', array(
1353
      'langcode' => 'fr',
1354
    ), t('Export'));
1355

    
1356
    // Ensure we have a translation file.
1357
    $this->assertRaw('# French translation of Drupal', 'Exported French translation file.');
1358
    // Ensure our imported translations exist in the file.
1359
    $this->assertRaw('msgstr "lundi"', 'French translations present in exported file.');
1360
  }
1361

    
1362
  /**
1363
   * Test exportation of translation template file.
1364
   */
1365
  function testExportTranslationTemplateFile() {
1366
    // Get the translation template file.
1367
    // There are two 'Export' buttons on this page, but it somehow works.  It'd
1368
    // be better if we could use the submit button id like documented but that
1369
    // doesn't work.
1370
    $this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
1371
    // Ensure we have a translation file.
1372
    $this->assertRaw('# LANGUAGE translation of PROJECT', 'Exported translation template file.');
1373
  }
1374

    
1375
  /**
1376
   * Helper function that returns a proper .po file.
1377
   */
1378
  function getPoFile() {
1379
    return <<< EOF
1380
msgid ""
1381
msgstr ""
1382
"Project-Id-Version: Drupal 6\\n"
1383
"MIME-Version: 1.0\\n"
1384
"Content-Type: text/plain; charset=UTF-8\\n"
1385
"Content-Transfer-Encoding: 8bit\\n"
1386
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1387

    
1388
msgid "Monday"
1389
msgstr "lundi"
1390
EOF;
1391
  }
1392

    
1393
}
1394

    
1395
/**
1396
 * Tests for the st() function.
1397
 */
1398
class LocaleInstallTest extends DrupalWebTestCase {
1399
  public static function getInfo() {
1400
    return array(
1401
      'name' => 'String translation using st()',
1402
      'description' => 'Tests that st() works like t().',
1403
      'group' => 'Locale',
1404
    );
1405
  }
1406

    
1407
  function setUp() {
1408
    parent::setUp('locale');
1409

    
1410
    // st() lives in install.inc, so ensure that it is loaded for all tests.
1411
    require_once DRUPAL_ROOT . '/includes/install.inc';
1412
  }
1413

    
1414
  /**
1415
   * Verify that function signatures of t() and st() are equal.
1416
   */
1417
  function testFunctionSignatures() {
1418
    $reflector_t = new ReflectionFunction('t');
1419
    $reflector_st = new ReflectionFunction('st');
1420
    $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), 'Function signatures of t() and st() are equal.');
1421
  }
1422
}
1423

    
1424
/**
1425
 * Locale uninstall with English UI functional test.
1426
 */
1427
class LocaleUninstallFunctionalTest extends DrupalWebTestCase {
1428
  public static function getInfo() {
1429
    return array(
1430
      'name' => 'Locale uninstall (EN)',
1431
      'description' => 'Tests the uninstall process using the built-in UI language.',
1432
      'group' => 'Locale',
1433
    );
1434
  }
1435

    
1436
  /**
1437
   * The default language set for the UI before uninstall.
1438
   */
1439
  protected $language;
1440

    
1441
  function setUp() {
1442
    parent::setUp('locale');
1443
    $this->language = 'en';
1444
  }
1445

    
1446
  /**
1447
   * Check if the values of the Locale variables are correct after uninstall.
1448
   */
1449
  function testUninstallProcess() {
1450
    $locale_module = array('locale');
1451

    
1452
    // Add a new language and optionally set it as default.
1453
    require_once DRUPAL_ROOT . '/includes/locale.inc';
1454
    locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->language == 'fr');
1455

    
1456
    // Check the UI language.
1457
    drupal_language_initialize();
1458
    global $language;
1459
    $this->assertEqual($language->language, $this->language, format_string('Current language: %lang', array('%lang' => $language->language)));
1460

    
1461
    // Enable multilingual workflow option for articles.
1462
    variable_set('language_content_type_article', 1);
1463

    
1464
    // Change JavaScript translations directory.
1465
    variable_set('locale_js_directory', 'js_translations');
1466

    
1467
    // Build the JavaScript translation file for French.
1468
    $user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
1469
    $this->drupalLogin($user);
1470
    $this->drupalGet('admin/config/regional/translate/translate');
1471
    $string = db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE :location AND textgroup = :textgroup', array(
1472
      ':location' => '%.js%',
1473
      ':textgroup' => 'default',
1474
    ))->fetchObject();
1475
    $edit = array('translations[fr]' => 'french translation');
1476
    $this->drupalPost('admin/config/regional/translate/edit/' . $string->lid, $edit, t('Save translations'));
1477
    _locale_rebuild_js('fr');
1478
    $file = db_query('SELECT javascript FROM {languages} WHERE language = :language', array(':language' => 'fr'))->fetchObject();
1479
    $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/fr_' . $file->javascript . '.js';
1480
    $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file created: %file', array('%file' => $result ? $js_file : 'none')));
1481

    
1482
    // Disable string caching.
1483
    variable_set('locale_cache_strings', 0);
1484

    
1485
    // Change language negotiation options.
1486
    drupal_load('module', 'locale');
1487
    variable_set('language_types', drupal_language_types() + array('language_custom' => TRUE));
1488
    variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info());
1489
    variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info());
1490
    variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, locale_language_negotiation_info());
1491

    
1492
    // Change language providers settings.
1493
    variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX);
1494
    variable_set('locale_language_negotiation_session_param', TRUE);
1495

    
1496
    // Uninstall Locale.
1497
    module_disable($locale_module);
1498
    drupal_uninstall_modules($locale_module);
1499

    
1500
    // Visit the front page.
1501
    $this->drupalGet('');
1502

    
1503
    // Check the init language logic.
1504
    drupal_language_initialize();
1505
    $this->assertEqual($language->language, 'en', format_string('Language after uninstall: %lang', array('%lang' => $language->language)));
1506

    
1507
    // Check JavaScript files deletion.
1508
    $this->assertTrue($result = !file_exists($js_file), format_string('JavaScript file deleted: %file', array('%file' => $result ? $js_file : 'found')));
1509

    
1510
    // Check language count.
1511
    $language_count = variable_get('language_count', 1);
1512
    $this->assertEqual($language_count, 1, format_string('Language count: %count', array('%count' => $language_count)));
1513

    
1514
    // Check language negotiation.
1515
    require_once DRUPAL_ROOT . '/includes/language.inc';
1516
    $this->assertTrue(count(language_types()) == count(drupal_language_types()), 'Language types reset');
1517
    $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT;
1518
    $this->assertTrue($language_negotiation, format_string('Interface language negotiation: %setting', array('%setting' => $language_negotiation ? 'none' : 'set')));
1519
    $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT;
1520
    $this->assertTrue($language_negotiation, format_string('Content language negotiation: %setting', array('%setting' => $language_negotiation ? 'none' : 'set')));
1521
    $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT;
1522
    $this->assertTrue($language_negotiation, format_string('URL language negotiation: %setting', array('%setting' => $language_negotiation ? 'none' : 'set')));
1523

    
1524
    // Check language providers settings.
1525
    $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE), 'URL language provider indicator settings cleared.');
1526
    $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE), 'Visit language provider settings cleared.');
1527

    
1528
    // Check JavaScript parsed.
1529
    $javascript_parsed_count = count(variable_get('javascript_parsed', array()));
1530
    $this->assertEqual($javascript_parsed_count, 0, format_string('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count)));
1531

    
1532
    // Check multilingual workflow option for articles.
1533
    $multilingual = variable_get('language_content_type_article', 0);
1534
    $this->assertEqual($multilingual, 0, format_string('Multilingual workflow option: %status', array('%status' => $multilingual ? 'enabled': 'disabled')));
1535

    
1536
    // Check JavaScript translations directory.
1537
    $locale_js_directory = variable_get('locale_js_directory', 'languages');
1538
    $this->assertEqual($locale_js_directory, 'languages', format_string('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory)));
1539

    
1540
    // Check string caching.
1541
    $locale_cache_strings = variable_get('locale_cache_strings', 1);
1542
    $this->assertEqual($locale_cache_strings, 1, format_string('String caching: %status', array('%status' => $locale_cache_strings ? 'enabled': 'disabled')));
1543
  }
1544
}
1545

    
1546
/**
1547
 * Locale uninstall with French UI functional test.
1548
 *
1549
 * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new
1550
 * test of its own. Rather, it switches the default UI language in setUp and then
1551
 * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest)
1552
 * to test with this new language.
1553
 */
1554
class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest {
1555
  public static function getInfo() {
1556
    return array(
1557
      'name' => 'Locale uninstall (FR)',
1558
      'description' => 'Tests the uninstall process using French as interface language.',
1559
      'group' => 'Locale',
1560
    );
1561
  }
1562

    
1563
  function setUp() {
1564
    parent::setUp();
1565
    $this->language = 'fr';
1566
  }
1567
}
1568

    
1569
/**
1570
 * Functional tests for the language switching feature.
1571
 */
1572
class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase {
1573

    
1574
  public static function getInfo() {
1575
    return array(
1576
      'name' => 'Language switching',
1577
      'description' => 'Tests for the language switching feature.',
1578
      'group' => 'Locale',
1579
    );
1580
  }
1581

    
1582
  function setUp() {
1583
    parent::setUp('locale');
1584

    
1585
    // Create and login user.
1586
    $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages'));
1587
    $this->drupalLogin($admin_user);
1588
  }
1589

    
1590
  /**
1591
   * Functional tests for the language switcher block.
1592
   */
1593
  function testLanguageBlock() {
1594
    // Enable the language switching block.
1595
    $language_type = LANGUAGE_TYPE_INTERFACE;
1596
    $edit = array(
1597
      "blocks[locale_{$language_type}][region]" => 'sidebar_first',
1598
    );
1599
    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
1600

    
1601
    // Add language.
1602
    $edit = array(
1603
      'langcode' => 'fr',
1604
    );
1605
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1606

    
1607
    // Enable URL language detection and selection.
1608
    $edit = array('language[enabled][locale-url]' => '1');
1609
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
1610

    
1611
    // Assert that the language switching block is displayed on the frontpage.
1612
    $this->drupalGet('');
1613
    $this->assertText(t('Languages'), 'Language switcher block found.');
1614

    
1615
    // Assert that only the current language is marked as active.
1616
    list($language_switcher) = $this->xpath('//div[@id=:id]/div[@class="content"]', array(':id' => 'block-locale-' . $language_type));
1617
    $links = array(
1618
      'active' => array(),
1619
      'inactive' => array(),
1620
    );
1621
    $anchors = array(
1622
      'active' => array(),
1623
      'inactive' => array(),
1624
    );
1625
    foreach ($language_switcher->ul->li as $link) {
1626
      $classes = explode(" ", (string) $link['class']);
1627
      list($language) = array_intersect($classes, array('en', 'fr'));
1628
      if (in_array('active', $classes)) {
1629
        $links['active'][] = $language;
1630
      }
1631
      else {
1632
        $links['inactive'][] = $language;
1633
      }
1634
      $anchor_classes = explode(" ", (string) $link->a['class']);
1635
      if (in_array('active', $anchor_classes)) {
1636
        $anchors['active'][] = $language;
1637
      }
1638
      else {
1639
        $anchors['inactive'][] = $language;
1640
      }
1641
    }
1642
    $this->assertIdentical($links, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language list item is marked as active on the language switcher block.');
1643
    $this->assertIdentical($anchors, array('active' => array('en'), 'inactive' => array('fr')), 'Only the current language anchor is marked as active on the language switcher block.');
1644
  }
1645
}
1646

    
1647
/**
1648
 * Test browser language detection.
1649
 */
1650
class LocaleBrowserDetectionTest extends DrupalUnitTestCase {
1651

    
1652
  public static function getInfo() {
1653
    return array(
1654
      'name' => 'Browser language detection',
1655
      'description' => 'Tests for the browser language detection.',
1656
      'group' => 'Locale',
1657
    );
1658
  }
1659

    
1660
  /**
1661
   * Unit tests for the locale_language_from_browser() function.
1662
   */
1663
  function testLanguageFromBrowser() {
1664
    // Load the required functions.
1665
    require_once DRUPAL_ROOT . '/includes/locale.inc';
1666

    
1667
    $languages = array(
1668
      // In our test case, 'en' has priority over 'en-US'.
1669
      'en' => (object) array(
1670
        'language' => 'en',
1671
      ),
1672
      'en-US' => (object) array(
1673
        'language' => 'en-US',
1674
      ),
1675
      // But 'fr-CA' has priority over 'fr'.
1676
      'fr-CA' => (object) array(
1677
        'language' => 'fr-CA',
1678
      ),
1679
      'fr' => (object) array(
1680
        'language' => 'fr',
1681
      ),
1682
      // 'es-MX' is alone.
1683
      'es-MX' => (object) array(
1684
        'language' => 'es-MX',
1685
      ),
1686
      // 'pt' is alone.
1687
      'pt' => (object) array(
1688
        'language' => 'pt',
1689
      ),
1690
      // Language codes with more then one dash are actually valid.
1691
      // eh-oh-laa-laa is the official language code of the Teletubbies.
1692
      'eh-oh-laa-laa' => (object) array(
1693
        'language' => 'eh-oh-laa-laa',
1694
      ),
1695
    );
1696

    
1697
    $test_cases = array(
1698
      // Equal qvalue for each language, choose the site preferred one.
1699
      'en,en-US,fr-CA,fr,es-MX' => 'en',
1700
      'en-US,en,fr-CA,fr,es-MX' => 'en',
1701
      'fr,en' => 'en',
1702
      'en,fr' => 'en',
1703
      'en-US,fr' => 'en',
1704
      'fr,en-US' => 'en',
1705
      'fr,fr-CA' => 'fr-CA',
1706
      'fr-CA,fr' => 'fr-CA',
1707
      'fr' => 'fr-CA',
1708
      'fr;q=1' => 'fr-CA',
1709
      'fr,es-MX' => 'fr-CA',
1710
      'fr,es' => 'fr-CA',
1711
      'es,fr' => 'fr-CA',
1712
      'es-MX,de' => 'es-MX',
1713
      'de,es-MX' => 'es-MX',
1714

    
1715
      // Different cases and whitespace.
1716
      'en' => 'en',
1717
      'En' => 'en',
1718
      'EN' => 'en',
1719
      ' en' => 'en',
1720
      'en ' => 'en',
1721
      'en, fr' => 'en',
1722

    
1723
      // A less specific language from the browser matches a more specific one
1724
      // from the website, and the other way around for compatibility with
1725
      // some versions of Internet Explorer.
1726
      'es' => 'es-MX',
1727
      'es-MX' => 'es-MX',
1728
      'pt' => 'pt',
1729
      'pt-PT' => 'pt',
1730
      'pt-PT;q=0.5,pt-BR;q=1,en;q=0.7' => 'en',
1731
      'pt-PT;q=1,pt-BR;q=0.5,en;q=0.7' => 'en',
1732
      'pt-PT;q=0.4,pt-BR;q=0.1,en;q=0.7' => 'en',
1733
      'pt-PT;q=0.1,pt-BR;q=0.4,en;q=0.7' => 'en',
1734

    
1735
      // Language code with several dashes are valid. The less specific language
1736
      // from the browser matches the more specific one from the website.
1737
      'eh-oh-laa-laa' => 'eh-oh-laa-laa',
1738
      'eh-oh-laa' => 'eh-oh-laa-laa',
1739
      'eh-oh' => 'eh-oh-laa-laa',
1740
      'eh' => 'eh-oh-laa-laa',
1741

    
1742
      // Different qvalues.
1743
      'fr,en;q=0.5' => 'fr-CA',
1744
      'fr,en;q=0.5,fr-CA;q=0.25' => 'fr',
1745

    
1746
      // Silly wildcards are also valid.
1747
      '*,fr-CA;q=0.5' => 'en',
1748
      '*,en;q=0.25' => 'fr-CA',
1749
      'en,en-US;q=0.5,fr;q=0.25' => 'en',
1750
      'en-US,en;q=0.5,fr;q=0.25' => 'en-US',
1751

    
1752
      // Unresolvable cases.
1753
      '' => FALSE,
1754
      'de,pl' => FALSE,
1755
      'iecRswK4eh' => FALSE,
1756
      $this->randomName(10) => FALSE,
1757
    );
1758

    
1759
    foreach ($test_cases as $accept_language => $expected_result) {
1760
      $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $accept_language;
1761
      $result = locale_language_from_browser($languages);
1762
      $this->assertIdentical($result, $expected_result, format_string("Language selection '@accept-language' selects '@result', result = '@actual'", array('@accept-language' => $accept_language, '@result' => $expected_result, '@actual' => isset($result) ? $result : 'none')));
1763
    }
1764
  }
1765
}
1766

    
1767
/**
1768
 * Functional tests for a user's ability to change their default language.
1769
 */
1770
class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase {
1771
  public static function getInfo() {
1772
    return array(
1773
      'name' => 'User language settings',
1774
      'description' => "Tests user's ability to change their default language.",
1775
      'group' => 'Locale',
1776
    );
1777
  }
1778

    
1779
  function setUp() {
1780
    parent::setUp('locale');
1781
  }
1782

    
1783
  /**
1784
   * Test if user can change their default language.
1785
   */
1786
  function testUserLanguageConfiguration() {
1787
    global $base_url;
1788

    
1789
    // User to add and remove language.
1790
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
1791
    // User to change their default language.
1792
    $web_user = $this->drupalCreateUser();
1793

    
1794
    // Add custom language.
1795
    $this->drupalLogin($admin_user);
1796
    // Code for the language.
1797
    $langcode = 'xx';
1798
    // The English name for the language.
1799
    $name = $this->randomName(16);
1800
    // The native name for the language.
1801
    $native = $this->randomName(16);
1802
    // The domain prefix.
1803
    $prefix = 'xx';
1804
    $edit = array(
1805
      'langcode' => $langcode,
1806
      'name' => $name,
1807
      'native' => $native,
1808
      'prefix' => $prefix,
1809
      'direction' => '0',
1810
    );
1811
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1812

    
1813
    // Add custom language and disable it.
1814
    // Code for the language.
1815
    $langcode_disabled = 'xx-yy';
1816
    // The English name for the language. This will be translated.
1817
    $name_disabled = $this->randomName(16);
1818
    // The native name for the language.
1819
    $native_disabled = $this->randomName(16);
1820
    // The domain prefix.
1821
    $prefix_disabled = $langcode_disabled;
1822
    $edit = array(
1823
      'langcode' => $langcode_disabled,
1824
      'name' => $name_disabled,
1825
      'native' => $native_disabled,
1826
      'prefix' => $prefix_disabled,
1827
      'direction' => '0',
1828
    );
1829
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1830
    // Disable the language.
1831
    $edit = array(
1832
      'enabled[' . $langcode_disabled . ']' => FALSE,
1833
    );
1834
    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
1835
    $this->drupalLogout();
1836

    
1837
    // Login as normal user and edit account settings.
1838
    $this->drupalLogin($web_user);
1839
    $path = 'user/' . $web_user->uid . '/edit';
1840
    $this->drupalGet($path);
1841
    // Ensure language settings fieldset is available.
1842
    $this->assertText(t('Language settings'), 'Language settings available.');
1843
    // Ensure custom language is present.
1844
    $this->assertText($name, 'Language present on form.');
1845
    // Ensure disabled language isn't present.
1846
    $this->assertNoText($name_disabled, 'Disabled language not present on form.');
1847
    // Switch to our custom language.
1848
    $edit = array(
1849
      'language' => $langcode,
1850
    );
1851
    $this->drupalPost($path, $edit, t('Save'));
1852
    // Ensure form was submitted successfully.
1853
    $this->assertText(t('The changes have been saved.'), 'Changes were saved.');
1854
    // Check if language was changed.
1855
    $elements = $this->xpath('//input[@id=:id]', array(':id' => 'edit-language-' . $langcode));
1856
    $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), 'Default language successfully updated.');
1857

    
1858
    $this->drupalLogout();
1859
  }
1860
}
1861

    
1862
/**
1863
 * Functional test for language handling during user creation.
1864
 */
1865
class LocaleUserCreationTest extends DrupalWebTestCase {
1866

    
1867
  public static function getInfo() {
1868
    return array(
1869
      'name' => 'User creation',
1870
      'description' => 'Tests whether proper language is stored for new users and access to language selector.',
1871
      'group' => 'Locale',
1872
    );
1873
  }
1874

    
1875
  function setUp() {
1876
    parent::setUp('locale');
1877
    variable_set('user_register', USER_REGISTER_VISITORS);
1878
  }
1879

    
1880
  /**
1881
   * Functional test for language handling during user creation.
1882
   */
1883
  function testLocalUserCreation() {
1884
    // User to add and remove language and create new users.
1885
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer users'));
1886
    $this->drupalLogin($admin_user);
1887

    
1888
    // Add predefined language.
1889
    $langcode = 'fr';
1890
    $edit = array(
1891
      'langcode' => 'fr',
1892
    );
1893
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1894
    $this->assertText($langcode, 'Language added successfully.');
1895
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
1896

    
1897
    // Set language negotiation.
1898
    $edit = array(
1899
      'language[enabled][locale-url]' => TRUE,
1900
    );
1901
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
1902
    $this->assertText(t('Language negotiation configuration saved.'), 'Set language negotiation.');
1903

    
1904
    // Check if the language selector is available on admin/people/create and
1905
    // set to the currently active language.
1906
    $this->drupalGet($langcode . '/admin/people/create');
1907
    $this->assertFieldChecked("edit-language-$langcode", 'Global language set in the language selector.');
1908

    
1909
    // Create a user with the admin/people/create form and check if the correct
1910
    // language is set.
1911
    $username = $this->randomName(10);
1912
    $edit = array(
1913
      'name' => $username,
1914
      'mail' => $this->randomName(4) . '@example.com',
1915
      'pass[pass1]' => $username,
1916
      'pass[pass2]' => $username,
1917
    );
1918

    
1919
    $this->drupalPost($langcode . '/admin/people/create', $edit, t('Create new account'));
1920

    
1921
    $user = user_load_by_name($username);
1922
    $this->assertEqual($user->language, $langcode, 'New user has correct language set.');
1923

    
1924
    // Register a new user and check if the language selector is hidden.
1925
    $this->drupalLogout();
1926

    
1927
    $this->drupalGet($langcode . '/user/register');
1928
    $this->assertNoFieldByName('language[fr]', 'Language selector is not accessible.');
1929

    
1930
    $username = $this->randomName(10);
1931
    $edit = array(
1932
      'name' => $username,
1933
      'mail' => $this->randomName(4) . '@example.com',
1934
    );
1935

    
1936
    $this->drupalPost($langcode . '/user/register', $edit, t('Create new account'));
1937

    
1938
    $user = user_load_by_name($username);
1939
    $this->assertEqual($user->language, $langcode, 'New user has correct language set.');
1940

    
1941
    // Test if the admin can use the language selector and if the
1942
    // correct language is was saved.
1943
    $user_edit = $langcode . '/user/' . $user->uid . '/edit';
1944

    
1945
    $this->drupalLogin($admin_user);
1946
    $this->drupalGet($user_edit);
1947
    $this->assertFieldChecked("edit-language-$langcode", 'Language selector is accessible and correct language is selected.');
1948

    
1949
    // Set pass_raw so we can login the new user.
1950
    $user->pass_raw = $this->randomName(10);
1951
    $edit = array(
1952
      'pass[pass1]' => $user->pass_raw,
1953
      'pass[pass2]' => $user->pass_raw,
1954
    );
1955

    
1956
    $this->drupalPost($user_edit, $edit, t('Save'));
1957

    
1958
    $this->drupalLogin($user);
1959
    $this->drupalGet($user_edit);
1960
    $this->assertFieldChecked("edit-language-$langcode", 'Language selector is accessible and correct language is selected.');
1961
  }
1962
}
1963

    
1964
/**
1965
 * Functional tests for configuring a different path alias per language.
1966
 */
1967
class LocalePathFunctionalTest extends DrupalWebTestCase {
1968
  public static function getInfo() {
1969
    return array(
1970
      'name' => 'Path language settings',
1971
      'description' => 'Checks you can configure a language for individual URL aliases.',
1972
      'group' => 'Locale',
1973
    );
1974
  }
1975

    
1976
  function setUp() {
1977
    parent::setUp('locale', 'path');
1978
  }
1979

    
1980
  /**
1981
   * Test if a language can be associated with a path alias.
1982
   */
1983
  function testPathLanguageConfiguration() {
1984
    global $base_url;
1985

    
1986
    // User to add and remove language.
1987
    $admin_user = $this->drupalCreateUser(array('administer languages', 'create page content', 'administer url aliases', 'create url aliases', 'access administration pages'));
1988

    
1989
    // Add custom language.
1990
    $this->drupalLogin($admin_user);
1991
    // Code for the language.
1992
    $langcode = 'xx';
1993
    // The English name for the language.
1994
    $name = $this->randomName(16);
1995
    // The native name for the language.
1996
    $native = $this->randomName(16);
1997
    // The domain prefix.
1998
    $prefix = $langcode;
1999
    $edit = array(
2000
      'langcode' => $langcode,
2001
      'name' => $name,
2002
      'native' => $native,
2003
      'prefix' => $prefix,
2004
      'direction' => '0',
2005
    );
2006
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
2007

    
2008
    // Check that the "xx" front page is not available when path prefixes are
2009
    // not enabled yet.
2010
    $this->drupalPost('admin/config/regional/language/configure', array(), t('Save settings'));
2011
    $this->drupalGet($prefix);
2012
    $this->assertResponse(404, 'The "xx" front page is not available yet.');
2013

    
2014
    // Enable URL language detection and selection.
2015
    $edit = array('language[enabled][locale-url]' => 1);
2016
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2017

    
2018
    // Create a node.
2019
    $node = $this->drupalCreateNode(array('type' => 'page'));
2020

    
2021
    // Create a path alias in default language (English).
2022
    $path = 'admin/config/search/path/add';
2023
    $english_path = $this->randomName(8);
2024
    $edit = array(
2025
      'source'   => 'node/' . $node->nid,
2026
      'alias'    => $english_path,
2027
      'language' => 'en',
2028
    );
2029
    $this->drupalPost($path, $edit, t('Save'));
2030

    
2031
    // Create a path alias in new custom language.
2032
    $custom_language_path = $this->randomName(8);
2033
    $edit = array(
2034
      'source'   => 'node/' . $node->nid,
2035
      'alias'    => $custom_language_path,
2036
      'language' => $langcode,
2037
    );
2038
    $this->drupalPost($path, $edit, t('Save'));
2039

    
2040
    // Confirm English language path alias works.
2041
    $this->drupalGet($english_path);
2042
    $this->assertText($node->title, 'English alias works.');
2043

    
2044
    // Confirm custom language path alias works.
2045
    $this->drupalGet($prefix . '/' . $custom_language_path);
2046
    $this->assertText($node->title, 'Custom language alias works.');
2047

    
2048
    // Create a custom path.
2049
    $custom_path = $this->randomName(8);
2050

    
2051
    // Check priority of language for alias by source path.
2052
    $edit = array(
2053
      'source'   => 'node/' . $node->nid,
2054
      'alias'    => $custom_path,
2055
      'language' => LANGUAGE_NONE,
2056
    );
2057
    path_save($edit);
2058
    $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en');
2059
    $this->assertEqual($english_path, $lookup_path, 'English language alias has priority.');
2060
    // Same check for language 'xx'.
2061
    $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix);
2062
    $this->assertEqual($custom_language_path, $lookup_path, 'Custom language alias has priority.');
2063
    path_delete($edit);
2064

    
2065
    // Create language nodes to check priority of aliases.
2066
    $first_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
2067
    $second_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
2068

    
2069
    // Assign a custom path alias to the first node with the English language.
2070
    $edit = array(
2071
      'source'   => 'node/' . $first_node->nid,
2072
      'alias'    => $custom_path,
2073
      'language' => 'en',
2074
    );
2075
    path_save($edit);
2076

    
2077
    // Assign a custom path alias to second node with LANGUAGE_NONE.
2078
    $edit = array(
2079
      'source'   => 'node/' . $second_node->nid,
2080
      'alias'    => $custom_path,
2081
      'language' => LANGUAGE_NONE,
2082
    );
2083
    path_save($edit);
2084

    
2085
    // Test that both node titles link to our path alias.
2086
    $this->drupalGet('<front>');
2087
    $custom_path_url = base_path() . (variable_get('clean_url', 0) ? $custom_path : '?q=' . $custom_path);
2088
    $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title));
2089
    $this->assertTrue(!empty($elements), 'First node links to the path alias.');
2090
    $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title));
2091
    $this->assertTrue(!empty($elements), 'Second node links to the path alias.');
2092

    
2093
    // Confirm that the custom path leads to the first node.
2094
    $this->drupalGet($custom_path);
2095
    $this->assertText($first_node->title, 'Custom alias returns first node.');
2096

    
2097
    // Confirm that the custom path with prefix leads to the second node.
2098
    $this->drupalGet($prefix . '/' . $custom_path);
2099
    $this->assertText($second_node->title, 'Custom alias with prefix returns second node.');
2100
  }
2101
}
2102

    
2103
/**
2104
 * Functional tests for multilingual support on nodes.
2105
 */
2106
class LocaleContentFunctionalTest extends DrupalWebTestCase {
2107
  public static function getInfo() {
2108
    return array(
2109
      'name' => 'Content language settings',
2110
      'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.',
2111
      'group' => 'Locale',
2112
    );
2113
  }
2114

    
2115
  function setUp() {
2116
    parent::setUp('locale');
2117
  }
2118

    
2119
  /**
2120
   * Verifies that machine name fields are always LTR.
2121
   */
2122
  function testMachineNameLTR() {
2123
    // User to add and remove language.
2124
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
2125

    
2126
    // Log in as admin.
2127
    $this->drupalLogin($admin_user);
2128

    
2129
    // Verify that the machine name field is LTR for a new content type.
2130
    $this->drupalGet('admin/structure/types/add');
2131
    $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when no additional language is configured.');
2132

    
2133
    // Install the Arabic language (which is RTL) and configure as the default.
2134
    $edit = array();
2135
    $edit['langcode'] = 'ar';
2136
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2137

    
2138
    $edit = array();
2139
    $edit['site_default'] = 'ar';
2140
    $this->drupalPost(NULL, $edit, t('Save configuration'));
2141

    
2142
    // Verify that the machine name field is still LTR for a new content type.
2143
    $this->drupalGet('admin/structure/types/add');
2144
    $this->assertFieldByXpath('//input[@name="type" and @dir="ltr"]', NULL, 'The machine name field is LTR when the default language is RTL.');
2145
  }
2146

    
2147
  /**
2148
   * Test if a content type can be set to multilingual and language setting is
2149
   * present on node add and edit forms.
2150
   */
2151
  function testContentTypeLanguageConfiguration() {
2152
    global $base_url;
2153

    
2154
    // User to add and remove language.
2155
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
2156
    // User to create a node.
2157
    $web_user = $this->drupalCreateUser(array('create article content', 'create page content', 'edit any page content'));
2158

    
2159
    // Add custom language.
2160
    $this->drupalLogin($admin_user);
2161
    // Code for the language.
2162
    $langcode = 'xx';
2163
    // The English name for the language.
2164
    $name = $this->randomName(16);
2165
    // The native name for the language.
2166
    $native = $this->randomName(16);
2167
    // The domain prefix.
2168
    $prefix = $langcode;
2169
    $edit = array(
2170
      'langcode' => $langcode,
2171
      'name' => $name,
2172
      'native' => $native,
2173
      'prefix' => $prefix,
2174
      'direction' => '0',
2175
    );
2176
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
2177

    
2178
    // Add disabled custom language.
2179
    // Code for the language.
2180
    $langcode_disabled = 'xx-yy';
2181
    // The English name for the language.
2182
    $name_disabled = $this->randomName(16);
2183
    // The native name for the language.
2184
    $native_disabled = $this->randomName(16);
2185
    // The domain prefix.
2186
    $prefix_disabled = $langcode_disabled;
2187
    $edit = array(
2188
      'langcode' => $langcode_disabled,
2189
      'name' => $name_disabled,
2190
      'native' => $native_disabled,
2191
      'prefix' => $prefix_disabled,
2192
      'direction' => '0',
2193
    );
2194
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
2195
    // Disable second custom language.
2196
    $path = 'admin/config/regional/language';
2197
    $edit = array(
2198
      'enabled[' . $langcode_disabled . ']' => FALSE,
2199
    );
2200
    $this->drupalPost($path, $edit, t('Save configuration'));
2201

    
2202
    // Set "Basic page" content type to use multilingual support.
2203
    $this->drupalGet('admin/structure/types/manage/page');
2204
    $this->assertText(t('Multilingual support'), 'Multilingual support fieldset present on content type configuration form.');
2205
    $edit = array(
2206
      'language_content_type' => 1,
2207
    );
2208
    $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
2209
    $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.');
2210
    $this->drupalLogout();
2211

    
2212
    // Verify language selection is not present on add article form.
2213
    $this->drupalLogin($web_user);
2214
    $this->drupalGet('node/add/article');
2215
    // Verify language select list is not present.
2216
    $this->assertNoFieldByName('language', NULL, 'Language select not present on add article form.');
2217

    
2218
    // Verify language selection appears on add "Basic page" form.
2219
    $this->drupalGet('node/add/page');
2220
    // Verify language select list is present.
2221
    $this->assertFieldByName('language', NULL, 'Language select present on add Basic page form.');
2222
    // Ensure enabled language appears.
2223
    $this->assertText($name, 'Enabled language present.');
2224
    // Ensure disabled language doesn't appear.
2225
    $this->assertNoText($name_disabled, 'Disabled language not present.');
2226

    
2227
    // Create "Basic page" content.
2228
    $node_title = $this->randomName();
2229
    $node_body =  $this->randomName();
2230
    $edit = array(
2231
      'type' => 'page',
2232
      'title' => $node_title,
2233
      'body' => array($langcode => array(array('value' => $node_body))),
2234
      'language' => $langcode,
2235
    );
2236
    $node = $this->drupalCreateNode($edit);
2237
    // Edit the content and ensure correct language is selected.
2238
    $path = 'node/' . $node->nid . '/edit';
2239
    $this->drupalGet($path);
2240
    $this->assertRaw('<option value="' . $langcode . '" selected="selected">' .  $name . '</option>', 'Correct language selected.');
2241
    // Ensure we can change the node language.
2242
    $edit = array(
2243
      'language' => 'en',
2244
    );
2245
    $this->drupalPost($path, $edit, t('Save'));
2246
    $this->assertRaw(t('%title has been updated.', array('%title' => $node_title)), 'Basic page content updated.');
2247

    
2248
    $this->drupalLogout();
2249
  }
2250

    
2251
  /**
2252
   * Verifies that nodes may be created with different languages.
2253
   */
2254
  function testNodeCreationWithLanguage() {
2255
    // Create an admin user and log them in.
2256
    $perms = array(
2257
      // Standard node permissions.
2258
      'create page content',
2259
      'administer content types',
2260
      'administer nodes',
2261
      'bypass node access',
2262
      // Locale.
2263
      'administer languages',
2264
    );
2265
    $web_user = $this->drupalCreateUser($perms);
2266
    $this->drupalLogin($web_user);
2267

    
2268
    // Create some test nodes using different langcodes.
2269
    foreach (array(LANGUAGE_NONE, 'en', 'fr') as $langcode) {
2270
      $node_args = array(
2271
        'type' => 'page',
2272
        'promote' => 1,
2273
        'language' => $langcode,
2274
      );
2275
      $node = $this->drupalCreateNode($node_args);
2276
      $node_reloaded = node_load($node->nid, NULL, TRUE);
2277
      $this->assertEqual($node_reloaded->language, $langcode, format_string('The language code of the node was successfully set to @langcode.', array('@langcode' => $langcode)));
2278
    }
2279
  }
2280

    
2281
}
2282

    
2283
/**
2284
 * Test UI language negotiation
2285
 * 1. URL (PATH) > DEFAULT
2286
 *    UI Language base on URL prefix, browser language preference has no
2287
 *    influence:
2288
 *      admin/config
2289
 *        UI in site default language
2290
 *      zh-hans/admin/config
2291
 *        UI in Chinese
2292
 *      blah-blah/admin/config
2293
 *        404
2294
 * 2. URL (PATH) > BROWSER > DEFAULT
2295
 *        admin/config
2296
 *          UI in user's browser language preference if the site has that
2297
 *          language enabled, if not, the default language
2298
 *        zh-hans/admin/config
2299
 *          UI in Chinese
2300
 *        blah-blah/admin/config
2301
 *          404
2302
 * 3. URL (DOMAIN) > DEFAULT
2303
 *        http://example.com/admin/config
2304
 *          UI language in site default
2305
 *        http://example.cn/admin/config
2306
 *          UI language in Chinese
2307
 */
2308
class LocaleUILanguageNegotiationTest extends DrupalWebTestCase {
2309
  public static function getInfo() {
2310
    return array(
2311
      'name' => 'UI language negotiation',
2312
      'description' => 'Test UI language switching by URL path prefix and domain.',
2313
      'group' => 'Locale',
2314
    );
2315
  }
2316

    
2317
  function setUp() {
2318
    parent::setUp('locale', 'locale_test');
2319
    require_once DRUPAL_ROOT . '/includes/language.inc';
2320
    drupal_load('module', 'locale');
2321
    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'administer blocks'));
2322
    $this->drupalLogin($admin_user);
2323
  }
2324

    
2325
  /**
2326
   * Tests for language switching by URL path.
2327
   */
2328
  function testUILanguageNegotiation() {
2329
    // A few languages to switch to.
2330
    // This one is unknown, should get the default lang version.
2331
    $language_unknown = 'blah-blah';
2332
    // For testing browser lang preference.
2333
    $language_browser_fallback = 'vi';
2334
    // For testing path prefix.
2335
    $language = 'zh-hans';
2336
    // For setting browser language preference to 'vi'.
2337
    $http_header_browser_fallback = array("Accept-Language: $language_browser_fallback;q=1");
2338
    // For setting browser language preference to some unknown.
2339
    $http_header_blah = array("Accept-Language: blah;q=1");
2340

    
2341
    // This domain should switch the UI to Chinese.
2342
    $language_domain = 'example.cn';
2343

    
2344
    // Setup the site languages by installing two languages.
2345
    require_once DRUPAL_ROOT . '/includes/locale.inc';
2346
    locale_add_language($language_browser_fallback);
2347
    locale_add_language($language);
2348

    
2349
    // We will look for this string in the admin/config screen to see if the
2350
    // corresponding translated string is shown.
2351
    $default_string = 'Configure languages for content and the user interface';
2352

    
2353
    // Set the default language in order for the translated string to be registered
2354
    // into database when seen by t(). Without doing this, our target string
2355
    // is for some reason not found when doing translate search. This might
2356
    // be some bug.
2357
    drupal_static_reset('language_list');
2358
    $languages = language_list('enabled');
2359
    variable_set('language_default', $languages[1]['vi']);
2360
    // First visit this page to make sure our target string is searchable.
2361
    $this->drupalGet('admin/config');
2362
    // Now the t()'ed string is in db so switch the language back to default.
2363
    variable_del('language_default');
2364

    
2365
    // Translate the string.
2366
    $language_browser_fallback_string = "In $language_browser_fallback In $language_browser_fallback In $language_browser_fallback";
2367
    $language_string = "In $language In $language In $language";
2368
    // Do a translate search of our target string.
2369
    $edit = array( 'string' => $default_string);
2370
    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter'));
2371
    // Should find the string and now click edit to post translated string.
2372
    $this->clickLink('edit');
2373
    $edit = array(
2374
      "translations[$language_browser_fallback]" => $language_browser_fallback_string,
2375
      "translations[$language]" => $language_string,
2376
    );
2377
    $this->drupalPost(NULL, $edit, t('Save translations'));
2378

    
2379
    // Configure URL language rewrite.
2380
    variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE);
2381

    
2382
    $tests = array(
2383
      // Default, browser preference should have no influence.
2384
      array(
2385
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2386
        'path' => 'admin/config',
2387
        'expect' => $default_string,
2388
        'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2389
        'http_header' => $http_header_browser_fallback,
2390
        'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.',
2391
      ),
2392
      // Language prefix.
2393
      array(
2394
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2395
        'path' => "$language/admin/config",
2396
        'expect' => $language_string,
2397
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_URL,
2398
        'http_header' => $http_header_browser_fallback,
2399
        'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
2400
      ),
2401
      // Default, go by browser preference.
2402
      array(
2403
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER),
2404
        'path' => 'admin/config',
2405
        'expect' => $language_browser_fallback_string,
2406
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_BROWSER,
2407
        'http_header' => $http_header_browser_fallback,
2408
        'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
2409
      ),
2410
      // Prefix, switch to the language.
2411
      array(
2412
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER),
2413
        'path' => "$language/admin/config",
2414
        'expect' => $language_string,
2415
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_URL,
2416
        'http_header' => $http_header_browser_fallback,
2417
        'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix',
2418
      ),
2419
      // Default, browser language preference is not one of site's lang.
2420
      array(
2421
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT),
2422
        'path' => 'admin/config',
2423
        'expect' => $default_string,
2424
        'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2425
        'http_header' => $http_header_blah,
2426
        'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
2427
      ),
2428
    );
2429

    
2430
    foreach ($tests as $test) {
2431
      $this->runTest($test);
2432
    }
2433

    
2434
    // Unknown language prefix should return 404.
2435
    variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info());
2436
    $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback);
2437
    $this->assertResponse(404, "Unknown language path prefix should return 404");
2438

    
2439
    // Setup for domain negotiation, first configure the language to have domain
2440
    // URL. We use HTTPS and a port to make sure that only the domain name is used.
2441
    $edit = array('prefix' => '', 'domain' => "https://$language_domain:99");
2442
    $this->drupalPost("admin/config/regional/language/edit/$language", $edit, t('Save language'));
2443
    // Set the site to use domain language negotiation.
2444

    
2445
    $tests = array(
2446
      // Default domain, browser preference should have no influence.
2447
      array(
2448
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2449
        'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN,
2450
        'path' => 'admin/config',
2451
        'expect' => $default_string,
2452
        'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2453
        'http_header' => $http_header_browser_fallback,
2454
        'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language',
2455
      ),
2456
      // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in
2457
      // locale_test.module hook_boot() to simulate this.
2458
      array(
2459
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2460
        'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN,
2461
        'locale_test_domain' => $language_domain . ':88',
2462
        'path' => 'admin/config',
2463
        'expect' => $language_string,
2464
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_URL,
2465
        'http_header' => $http_header_browser_fallback,
2466
        'message' => 'URL (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese',
2467
      ),
2468
    );
2469

    
2470
    foreach ($tests as $test) {
2471
      $this->runTest($test);
2472
    }
2473
  }
2474

    
2475
  private function runTest($test) {
2476
    if (!empty($test['language_negotiation'])) {
2477
      $negotiation = array_flip($test['language_negotiation']);
2478
      language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation);
2479
    }
2480
    if (!empty($test['locale_language_negotiation_url_part'])) {
2481
      variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']);
2482
    }
2483
    if (!empty($test['locale_test_domain'])) {
2484
      variable_set('locale_test_domain', $test['locale_test_domain']);
2485
    }
2486
    $this->drupalGet($test['path'], array(), $test['http_header']);
2487
    $this->assertText($test['expect'], $test['message']);
2488
    $this->assertText(t('Language negotiation provider: @name', array('@name' => $test['expected_provider'])));
2489
  }
2490

    
2491
  /**
2492
   * Test URL language detection when the requested URL has no language.
2493
   */
2494
  function testUrlLanguageFallback() {
2495
    // Add the Italian language.
2496
    $language_browser_fallback = 'it';
2497
    locale_add_language($language_browser_fallback);
2498
    $languages = language_list();
2499

    
2500
    // Enable the path prefix for the default language: this way any unprefixed
2501
    // URL must have a valid fallback value.
2502
    $edit = array('prefix' => 'en');
2503
    $this->drupalPost('admin/config/regional/language/edit/en', $edit, t('Save language'));
2504

    
2505
    // Enable browser and URL language detection.
2506
    $edit = array(
2507
      'language[enabled][locale-browser]' => TRUE,
2508
      'language[enabled][locale-url]' => TRUE,
2509
      'language[weight][locale-browser]' => -8,
2510
      'language[weight][locale-url]' => -10,
2511
    );
2512
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2513
    $this->drupalGet('admin/config/regional/language/configure');
2514

    
2515
    // Enable the language switcher block.
2516
    $edit = array('blocks[locale_language][region]' => 'sidebar_first');
2517
    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
2518

    
2519
    // Access the front page without specifying any valid URL language prefix
2520
    // and having as browser language preference a non-default language.
2521
    $http_header = array("Accept-Language: $language_browser_fallback;q=1");
2522
    $this->drupalGet('', array(), $http_header);
2523

    
2524
    // Check that the language switcher active link matches the given browser
2525
    // language.
2526
    $args = array(':url' => base_path() . (!empty($GLOBALS['conf']['clean_url']) ? $language_browser_fallback : "?q=$language_browser_fallback"));
2527
    $fields = $this->xpath('//div[@id="block-locale-language"]//a[@class="language-link active" and @href=:url]', $args);
2528
    $this->assertTrue($fields[0] == $languages[$language_browser_fallback]->native, 'The browser language is the URL active language');
2529

    
2530
    // Check that URLs are rewritten using the given browser language.
2531
    $fields = $this->xpath('//div[@id="site-name"]//a[@rel="home" and @href=:url]//span', $args);
2532
    $this->assertTrue($fields[0] == 'Drupal', 'URLs are rewritten using the browser language.');
2533
  }
2534

    
2535
  /**
2536
   * Tests url() when separate domains are used for multiple languages.
2537
   */
2538
  function testLanguageDomain() {
2539
    // Add the Italian language, without protocol.
2540
    $langcode = 'it';
2541
    locale_add_language($langcode, 'Italian', 'Italian', LANGUAGE_LTR, 'it.example.com', '', TRUE, FALSE);
2542

    
2543
    // Add the French language, with protocol.
2544
    $langcode = 'fr';
2545
    locale_add_language($langcode, 'French', 'French', LANGUAGE_LTR, 'http://fr.example.com', '', TRUE, FALSE);
2546

    
2547
    // Enable language URL detection.
2548
    $negotiation = array_flip(array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT));
2549
    language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation);
2550

    
2551
    variable_set('locale_language_negotiation_url_part', 1);
2552

    
2553
    global $is_https;
2554
    $languages = language_list();
2555

    
2556
    foreach (array('it', 'fr') as $langcode) {
2557
      // Build the link we're going to test based on the clean URL setting.
2558
      $link = (!empty($GLOBALS['conf']['clean_url'])) ? $langcode . '.example.com/admin' : $langcode . '.example.com/?q=admin';
2559

    
2560
      // Test URL in another language.
2561
      // Base path gives problems on the testbot, so $correct_link is hard-coded.
2562
      // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
2563
      $url = url('admin', array('language' => $languages[$langcode]));
2564
      $url_scheme = ($is_https) ? 'https://' : 'http://';
2565
      $correct_link = $url_scheme . $link;
2566
      $this->assertTrue($url == $correct_link, format_string('The url() function returns the right url (@url) in accordance with the chosen language', array('@url' => $url . " == " . $correct_link)));
2567

    
2568
      // Test HTTPS via options.
2569
      variable_set('https', TRUE);
2570
      $url = url('admin', array('https' => TRUE, 'language' => $languages[$langcode]));
2571
      $correct_link = 'https://' . $link;
2572
      $this->assertTrue($url == $correct_link, format_string('The url() function returns the right https url (via options) (@url) in accordance with the chosen language', array('@url' => $url . " == " . $correct_link)));
2573
      variable_set('https', FALSE);
2574

    
2575
      // Test HTTPS via current URL scheme.
2576
      $temp_https = $is_https;
2577
      $is_https = TRUE;
2578
      $url = url('admin', array('language' => $languages[$langcode]));
2579
      $correct_link = 'https://' . $link;
2580
      $this->assertTrue($url == $correct_link, format_string('The url() function returns the right url (via current url scheme) (@url) in accordance with the chosen language', array('@url' => $url . " == " . $correct_link)));
2581
      $is_https = $temp_https;
2582
    }
2583
  }
2584
}
2585

    
2586
/**
2587
 * Test that URL rewriting works as expected.
2588
 */
2589
class LocaleUrlRewritingTest extends DrupalWebTestCase {
2590
  public static function getInfo() {
2591
    return array(
2592
      'name' => 'URL rewriting',
2593
      'description' => 'Test that URL rewriting works as expected.',
2594
      'group' => 'Locale',
2595
    );
2596
  }
2597

    
2598
  function setUp() {
2599
    parent::setUp('locale');
2600

    
2601
    // Create and login user.
2602
    $this->web_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
2603
    $this->drupalLogin($this->web_user);
2604

    
2605
    // Install French language.
2606
    $edit = array();
2607
    $edit['langcode'] = 'fr';
2608
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2609

    
2610
    // Install Italian language.
2611
    $edit = array();
2612
    $edit['langcode'] = 'it';
2613
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2614

    
2615
    // Disable Italian language.
2616
    $edit = array('enabled[it]' => FALSE);
2617
    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
2618

    
2619
    // Enable URL language detection and selection.
2620
    $edit = array('language[enabled][locale-url]' => 1);
2621
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2622

    
2623
    // Reset static caching.
2624
    drupal_static_reset('language_list');
2625
    drupal_static_reset('locale_url_outbound_alter');
2626
    drupal_static_reset('locale_language_url_rewrite_url');
2627
  }
2628

    
2629
  /**
2630
   * Check that disabled or non-installed languages are not considered.
2631
   */
2632
  function testUrlRewritingEdgeCases() {
2633
    // Check URL rewriting with a disabled language.
2634
    $languages = language_list();
2635
    $this->checkUrl($languages['it'], 'Path language is ignored if language is disabled.', 'URL language negotiation does not work with disabled languages');
2636

    
2637
    // Check URL rewriting with a non-installed language.
2638
    $non_existing = language_default();
2639
    $non_existing->language = $this->randomName();
2640
    $non_existing->prefix = $this->randomName();
2641
    $this->checkUrl($non_existing, 'Path language is ignored if language is not installed.', 'URL language negotiation does not work with non-installed languages');
2642
  }
2643

    
2644
  /**
2645
   * Check URL rewriting for the given language.
2646
   *
2647
   * The test is performed with a fixed URL (the default front page) to simply
2648
   * check that language prefixes are not added to it and that the prefixed URL
2649
   * is actually not working.
2650
   *
2651
   * @param string $language
2652
   *   The language prefix, e.g. 'es'.
2653
   * @param string $message1
2654
   *   Message to display in assertion that language prefixes are not added.
2655
   * @param string $message2
2656
   *   The message to display confirming prefixed URL is not working.
2657
   */
2658
  private function checkUrl($language, $message1, $message2) {
2659
    $options = array('language' => $language);
2660
    $base_path = trim(base_path(), '/');
2661
    $rewritten_path = trim(str_replace(array('?q=', $base_path), '', url('node', $options)), '/');
2662
    $segments = explode('/', $rewritten_path, 2);
2663
    $prefix = $segments[0];
2664
    $path = isset($segments[1]) ? $segments[1] : $prefix;
2665
    // If the rewritten URL has not a language prefix we pick the right one from
2666
    // the language object so we can always check the prefixed URL.
2667
    if ($this->assertNotEqual($language->prefix, $prefix, $message1)) {
2668
      $prefix = $language->prefix;
2669
    }
2670
    $this->drupalGet("$prefix/$path");
2671
    $this->assertResponse(404, $message2);
2672
  }
2673

    
2674
  /**
2675
   * Check URL rewriting when using a domain name and a non-standard port.
2676
   */
2677
  function testDomainNameNegotiationPort() {
2678
    $language_domain = 'example.fr';
2679
    $edit = array(
2680
      'locale_language_negotiation_url_part' => 1,
2681
    );
2682
    $this->drupalPost('admin/config/regional/language/configure/url', $edit, t('Save configuration'));
2683
    $edit = array(
2684
      'prefix' => '',
2685
      'domain' => $language_domain
2686
    );
2687
    $this->drupalPost('admin/config/regional/language/edit/fr', $edit, t('Save language'));
2688

    
2689
    // Enable domain configuration.
2690
    variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN);
2691

    
2692
    // Reset static caching.
2693
    drupal_static_reset('language_list');
2694
    drupal_static_reset('language_url_outbound_alter');
2695
    drupal_static_reset('language_url_rewrite_url');
2696

    
2697
    // In case index.php is part of the URLs, we need to adapt the asserted
2698
    // URLs as well.
2699
    $index_php = strpos(url('', array('absolute' => TRUE)), 'index.php') !== FALSE;
2700

    
2701
    // Remember current HTTP_HOST.
2702
    $http_host = $_SERVER['HTTP_HOST'];
2703

    
2704
    // Fake a different port.
2705
    $_SERVER['HTTP_HOST'] .= ':88';
2706

    
2707
    // Create an absolute French link.
2708
    $languages = language_list();
2709
    $language = $languages['fr'];
2710
    $url = url('', array(
2711
      'absolute' => TRUE,
2712
      'language' => $language
2713
    ));
2714

    
2715
    $expected = 'http://example.fr:88/';
2716
    $expected .= $index_php ? 'index.php/' : '';
2717

    
2718
    $this->assertEqual($url, $expected, 'The right port is used.');
2719

    
2720
    // If we set the port explicitly in url(), it should not be overriden.
2721
    $url = url('', array(
2722
      'absolute' => TRUE,
2723
      'language' => $language,
2724
      'base_url' => $GLOBALS['base_url'] . ':90',
2725
    ));
2726

    
2727
    $expected = 'http://example.fr:90/';
2728
    $expected .= $index_php ? 'index.php/' : '';
2729

    
2730
    $this->assertEqual($url, $expected, 'A given port is not overriden.');
2731

    
2732
    // Restore HTTP_HOST.
2733
    $_SERVER['HTTP_HOST'] = $http_host;
2734
  }
2735
}
2736

    
2737
/**
2738
 * Functional test for multilingual fields.
2739
 */
2740
class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase {
2741
  public static function getInfo() {
2742
    return array(
2743
      'name' => 'Multilingual fields',
2744
      'description' => 'Test multilingual support for fields.',
2745
      'group' => 'Locale',
2746
    );
2747
  }
2748

    
2749
  function setUp() {
2750
    parent::setUp('locale');
2751
    // Setup users.
2752
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content'));
2753
    $this->drupalLogin($admin_user);
2754

    
2755
    // Add a new language.
2756
    require_once DRUPAL_ROOT . '/includes/locale.inc';
2757
    locale_add_language('it', 'Italian', 'Italiano', LANGUAGE_LTR, '', '', TRUE, FALSE);
2758

    
2759
    // Enable URL language detection and selection.
2760
    $edit = array('language[enabled][locale-url]' => '1');
2761
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2762

    
2763
    // Set "Basic page" content type to use multilingual support.
2764
    $edit = array(
2765
      'language_content_type' => 1,
2766
    );
2767
    $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
2768
    $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.');
2769

    
2770
    // Make node body translatable.
2771
    $field = field_info_field('body');
2772
    $field['translatable'] = TRUE;
2773
    field_update_field($field);
2774
  }
2775

    
2776
  /**
2777
   * Test if field languages are correctly set through the node form.
2778
   */
2779
  function testMultilingualNodeForm() {
2780
    // Create "Basic page" content.
2781
    $langcode = LANGUAGE_NONE;
2782
    $title_key = "title";
2783
    $title_value = $this->randomName(8);
2784
    $body_key = "body[$langcode][0][value]";
2785
    $body_value = $this->randomName(16);
2786

    
2787
    // Create node to edit.
2788
    $edit = array();
2789
    $edit[$title_key] = $title_value;
2790
    $edit[$body_key] = $body_value;
2791
    $edit['language'] = 'en';
2792
    $this->drupalPost('node/add/page', $edit, t('Save'));
2793

    
2794
    // Check that the node exists in the database.
2795
    $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2796
    $this->assertTrue($node, 'Node found in database.');
2797

    
2798
    $assert = isset($node->body['en']) && !isset($node->body[LANGUAGE_NONE]) && $node->body['en'][0]['value'] == $body_value;
2799
    $this->assertTrue($assert, 'Field language correctly set.');
2800

    
2801
    // Change node language.
2802
    $this->drupalGet("node/$node->nid/edit");
2803
    $edit = array(
2804
      $title_key => $this->randomName(8),
2805
      'language' => 'it'
2806
    );
2807
    $this->drupalPost(NULL, $edit, t('Save'));
2808
    $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2809
    $this->assertTrue($node, 'Node found in database.');
2810

    
2811
    $assert = isset($node->body['it']) && !isset($node->body['en']) && $node->body['it'][0]['value'] == $body_value;
2812
    $this->assertTrue($assert, 'Field language correctly changed.');
2813

    
2814
    // Enable content language URL detection.
2815
    language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LOCALE_LANGUAGE_NEGOTIATION_URL => 0));
2816

    
2817
    // Test multilingual field language fallback logic.
2818
    $this->drupalGet("it/node/$node->nid");
2819
    $this->assertRaw($body_value, 'Body correctly displayed using Italian as requested language');
2820

    
2821
    $this->drupalGet("node/$node->nid");
2822
    $this->assertRaw($body_value, 'Body correctly displayed using English as requested language');
2823
  }
2824

    
2825
  /*
2826
   * Test multilingual field display settings.
2827
   */
2828
  function testMultilingualDisplaySettings() {
2829
    // Create "Basic page" content.
2830
    $langcode = LANGUAGE_NONE;
2831
    $title_key = "title";
2832
    $title_value = $this->randomName(8);
2833
    $body_key = "body[$langcode][0][value]";
2834
    $body_value = $this->randomName(16);
2835

    
2836
    // Create node to edit.
2837
    $edit = array();
2838
    $edit[$title_key] = $title_value;
2839
    $edit[$body_key] = $body_value;
2840
    $edit['language'] = 'en';
2841
    $this->drupalPost('node/add/page', $edit, t('Save'));
2842

    
2843
    // Check that the node exists in the database.
2844
    $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2845
    $this->assertTrue($node, 'Node found in database.');
2846

    
2847
    // Check if node body is showed.
2848
    $this->drupalGet("node/$node->nid");
2849
    $body = $this->xpath('//div[@id=:id]//div[@property="content:encoded"]/p', array(':id' => 'node-' . $node->nid));
2850
    $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body is correctly showed.');
2851
  }
2852
}
2853

    
2854
/**
2855
 * Functional tests for comment language.
2856
 */
2857
class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase {
2858

    
2859
  public static function getInfo() {
2860
    return array(
2861
      'name' => 'Comment language',
2862
      'description' => 'Tests for comment language.',
2863
      'group' => 'Locale',
2864
    );
2865
  }
2866

    
2867
  function setUp() {
2868
    parent::setUp('locale', 'locale_test');
2869

    
2870
    // Create and login user.
2871
    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'administer content types', 'administer comments', 'create article content'));
2872
    $this->drupalLogin($admin_user);
2873

    
2874
    // Add language.
2875
    $edit = array('langcode' => 'fr');
2876
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2877

    
2878
    // Set "Article" content type to use multilingual support.
2879
    $edit = array('language_content_type' => 1);
2880
    $this->drupalPost('admin/structure/types/manage/article', $edit, t('Save content type'));
2881

    
2882
    // Enable content language negotiation UI.
2883
    variable_set('locale_test_content_language_type', TRUE);
2884

    
2885
    // Set interface language detection to user and content language detection
2886
    // to URL. Disable inheritance from interface language to ensure content
2887
    // language will fall back to the default language if no URL language can be
2888
    // detected.
2889
    $edit = array(
2890
      'language[enabled][locale-user]' => TRUE,
2891
      'language_content[enabled][locale-url]' => TRUE,
2892
      'language_content[enabled][locale-interface]' => FALSE,
2893
    );
2894
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2895

    
2896
    // Change user language preference, this way interface language is always
2897
    // French no matter what path prefix the URLs have.
2898
    $edit = array('language' => 'fr');
2899
    $this->drupalPost("user/{$admin_user->uid}/edit", $edit, t('Save'));
2900

    
2901
    // Make comment body translatable.
2902
    $field = field_info_field('comment_body');
2903
    $field['translatable'] = TRUE;
2904
    field_update_field($field);
2905
    $this->assertTrue(field_is_translatable('comment', $field), 'Comment body is translatable.');
2906
  }
2907

    
2908
  /**
2909
   * Test that comment language is properly set.
2910
   */
2911
  function testCommentLanguage() {
2912
    drupal_static_reset('language_list');
2913

    
2914
    // Create two nodes, one for english and one for french, and comment each
2915
    // node using both english and french as content language by changing URL
2916
    // language prefixes. Meanwhile interface language is always French, which
2917
    // is the user language preference. This way we can ensure that node
2918
    // language and interface language do not influence comment language, as
2919
    // only content language has to.
2920
    foreach (language_list() as $node_langcode => $node_language) {
2921
      $language_none = LANGUAGE_NONE;
2922

    
2923
      // Create "Article" content.
2924
      $title = $this->randomName();
2925
      $edit = array(
2926
        "title" => $title,
2927
        "body[$language_none][0][value]" => $this->randomName(),
2928
        "language" => $node_langcode,
2929
      );
2930
      $this->drupalPost("node/add/article", $edit, t('Save'));
2931
      $node = $this->drupalGetNodeByTitle($title);
2932

    
2933
      foreach (language_list() as $langcode => $language) {
2934
        // Post a comment with content language $langcode.
2935
        $prefix = empty($language->prefix) ? '' : $language->prefix . '/';
2936
        $comment_values[$node_langcode][$langcode] = $this->randomName();
2937
        // Initially field form widgets have no language.
2938
        $edit = array(
2939
          'subject' => $this->randomName(),
2940
          "comment_body[$language_none][0][value]" => $comment_values[$node_langcode][$langcode],
2941
        );
2942
        $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Preview'));
2943
        // After the first submit the submitted entity language is taken into
2944
        // account.
2945
        $edit = array(
2946
          'subject' => $edit['subject'],
2947
          "comment_body[$langcode][0][value]" => $comment_values[$node_langcode][$langcode],
2948
        );
2949
        $this->drupalPost(NULL, $edit, t('Save'));
2950

    
2951
        // Check that comment language matches the current content language.
2952
        $cid = db_select('comment', 'c')
2953
          ->fields('c', array('cid'))
2954
          ->condition('nid', $node->nid)
2955
          ->orderBy('cid', 'DESC')
2956
          ->range(0, 1)
2957
          ->execute()
2958
          ->fetchField();
2959
        $comment = comment_load($cid);
2960
        $comment_langcode = entity_language('comment', $comment);
2961
        $args = array('%node_language' => $node_langcode, '%comment_language' => $comment_langcode, '%langcode' => $langcode);
2962
        $this->assertEqual($comment_langcode, $langcode, format_string('The comment posted with content language %langcode and belonging to the node with language %node_language has language %comment_language', $args));
2963
        $this->assertEqual($comment->comment_body[$langcode][0]['value'], $comment_values[$node_langcode][$langcode], 'Comment body correctly stored.');
2964
      }
2965
    }
2966

    
2967
    // Check that comment bodies appear in the administration UI.
2968
    $this->drupalGet('admin/content/comment');
2969
    foreach ($comment_values as $node_values) {
2970
      foreach ($node_values as $value) {
2971
        $this->assertRaw($value);
2972
      }
2973
    }
2974
  }
2975

    
2976
}
2977

    
2978
/**
2979
 * Functional tests for localizing date formats.
2980
 */
2981
class LocaleDateFormatsFunctionalTest extends DrupalWebTestCase {
2982

    
2983
  public static function getInfo() {
2984
    return array(
2985
      'name' => 'Localize date formats',
2986
      'description' => 'Tests for the localization of date formats.',
2987
      'group' => 'Locale',
2988
    );
2989
  }
2990

    
2991
  function setUp() {
2992
    parent::setUp('locale');
2993

    
2994
    // Create and login user.
2995
    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content'));
2996
    $this->drupalLogin($admin_user);
2997
  }
2998

    
2999
  /**
3000
   * Functional tests for localizing date formats.
3001
   */
3002
  function testLocalizeDateFormats() {
3003
    // Add language.
3004
    $edit = array(
3005
      'langcode' => 'fr',
3006
    );
3007
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
3008

    
3009
    // Set language negotiation.
3010
    $language_type = LANGUAGE_TYPE_INTERFACE;
3011
    $edit = array(
3012
      "{$language_type}[enabled][locale-url]" => TRUE,
3013
    );
3014
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
3015

    
3016
    // Configure date formats.
3017
    $this->drupalGet('admin/config/regional/date-time/locale');
3018
    $this->assertText('Français', 'Configured languages appear.');
3019
    $edit = array(
3020
      'date_format_long' => 'd.m.Y - H:i',
3021
      'date_format_medium' => 'd.m.Y - H:i',
3022
      'date_format_short' => 'd.m.Y - H:i',
3023
    );
3024
    $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration'));
3025
    $this->assertText(t('Configuration saved.'), 'French date formats updated.');
3026
    $edit = array(
3027
      'date_format_long' => 'j M Y - g:ia',
3028
      'date_format_medium' => 'j M Y - g:ia',
3029
      'date_format_short' => 'j M Y - g:ia',
3030
    );
3031
    $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration'));
3032
    $this->assertText(t('Configuration saved.'), 'English date formats updated.');
3033

    
3034
    // Create node content.
3035
    $node = $this->drupalCreateNode(array('type' => 'article'));
3036

    
3037
    // Configure format for the node posted date changes with the language.
3038
    $this->drupalGet('node/' . $node->nid);
3039
    $english_date = format_date($node->created, 'custom', 'j M Y');
3040
    $this->assertText($english_date, 'English date format appears');
3041
    $this->drupalGet('fr/node/' . $node->nid);
3042
    $french_date = format_date($node->created, 'custom', 'd.m.Y');
3043
    $this->assertText($french_date, 'French date format appears');
3044
  }
3045
}
3046

    
3047
/**
3048
 * Functional test for language types/negotiation info.
3049
 */
3050
class LocaleLanguageNegotiationInfoFunctionalTest extends DrupalWebTestCase {
3051

    
3052
  public static function getInfo() {
3053
    return array(
3054
      'name' => 'Language negotiation info',
3055
      'description' => 'Tests alterations to language types/negotiation info.',
3056
      'group' => 'Locale',
3057
    );
3058
  }
3059

    
3060
  function setUp() {
3061
    parent::setUp('locale');
3062
    require_once DRUPAL_ROOT .'/includes/language.inc';
3063
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
3064
    $this->drupalLogin($admin_user);
3065
    $this->drupalPost('admin/config/regional/language/add', array('langcode' => 'it'), t('Add language'));
3066
  }
3067

    
3068
  /**
3069
   * Tests alterations to language types/negotiation info.
3070
   */
3071
  function testInfoAlterations() {
3072
    // Enable language type/negotiation info alterations.
3073
    variable_set('locale_test_language_types', TRUE);
3074
    variable_set('locale_test_language_negotiation_info', TRUE);
3075
    $this->languageNegotiationUpdate();
3076

    
3077
    // Check that fixed language types are properly configured without the need
3078
    // of saving the language negotiation settings.
3079
    $this->checkFixedLanguageTypes();
3080

    
3081
    // Make the content language type configurable by updating the language
3082
    // negotiation settings with the proper flag enabled.
3083
    variable_set('locale_test_content_language_type', TRUE);
3084
    $this->languageNegotiationUpdate();
3085
    $type = LANGUAGE_TYPE_CONTENT;
3086
    $language_types = variable_get('language_types', drupal_language_types());
3087
    $this->assertTrue($language_types[$type], 'Content language type is configurable.');
3088

    
3089
    // Enable some core and custom language providers. The test language type is
3090
    // supposed to be configurable.
3091
    $test_type = 'test_language_type';
3092
    $provider = LOCALE_LANGUAGE_NEGOTIATION_INTERFACE;
3093
    $test_provider = 'test_language_provider';
3094
    $form_field = $type . '[enabled]['. $provider .']';
3095
    $edit = array(
3096
      $form_field => TRUE,
3097
      $type . '[enabled][' . $test_provider . ']' => TRUE,
3098
      $test_type . '[enabled][' . $test_provider . ']' => TRUE,
3099
    );
3100
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
3101

    
3102
    // Remove the interface language provider by updating the language
3103
    // negotiation settings with the proper flag enabled.
3104
    variable_set('locale_test_language_negotiation_info_alter', TRUE);
3105
    $this->languageNegotiationUpdate();
3106
    $negotiation = variable_get("language_negotiation_$type", array());
3107
    $this->assertFalse(isset($negotiation[$provider]), 'Interface language provider removed from the stored settings.');
3108
    $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, 'Interface language provider unavailable.');
3109

    
3110
    // Check that type-specific language providers can be assigned only to the
3111
    // corresponding language types.
3112
    foreach (language_types_configurable() as $type) {
3113
      $form_field = $type . '[enabled][test_language_provider_ts]';
3114
      if ($type == $test_type) {
3115
        $this->assertFieldByXPath("//input[@name=\"$form_field\"]", NULL, format_string('Type-specific test language provider available for %type.', array('%type' => $type)));
3116
      }
3117
      else {
3118
        $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, format_string('Type-specific test language provider unavailable for %type.', array('%type' => $type)));
3119
      }
3120
    }
3121

    
3122
    // Check language negotiation results.
3123
    $this->drupalGet('');
3124
    $last = variable_get('locale_test_language_negotiation_last', array());
3125
    foreach (language_types() as $type) {
3126
      $langcode = $last[$type];
3127
      $value = $type == LANGUAGE_TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en';
3128
      $this->assertEqual($langcode, $value, format_string('The negotiated language for %type is %language', array('%type' => $type, '%language' => $langcode)));
3129
    }
3130

    
3131
    // Disable locale_test and check that everything is set back to the original
3132
    // status.
3133
    $this->languageNegotiationUpdate('disable');
3134

    
3135
    // Check that only the core language types are available.
3136
    foreach (language_types() as $type) {
3137
      $this->assertTrue(strpos($type, 'test') === FALSE, format_string('The %type language is still available', array('%type' => $type)));
3138
    }
3139

    
3140
    // Check that fixed language types are properly configured, even those
3141
    // previously set to configurable.
3142
    $this->checkFixedLanguageTypes();
3143

    
3144
    // Check that unavailable language providers are not present in the
3145
    // negotiation settings.
3146
    $negotiation = variable_get("language_negotiation_$type", array());
3147
    $this->assertFalse(isset($negotiation[$test_provider]), 'The disabled test language provider is not part of the content language negotiation settings.');
3148

    
3149
    // Check that configuration page presents the correct options and settings.
3150
    $this->assertNoRaw(t('Test language detection'), 'No test language type configuration available.');
3151
    $this->assertNoRaw(t('This is a test language provider'), 'No test language provider available.');
3152
  }
3153

    
3154
  /**
3155
   * Update language types/negotiation information.
3156
   *
3157
   * Manually invoke locale_modules_enabled()/locale_modules_disabled() since
3158
   * they would not be invoked after enabling/disabling locale_test the first
3159
   * time.
3160
   */
3161
  private function languageNegotiationUpdate($op = 'enable') {
3162
    static $last_op = NULL;
3163
    $modules = array('locale_test');
3164

    
3165
    // Enable/disable locale_test only if we did not already before.
3166
    if ($last_op != $op) {
3167
      $function = "module_{$op}";
3168
      $function($modules);
3169
      // Reset hook implementation cache.
3170
      module_implements(NULL, FALSE, TRUE);
3171
    }
3172

    
3173
    drupal_static_reset('language_types_info');
3174
    drupal_static_reset('language_negotiation_info');
3175
    $function = "locale_modules_{$op}d";
3176
    if (function_exists($function)) {
3177
      $function($modules);
3178
    }
3179

    
3180
    $this->drupalGet('admin/config/regional/language/configure');
3181
  }
3182

    
3183
  /**
3184
   * Check that language negotiation for fixed types matches the stored one.
3185
   */
3186
  private function checkFixedLanguageTypes() {
3187
    drupal_static_reset('language_types_info');
3188
    foreach (language_types_info() as $type => $info) {
3189
      if (isset($info['fixed'])) {
3190
        $negotiation = variable_get("language_negotiation_$type", array());
3191
        $equal = count($info['fixed']) == count($negotiation);
3192
        while ($equal && list($id) = each($negotiation)) {
3193
          list(, $info_id) = each($info['fixed']);
3194
          $equal = $info_id == $id;
3195
        }
3196
        $this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type)));
3197
      }
3198
    }
3199
  }
3200
}
3201

    
3202
/**
3203
 * Functional tests for CSS alter functions.
3204
 */
3205
class LocaleCSSAlterTest extends DrupalWebTestCase {
3206
  public static function getInfo() {
3207
    return array(
3208
      'name' => 'CSS altering',
3209
      'description' => 'Test CSS alter functions.',
3210
      'group' => 'Locale',
3211
    );
3212
  }
3213

    
3214
  function setUp() {
3215
    parent::setUp('locale');
3216
  }
3217

    
3218
  /**
3219
   * Verifies that -rtl.css file is added directly after LTR .css file.
3220
   */
3221
  function testCSSFilesOrderInRTLMode() {
3222
    global $base_url;
3223

    
3224
    // User to add and remove language.
3225
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
3226

    
3227
    // Log in as admin.
3228
    $this->drupalLogin($admin_user);
3229

    
3230
    // Install the Arabic language (which is RTL) and configure as the default.
3231
    $edit = array();
3232
    $edit['langcode'] = 'ar';
3233
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
3234

    
3235
    $edit = array();
3236
    $edit['site_default'] = 'ar';
3237
    $this->drupalPost(NULL, $edit, t('Save configuration'));
3238

    
3239
    // Verify that the -rtl.css file is added directly after LTR file.
3240
    $this->drupalGet('');
3241
    $query_string = '?' . variable_get('css_js_query_string', '0');
3242
    $this->assertRaw('@import url("' . $base_url . '/modules/system/system.base.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.base-rtl.css' . $query_string . '");' . "\n", 'CSS: system.base-rtl.css is added directly after system.base.css.');
3243
    $this->assertRaw('@import url("' . $base_url . '/modules/system/system.menus.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.menus-rtl.css' . $query_string . '");' . "\n", 'CSS: system.menus-rtl.css is added directly after system.menus.css.');
3244
    $this->assertRaw('@import url("' . $base_url . '/modules/system/system.messages.css' . $query_string . '");' . "\n" . '@import url("' . $base_url . '/modules/system/system.messages-rtl.css' . $query_string . '");' . "\n", 'CSS: system.messages-rtl.css is added directly after system.messages.css.');
3245
  }
3246
}
3247

    
3248
/**
3249
 * Tests locale translation safe string handling.
3250
 */
3251
class LocaleStringIsSafeTest extends DrupalWebTestCase {
3252
  public static function getInfo() {
3253
    return array(
3254
      'name' => 'Test if a string is safe',
3255
      'description' => 'Tests locale translation safe string handling.',
3256
      'group' => 'Locale',
3257
    );
3258
  }
3259

    
3260
  function setUp() {
3261
    parent::setUp('locale');
3262
  }
3263

    
3264
  /**
3265
   * Tests for locale_string_is_safe().
3266
   */
3267
  public function testLocaleStringIsSafe() {
3268
    // Check a translatable string without HTML.
3269
    $string = 'Hello world!';
3270
    $result = locale_string_is_safe($string);
3271
    $this->assertTrue($result);
3272

    
3273
    // Check a translatable string which includes trustable HTML.
3274
    $string = 'Hello <strong>world</strong>!';
3275
    $result = locale_string_is_safe($string);
3276
    $this->assertTrue($result);
3277

    
3278
    // Check an untranslatable string which includes untrustable HTML (according
3279
    // to the locale_string_is_safe() function definition).
3280
    $string = 'Hello <img src="world.png" alt="world" />!';
3281
    $result = locale_string_is_safe($string);
3282
    $this->assertFalse($result);
3283

    
3284
    // Check a translatable string which includes a token in an href attribute.
3285
    $string = 'Hi <a href="[current-user:url]">user</a>';
3286
    $result = locale_string_is_safe($string);
3287
    $this->assertTrue($result);
3288
  }
3289
}