Projet

Général

Profil

Paste
Télécharger (121 ko) Statistiques
| Branche: | Révision:

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

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
    // Try to edit a non-existent string and ensure we're redirected correctly.
397
    // Assuming we don't have 999,999 strings already.
398
    $random_lid = 999999;
399
    $this->drupalGet('admin/config/regional/translate/edit/' . $random_lid);
400
    $this->assertText(t('String not found'), 'String not found.');
401
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/translate', array('absolute' => TRUE)), 'Correct page redirection.');
402
    $this->drupalLogout();
403

    
404
    // Delete the language.
405
    $this->drupalLogin($admin_user);
406
    $path = 'admin/config/regional/language/delete/' . $langcode;
407
    // This a confirm form, we do not need any fields changed.
408
    $this->drupalPost($path, array(), t('Delete'));
409
    // We need raw here because %locale will add HTML.
410
    $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), 'The test language has been removed.');
411
    // Reload to remove $name.
412
    $this->drupalGet($path);
413
    // Verify that language is no longer found.
414
    $this->assertResponse(404, 'Language no longer found.');
415
    $this->drupalLogout();
416

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

    
444
  /*
445
   * Adds a language and checks that the JavaScript translation files are
446
   * properly created and rebuilt on deletion.
447
   */
448
  function testJavaScriptTranslation() {
449
    $user = $this->drupalCreateUser(array('translate interface', 'administer languages', 'access administration pages'));
450
    $this->drupalLogin($user);
451

    
452
    $langcode = 'xx';
453
    // The English name for the language. This will be translated.
454
    $name = $this->randomName(16);
455
    // The native name for the language.
456
    $native = $this->randomName(16);
457
    // The domain prefix.
458
    $prefix = $langcode;
459

    
460
    // Add custom language.
461
    $edit = array(
462
      'langcode' => $langcode,
463
      'name' => $name,
464
      'native' => $native,
465
      'prefix' => $prefix,
466
      'direction' => '0',
467
    );
468
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
469
    drupal_static_reset('language_list');
470

    
471
    // Build the JavaScript translation file.
472
    $this->drupalGet('admin/config/regional/translate/translate');
473

    
474
    // Retrieve the id of the first string available in the {locales_source}
475
    // table and translate it.
476
    $query = db_select('locales_source', 'l');
477
    $query->addExpression('min(l.lid)', 'lid');
478
    $result = $query->condition('l.location', '%.js%', 'LIKE')
479
      ->condition('l.textgroup', 'default')
480
      ->execute();
481
    $url = 'admin/config/regional/translate/edit/' . $result->fetchObject()->lid;
482
    $edit = array('translations['. $langcode .']' => $this->randomName());
483
    $this->drupalPost($url, $edit, t('Save translations'));
484

    
485
    // Trigger JavaScript translation parsing and building.
486
    require_once DRUPAL_ROOT . '/includes/locale.inc';
487
    _locale_rebuild_js($langcode);
488

    
489
    // Retrieve the JavaScript translation hash code for the custom language to
490
    // check that the translation file has been properly built.
491
    $file = db_select('languages', 'l')
492
      ->fields('l', array('javascript'))
493
      ->condition('language', $langcode)
494
      ->execute()
495
      ->fetchObject();
496
    $js_file = 'public://' . variable_get('locale_js_directory', 'languages') . '/' . $langcode . '_' . $file->javascript . '.js';
497
    $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file created: %file', array('%file' => $result ? $js_file : 'not found')));
498

    
499
    // Test JavaScript translation rebuilding.
500
    file_unmanaged_delete($js_file);
501
    $this->assertTrue($result = !file_exists($js_file), format_string('JavaScript file deleted: %file', array('%file' => $result ? $js_file : 'found')));
502
    cache_clear_all();
503
    _locale_rebuild_js($langcode);
504
    $this->assertTrue($result = file_exists($js_file), format_string('JavaScript file rebuilt: %file', array('%file' => $result ? $js_file : 'not found')));
505
  }
506

    
507
  /**
508
   * Tests the validation of the translation input.
509
   */
510
  function testStringValidation() {
511
    global $base_url;
512

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

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

    
571
  /**
572
   * Tests translation search form.
573
   */
574
  function testStringSearch() {
575
    global $base_url;
576

    
577
    // User to add and remove language.
578
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
579
    // User to translate and delete string.
580
    $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
581

    
582
    // Code for the language.
583
    $langcode = 'xx';
584
    // The English name for the language. This will be translated.
585
    $name = $this->randomName(16);
586
    // The native name for the language.
587
    $native = $this->randomName(16);
588
    // The domain prefix.
589
    $prefix = $langcode;
590
    // This is the language indicator on the translation search screen for
591
    // untranslated strings. Copied straight from locale.inc.
592
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
593
    // This will be the translation of $name.
594
    $translation = $this->randomName(16);
595

    
596
    // Add custom language.
597
    $this->drupalLogin($admin_user);
598
    $edit = array(
599
      'langcode' => $langcode,
600
      'name' => $name,
601
      'native' => $native,
602
      'prefix' => $prefix,
603
      'direction' => '0',
604
    );
605
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
606
    // Add string.
607
    t($name, array(), array('langcode' => $langcode));
608
    // Reset locale cache.
609
    locale_reset();
610
    $this->drupalLogout();
611

    
612
    // Search for the name.
613
    $this->drupalLogin($translate_user);
614
    $search = array(
615
      'string' => $name,
616
      'language' => 'all',
617
      'translation' => 'all',
618
      'group' => 'all',
619
    );
620
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
621
    // assertText() seems to remove the input field where $name always could be
622
    // found, so this is not a false assert. See how assertNoText succeeds
623
    // later.
624
    $this->assertText($name, 'Search found the string.');
625

    
626
    // Ensure untranslated string doesn't appear if searching on 'only
627
    // translated strings'.
628
    $search = array(
629
      'string' => $name,
630
      'language' => 'all',
631
      'translation' => 'translated',
632
      'group' => 'all',
633
    );
634
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
635
    $this->assertText(t('No strings available.'), "Search didn't find the string.");
636

    
637
    // Ensure untranslated string appears if searching on 'only untranslated
638
    // strings' in "all" (hasn't been translated to any language).
639
    $search = array(
640
      'string' => $name,
641
      'language' => 'all',
642
      'translation' => 'untranslated',
643
      'group' => 'all',
644
    );
645
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
646
    $this->assertNoText(t('No strings available.'), 'Search found the string.');
647

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

    
659
    // Add translation.
660
    // Assume this is the only result, given the random name.
661
    $this->clickLink(t('edit'));
662
    // We save the lid from the path.
663
    $matches = array();
664
    preg_match('!admin/config/regional/translate/edit/(\d)+!', $this->getUrl(), $matches);
665
    $lid = $matches[1];
666
    $edit = array(
667
      "translations[$langcode]" => $translation,
668
    );
669
    $this->drupalPost(NULL, $edit, t('Save translations'));
670

    
671
    // Ensure translated string does appear if searching on 'only
672
    // translated strings'.
673
    $search = array(
674
      'string' => $translation,
675
      'language' => 'all',
676
      'translation' => 'translated',
677
      'group' => 'all',
678
    );
679
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
680
    $this->assertNoText(t('No strings available.'), 'Search found the translation.');
681

    
682
    // Ensure translated source string doesn't appear if searching on 'only
683
    // untranslated strings'.
684
    $search = array(
685
      'string' => $name,
686
      'language' => 'all',
687
      'translation' => 'untranslated',
688
      'group' => 'all',
689
    );
690
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
691
    $this->assertText(t('No strings available.'), "Search didn't find the source string.");
692

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

    
704
    // Ensure translated string does appear if searching on the custom language.
705
    $search = array(
706
      'string' => $translation,
707
      'language' => $langcode,
708
      'translation' => 'all',
709
      'group' => 'all',
710
    );
711
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
712
    $this->assertNoText(t('No strings available.'), 'Search found the translation.');
713

    
714
    // Ensure translated string doesn't appear if searching on English.
715
    $search = array(
716
      'string' => $translation,
717
      'language' => 'en',
718
      'translation' => 'all',
719
      'group' => 'all',
720
    );
721
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
722
    $this->assertText(t('No strings available.'), "Search didn't find the translation.");
723

    
724
    // Search for a string that isn't in the system.
725
    $unavailable_string = $this->randomName(16);
726
    $search = array(
727
      'string' => $unavailable_string,
728
      'language' => 'all',
729
      'translation' => 'all',
730
      'group' => 'all',
731
    );
732
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
733
    $this->assertText(t('No strings available.'), "Search didn't find the invalid string.");
734
  }
735
}
736

    
737
/**
738
 * Tests plural index computation functionality.
739
 */
740
class LocalePluralFormatTest extends DrupalWebTestCase {
741
  public static function getInfo() {
742
    return array(
743
      'name' => 'Plural formula evaluation',
744
      'description' => 'Tests plural formula evaluation for various languages.',
745
      'group' => 'Locale',
746
    );
747
  }
748

    
749
  function setUp() {
750
    parent::setUp('locale', 'locale_test');
751

    
752
    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
753
    $this->drupalLogin($admin_user);
754

    
755
    // Import some .po files with formulas to set up the environment.
756
    // These will also add the languages to the system and enable them.
757
    $this->importPoFile($this->getPoFileWithSimplePlural(), array(
758
      'langcode' => 'fr',
759
    ));
760
    $this->importPoFile($this->getPoFileWithComplexPlural(), array(
761
      'langcode' => 'hr',
762
    ));
763
  }
764

    
765
  /**
766
   * Tests locale_get_plural() functionality.
767
   */
768
  function testGetPluralFormat() {
769
    $this->drupalGet('locale_test_plural_format_page');
770
    $tests = _locale_test_plural_format_tests();
771
    $result = array();
772
    foreach ($tests as $test) {
773
      $this->assertPluralFormat($test['count'], $test['language'], $test['expected-result']);
774
    }
775
  }
776

    
777
  /**
778
   * Helper assert to test locale_get_plural page.
779
   *
780
   * @param $count
781
   *  Number for testing.
782
   * @param $lang
783
   *  Language for testing
784
   * @param $expected_result
785
   *   Expected result.
786
   * @param $message
787
   */
788
  function assertPluralFormat($count, $lang, $expected_result) {
789
    $message_param =  array(
790
      '@lang' => $lang,
791
      '@count' => $count,
792
      '@expected_result' => $expected_result,
793
    );
794
    $message = t("Computed plural index for '@lang' with count @count is @expected_result.", $message_param);
795

    
796
    $message_param = array(
797
      '@lang' => $lang,
798
      '@expected_result' => $expected_result,
799
    );
800
    $this->assertText(format_string('Language: @lang, locale_get_plural: @expected_result.', $message_param, $message));
801
  }
802

    
803
  /**
804
   * Imports a standalone .po file in a given language.
805
   *
806
   * @param $contents
807
   *   Contents of the .po file to import.
808
   * @param $options
809
   *   Additional options to pass to the translation import form.
810
   */
811
  function importPoFile($contents, array $options = array()) {
812
    $name = tempnam('temporary://', "po_") . '.po';
813
    file_put_contents($name, $contents);
814
    $options['files[file]'] = $name;
815
    $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
816
    drupal_unlink($name);
817
  }
818

    
819
  /**
820
   * Returns a .po file with a simple plural formula.
821
   */
822
  function getPoFileWithSimplePlural() {
823
    return <<< EOF
824
msgid ""
825
msgstr ""
826
"Project-Id-Version: Drupal 7\\n"
827
"MIME-Version: 1.0\\n"
828
"Content-Type: text/plain; charset=UTF-8\\n"
829
"Content-Transfer-Encoding: 8bit\\n"
830
"Plural-Forms: nplurals=2; plural=(n!=1);\\n"
831

    
832
msgid "One sheep"
833
msgid_plural "@count sheep"
834
msgstr[0] "un mouton"
835
msgstr[1] "@count moutons"
836

    
837
msgid "Monday"
838
msgstr "lundi"
839
EOF;
840
  }
841

    
842
  /**
843
   * Returns a .po file with a complex plural formula.
844
   */
845
  function getPoFileWithComplexPlural() {
846
    return <<< EOF
847
msgid ""
848
msgstr ""
849
"Project-Id-Version: Drupal 7\\n"
850
"MIME-Version: 1.0\\n"
851
"Content-Type: text/plain; charset=UTF-8\\n"
852
"Content-Transfer-Encoding: 8bit\\n"
853
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
854

    
855
msgid "1 hour"
856
msgid_plural "@count hours"
857
msgstr[0] "@count sat"
858
msgstr[1] "@count sata"
859
msgstr[2] "@count sati"
860

    
861
msgid "Monday"
862
msgstr "Ponedjeljak"
863
EOF;
864
  }
865
}
866

    
867
/**
868
 * Functional tests for the import of translation files.
869
 */
870
class LocaleImportFunctionalTest extends DrupalWebTestCase {
871
  public static function getInfo() {
872
    return array(
873
      'name' => 'Translation import',
874
      'description' => 'Tests the import of locale files.',
875
      'group' => 'Locale',
876
    );
877
  }
878

    
879
  /**
880
   * A user able to create languages and import translations.
881
   */
882
  protected $admin_user = NULL;
883

    
884
  function setUp() {
885
    parent::setUp('locale', 'locale_test');
886

    
887
    $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
888
    $this->drupalLogin($this->admin_user);
889
  }
890

    
891
  /**
892
   * Test import of standalone .po files.
893
   */
894
  function testStandalonePoFile() {
895
    // Try importing a .po file.
896
    $this->importPoFile($this->getPoFile(), array(
897
      'langcode' => 'fr',
898
    ));
899

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

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

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

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

    
912

    
913
    // Try importing a .po file with invalid tags in the default text group.
914
    $this->importPoFile($this->getBadPoFile(), array(
915
      'langcode' => 'fr',
916
    ));
917

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

    
923

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

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

    
933

    
934
    // Try importing a .po file which doesn't exist.
935
    $name = $this->randomName(16);
936
    $this->drupalPost('admin/config/regional/translate/import', array(
937
      'langcode' => 'fr',
938
      'files[file]' => $name,
939
      'group' => 'custom',
940
    ), t('Import'));
941
    $this->assertEqual($this->getUrl(), url('admin/config/regional/translate/import', array('absolute' => TRUE)), 'Correct page redirection.');
942
    $this->assertText(t('File to import not found.'), 'File to import not found message.');
943

    
944

    
945
    // Try importing a .po file with overriding strings, and ensure existing
946
    // strings are kept.
947
    $this->importPoFile($this->getOverwritePoFile(), array(
948
      'langcode' => 'fr',
949
      'mode' => 1, // Existing strings are kept, only new strings are added.
950
    ));
951

    
952
    // The import should have created 1 string.
953
    $this->assertRaw(t('The translation was successfully imported. There are %number newly created translated strings, %update strings were updated and %delete strings were removed.', array('%number' => 1, '%update' => 0, '%delete' => 0)), 'The translation file was successfully imported.');
954
    // Ensure string wasn't overwritten.
955
    $search = array(
956
      'string' => 'Montag',
957
      'language' => 'fr',
958
      'translation' => 'translated',
959
      'group' => 'all',
960
    );
961
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
962
    $this->assertText(t('No strings available.'), 'String not overwritten by imported string.');
963

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

    
967
    $this->importPoFile($this->getPoFileWithBrokenPlural(), array(
968
      'langcode' => 'fr',
969
      'mode' => 1, // Existing strings are kept, only new strings are added.
970
    ));
971

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

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

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

    
985

    
986
    // Try importing a .po file with overriding strings, and ensure existing
987
    // strings are overwritten.
988
    $this->importPoFile($this->getOverwritePoFile(), array(
989
      'langcode' => 'fr',
990
      'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
991
    ));
992

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

    
1008
  /**
1009
   * Test automatic import of a module's translation files when a language is
1010
   * enabled.
1011
   */
1012
  function testAutomaticModuleTranslationImportLanguageEnable() {
1013
    // Code for the language - manually set to match the test translation file.
1014
    $langcode = 'xx';
1015
    // The English name for the language.
1016
    $name = $this->randomName(16);
1017
    // The native name for the language.
1018
    $native = $this->randomName(16);
1019
    // The domain prefix.
1020
    $prefix = $langcode;
1021

    
1022
    // Create a custom language.
1023
    $edit = array(
1024
      'langcode' => $langcode,
1025
      'name' => $name,
1026
      'native' => $native,
1027
      'prefix' => $prefix,
1028
      'direction' => '0',
1029
    );
1030
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1031

    
1032
    // Ensure the translation file was automatically imported when language was
1033
    // added.
1034
    $this->assertText(t('One translation file imported for the enabled modules.'), 'Language file automatically imported.');
1035

    
1036
    // Ensure strings were successfully imported.
1037
    $search = array(
1038
      'string' => 'lundi',
1039
      'language' => $langcode,
1040
      'translation' => 'translated',
1041
      'group' => 'all',
1042
    );
1043
    $this->drupalPost('admin/config/regional/translate/translate', $search, t('Filter'));
1044
    $this->assertNoText(t('No strings available.'), 'String successfully imported.');
1045
  }
1046

    
1047
  /**
1048
   * Test msgctxt context support.
1049
   */
1050
  function testLanguageContext() {
1051
    // Try importing a .po file.
1052
    $this->importPoFile($this->getPoFileWithContext(), array(
1053
      'langcode' => 'hr',
1054
    ));
1055

    
1056
    $this->assertIdentical(t('May', array(), array('langcode' => 'hr', 'context' => 'Long month name')), 'Svibanj', 'Long month name context is working.');
1057
    $this->assertIdentical(t('May', array(), array('langcode' => 'hr')), 'Svi.', 'Default context is working.');
1058
  }
1059

    
1060
  /**
1061
   * Test empty msgstr at end of .po file see #611786.
1062
   */
1063
  function testEmptyMsgstr() {
1064
    $langcode = 'hu';
1065

    
1066
    // Try importing a .po file.
1067
    $this->importPoFile($this->getPoFileWithMsgstr(), array(
1068
      'langcode' => $langcode,
1069
    ));
1070

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

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

    
1097
  /**
1098
   * Helper function: import a standalone .po file in a given language.
1099
   *
1100
   * @param $contents
1101
   *   Contents of the .po file to import.
1102
   * @param $options
1103
   *   Additional options to pass to the translation import form.
1104
   */
1105
  function importPoFile($contents, array $options = array()) {
1106
    $name = tempnam('temporary://', "po_") . '.po';
1107
    file_put_contents($name, $contents);
1108
    $options['files[file]'] = $name;
1109
    $this->drupalPost('admin/config/regional/translate/import', $options, t('Import'));
1110
    drupal_unlink($name);
1111
  }
1112

    
1113
  /**
1114
   * Helper function that returns a proper .po file.
1115
   */
1116
  function getPoFile() {
1117
    return <<< EOF
1118
msgid ""
1119
msgstr ""
1120
"Project-Id-Version: Drupal 7\\n"
1121
"MIME-Version: 1.0\\n"
1122
"Content-Type: text/plain; charset=UTF-8\\n"
1123
"Content-Transfer-Encoding: 8bit\\n"
1124
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1125

    
1126
msgid "One sheep"
1127
msgid_plural "@count sheep"
1128
msgstr[0] "un mouton"
1129
msgstr[1] "@count moutons"
1130

    
1131
msgid "Monday"
1132
msgstr "lundi"
1133

    
1134
msgid "Tuesday"
1135
msgstr "mardi"
1136

    
1137
msgid "Wednesday"
1138
msgstr "mercredi"
1139

    
1140
msgid "Thursday"
1141
msgstr "jeudi"
1142

    
1143
msgid "Friday"
1144
msgstr "vendredi"
1145

    
1146
msgid "Saturday"
1147
msgstr "samedi"
1148

    
1149
msgid "Sunday"
1150
msgstr "dimanche"
1151
EOF;
1152
  }
1153

    
1154
  /**
1155
   * Helper function that returns a bad .po file.
1156
   */
1157
  function getBadPoFile() {
1158
    return <<< EOF
1159
msgid ""
1160
msgstr ""
1161
"Project-Id-Version: Drupal 7\\n"
1162
"MIME-Version: 1.0\\n"
1163
"Content-Type: text/plain; charset=UTF-8\\n"
1164
"Content-Transfer-Encoding: 8bit\\n"
1165
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1166

    
1167
msgid "Save configuration"
1168
msgstr "Enregistrer la configuration"
1169

    
1170
msgid "edit"
1171
msgstr "modifier<img SRC="javascript:alert(\'xss\');">"
1172

    
1173
msgid "delete"
1174
msgstr "supprimer<script>alert('xss');</script>"
1175

    
1176
EOF;
1177
  }
1178

    
1179
  /**
1180
   * Helper function that returns a proper .po file, for testing overwriting
1181
   * existing translations.
1182
   */
1183
  function getOverwritePoFile() {
1184
    return <<< EOF
1185
msgid ""
1186
msgstr ""
1187
"Project-Id-Version: Drupal 7\\n"
1188
"MIME-Version: 1.0\\n"
1189
"Content-Type: text/plain; charset=UTF-8\\n"
1190
"Content-Transfer-Encoding: 8bit\\n"
1191
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
1192

    
1193
msgid "Monday"
1194
msgstr "Montag"
1195

    
1196
msgid "Day"
1197
msgstr "Jour"
1198
EOF;
1199
  }
1200

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

    
1216
msgctxt "Long month name"
1217
msgid "May"
1218
msgstr "Svibanj"
1219

    
1220
msgid "May"
1221
msgstr "Svi."
1222
EOF;
1223
  }
1224

    
1225
  /**
1226
   * Helper function that returns a .po file with an empty last item.
1227
   */
1228
  function getPoFileWithEmptyMsgstr() {
1229
    return <<< EOF
1230
msgid ""
1231
msgstr ""
1232
"Project-Id-Version: Drupal 7\\n"
1233
"MIME-Version: 1.0\\n"
1234
"Content-Type: text/plain; charset=UTF-8\\n"
1235
"Content-Transfer-Encoding: 8bit\\n"
1236
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1237

    
1238
msgid "Operations"
1239
msgstr ""
1240

    
1241
EOF;
1242
  }
1243
  /**
1244
   * Helper function that returns a .po file with an empty last item.
1245
   */
1246
  function getPoFileWithMsgstr() {
1247
    return <<< EOF
1248
msgid ""
1249
msgstr ""
1250
"Project-Id-Version: Drupal 7\\n"
1251
"MIME-Version: 1.0\\n"
1252
"Content-Type: text/plain; charset=UTF-8\\n"
1253
"Content-Transfer-Encoding: 8bit\\n"
1254
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1255

    
1256
msgid "Operations"
1257
msgstr "Műveletek"
1258

    
1259
msgid "Will not appear in Drupal core, so we can ensure the test passes"
1260
msgstr ""
1261

    
1262
EOF;
1263
  }
1264

    
1265

    
1266
  /**
1267
   * Returns a .po file with a missing plural formula.
1268
   */
1269
  function getPoFileWithMissingPlural() {
1270
    return <<< EOF
1271
msgid ""
1272
msgstr ""
1273
"Project-Id-Version: Drupal 7\\n"
1274
"MIME-Version: 1.0\\n"
1275
"Content-Type: text/plain; charset=UTF-8\\n"
1276
"Content-Transfer-Encoding: 8bit\\n"
1277

    
1278
msgid "Monday"
1279
msgstr "Ponedjeljak"
1280
EOF;
1281
  }
1282

    
1283
  /**
1284
   * Returns a .po file with a broken plural formula.
1285
   */
1286
  function getPoFileWithBrokenPlural() {
1287
    return <<< EOF
1288
msgid ""
1289
msgstr ""
1290
"Project-Id-Version: Drupal 7\\n"
1291
"MIME-Version: 1.0\\n"
1292
"Content-Type: text/plain; charset=UTF-8\\n"
1293
"Content-Transfer-Encoding: 8bit\\n"
1294
"Plural-Forms: broken, will not parse\\n"
1295

    
1296
msgid "Monday"
1297
msgstr "lundi"
1298
EOF;
1299
  }
1300

    
1301
}
1302

    
1303
/**
1304
 * Functional tests for the export of translation files.
1305
 */
1306
class LocaleExportFunctionalTest extends DrupalWebTestCase {
1307
  public static function getInfo() {
1308
    return array(
1309
      'name' => 'Translation export',
1310
      'description' => 'Tests the exportation of locale files.',
1311
      'group' => 'Locale',
1312
    );
1313
  }
1314

    
1315
  /**
1316
   * A user able to create languages and export translations.
1317
   */
1318
  protected $admin_user = NULL;
1319

    
1320
  function setUp() {
1321
    parent::setUp('locale', 'locale_test');
1322

    
1323
    $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
1324
    $this->drupalLogin($this->admin_user);
1325
  }
1326

    
1327
  /**
1328
   * Test exportation of translations.
1329
   */
1330
  function testExportTranslation() {
1331
    // First import some known translations.
1332
    // This will also automatically enable the 'fr' language.
1333
    $name = tempnam('temporary://', "po_") . '.po';
1334
    file_put_contents($name, $this->getPoFile());
1335
    $this->drupalPost('admin/config/regional/translate/import', array(
1336
      'langcode' => 'fr',
1337
      'files[file]' => $name,
1338
    ), t('Import'));
1339
    drupal_unlink($name);
1340

    
1341
    // Get the French translations.
1342
    $this->drupalPost('admin/config/regional/translate/export', array(
1343
      'langcode' => 'fr',
1344
    ), t('Export'));
1345

    
1346
    // Ensure we have a translation file.
1347
    $this->assertRaw('# French translation of Drupal', 'Exported French translation file.');
1348
    // Ensure our imported translations exist in the file.
1349
    $this->assertRaw('msgstr "lundi"', 'French translations present in exported file.');
1350
  }
1351

    
1352
  /**
1353
   * Test exportation of translation template file.
1354
   */
1355
  function testExportTranslationTemplateFile() {
1356
    // Get the translation template file.
1357
    // There are two 'Export' buttons on this page, but it somehow works.  It'd
1358
    // be better if we could use the submit button id like documented but that
1359
    // doesn't work.
1360
    $this->drupalPost('admin/config/regional/translate/export', array(), t('Export'));
1361
    // Ensure we have a translation file.
1362
    $this->assertRaw('# LANGUAGE translation of PROJECT', 'Exported translation template file.');
1363
  }
1364

    
1365
  /**
1366
   * Helper function that returns a proper .po file.
1367
   */
1368
  function getPoFile() {
1369
    return <<< EOF
1370
msgid ""
1371
msgstr ""
1372
"Project-Id-Version: Drupal 6\\n"
1373
"MIME-Version: 1.0\\n"
1374
"Content-Type: text/plain; charset=UTF-8\\n"
1375
"Content-Transfer-Encoding: 8bit\\n"
1376
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1377

    
1378
msgid "Monday"
1379
msgstr "lundi"
1380
EOF;
1381
  }
1382

    
1383
}
1384

    
1385
/**
1386
 * Tests for the st() function.
1387
 */
1388
class LocaleInstallTest extends DrupalWebTestCase {
1389
  public static function getInfo() {
1390
    return array(
1391
      'name' => 'String translation using st()',
1392
      'description' => 'Tests that st() works like t().',
1393
      'group' => 'Locale',
1394
    );
1395
  }
1396

    
1397
  function setUp() {
1398
    parent::setUp('locale');
1399

    
1400
    // st() lives in install.inc, so ensure that it is loaded for all tests.
1401
    require_once DRUPAL_ROOT . '/includes/install.inc';
1402
  }
1403

    
1404
  /**
1405
   * Verify that function signatures of t() and st() are equal.
1406
   */
1407
  function testFunctionSignatures() {
1408
    $reflector_t = new ReflectionFunction('t');
1409
    $reflector_st = new ReflectionFunction('st');
1410
    $this->assertEqual($reflector_t->getParameters(), $reflector_st->getParameters(), 'Function signatures of t() and st() are equal.');
1411
  }
1412
}
1413

    
1414
/**
1415
 * Locale uninstall with English UI functional test.
1416
 */
1417
class LocaleUninstallFunctionalTest extends DrupalWebTestCase {
1418
  public static function getInfo() {
1419
    return array(
1420
      'name' => 'Locale uninstall (EN)',
1421
      'description' => 'Tests the uninstall process using the built-in UI language.',
1422
      'group' => 'Locale',
1423
    );
1424
  }
1425

    
1426
  /**
1427
   * The default language set for the UI before uninstall.
1428
   */
1429
  protected $language;
1430

    
1431
  function setUp() {
1432
    parent::setUp('locale');
1433
    $this->language = 'en';
1434
  }
1435

    
1436
  /**
1437
   * Check if the values of the Locale variables are correct after uninstall.
1438
   */
1439
  function testUninstallProcess() {
1440
    $locale_module = array('locale');
1441

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

    
1446
    // Check the UI language.
1447
    drupal_language_initialize();
1448
    global $language;
1449
    $this->assertEqual($language->language, $this->language, format_string('Current language: %lang', array('%lang' => $language->language)));
1450

    
1451
    // Enable multilingual workflow option for articles.
1452
    variable_set('language_content_type_article', 1);
1453

    
1454
    // Change JavaScript translations directory.
1455
    variable_set('locale_js_directory', 'js_translations');
1456

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

    
1472
    // Disable string caching.
1473
    variable_set('locale_cache_strings', 0);
1474

    
1475
    // Change language negotiation options.
1476
    drupal_load('module', 'locale');
1477
    variable_set('language_types', drupal_language_types() + array('language_custom' => TRUE));
1478
    variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info());
1479
    variable_set('language_negotiation_' . LANGUAGE_TYPE_CONTENT, locale_language_negotiation_info());
1480
    variable_set('language_negotiation_' . LANGUAGE_TYPE_URL, locale_language_negotiation_info());
1481

    
1482
    // Change language providers settings.
1483
    variable_set('locale_language_negotiation_url_part', LOCALE_LANGUAGE_NEGOTIATION_URL_PREFIX);
1484
    variable_set('locale_language_negotiation_session_param', TRUE);
1485

    
1486
    // Uninstall Locale.
1487
    module_disable($locale_module);
1488
    drupal_uninstall_modules($locale_module);
1489

    
1490
    // Visit the front page.
1491
    $this->drupalGet('');
1492

    
1493
    // Check the init language logic.
1494
    drupal_language_initialize();
1495
    $this->assertEqual($language->language, 'en', format_string('Language after uninstall: %lang', array('%lang' => $language->language)));
1496

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

    
1500
    // Check language count.
1501
    $language_count = variable_get('language_count', 1);
1502
    $this->assertEqual($language_count, 1, format_string('Language count: %count', array('%count' => $language_count)));
1503

    
1504
    // Check language negotiation.
1505
    require_once DRUPAL_ROOT . '/includes/language.inc';
1506
    $this->assertTrue(count(language_types()) == count(drupal_language_types()), 'Language types reset');
1507
    $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_INTERFACE) == LANGUAGE_NEGOTIATION_DEFAULT;
1508
    $this->assertTrue($language_negotiation, format_string('Interface language negotiation: %setting', array('%setting' => $language_negotiation ? 'none' : 'set')));
1509
    $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_CONTENT) == LANGUAGE_NEGOTIATION_DEFAULT;
1510
    $this->assertTrue($language_negotiation, format_string('Content language negotiation: %setting', array('%setting' => $language_negotiation ? 'none' : 'set')));
1511
    $language_negotiation = language_negotiation_get(LANGUAGE_TYPE_URL) == LANGUAGE_NEGOTIATION_DEFAULT;
1512
    $this->assertTrue($language_negotiation, format_string('URL language negotiation: %setting', array('%setting' => $language_negotiation ? 'none' : 'set')));
1513

    
1514
    // Check language providers settings.
1515
    $this->assertFalse(variable_get('locale_language_negotiation_url_part', FALSE), 'URL language provider indicator settings cleared.');
1516
    $this->assertFalse(variable_get('locale_language_negotiation_session_param', FALSE), 'Visit language provider settings cleared.');
1517

    
1518
    // Check JavaScript parsed.
1519
    $javascript_parsed_count = count(variable_get('javascript_parsed', array()));
1520
    $this->assertEqual($javascript_parsed_count, 0, format_string('JavaScript parsed count: %count', array('%count' => $javascript_parsed_count)));
1521

    
1522
    // Check multilingual workflow option for articles.
1523
    $multilingual = variable_get('language_content_type_article', 0);
1524
    $this->assertEqual($multilingual, 0, format_string('Multilingual workflow option: %status', array('%status' => $multilingual ? 'enabled': 'disabled')));
1525

    
1526
    // Check JavaScript translations directory.
1527
    $locale_js_directory = variable_get('locale_js_directory', 'languages');
1528
    $this->assertEqual($locale_js_directory, 'languages', format_string('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory)));
1529

    
1530
    // Check string caching.
1531
    $locale_cache_strings = variable_get('locale_cache_strings', 1);
1532
    $this->assertEqual($locale_cache_strings, 1, format_string('String caching: %status', array('%status' => $locale_cache_strings ? 'enabled': 'disabled')));
1533
  }
1534
}
1535

    
1536
/**
1537
 * Locale uninstall with French UI functional test.
1538
 *
1539
 * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new
1540
 * test of its own. Rather, it switches the default UI language in setUp and then
1541
 * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest)
1542
 * to test with this new language.
1543
 */
1544
class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest {
1545
  public static function getInfo() {
1546
    return array(
1547
      'name' => 'Locale uninstall (FR)',
1548
      'description' => 'Tests the uninstall process using French as interface language.',
1549
      'group' => 'Locale',
1550
    );
1551
  }
1552

    
1553
  function setUp() {
1554
    parent::setUp();
1555
    $this->language = 'fr';
1556
  }
1557
}
1558

    
1559
/**
1560
 * Functional tests for the language switching feature.
1561
 */
1562
class LocaleLanguageSwitchingFunctionalTest extends DrupalWebTestCase {
1563

    
1564
  public static function getInfo() {
1565
    return array(
1566
      'name' => 'Language switching',
1567
      'description' => 'Tests for the language switching feature.',
1568
      'group' => 'Locale',
1569
    );
1570
  }
1571

    
1572
  function setUp() {
1573
    parent::setUp('locale');
1574

    
1575
    // Create and login user.
1576
    $admin_user = $this->drupalCreateUser(array('administer blocks', 'administer languages', 'translate interface', 'access administration pages'));
1577
    $this->drupalLogin($admin_user);
1578
  }
1579

    
1580
  /**
1581
   * Functional tests for the language switcher block.
1582
   */
1583
  function testLanguageBlock() {
1584
    // Enable the language switching block.
1585
    $language_type = LANGUAGE_TYPE_INTERFACE;
1586
    $edit = array(
1587
      "blocks[locale_{$language_type}][region]" => 'sidebar_first',
1588
    );
1589
    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
1590

    
1591
    // Add language.
1592
    $edit = array(
1593
      'langcode' => 'fr',
1594
    );
1595
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1596

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

    
1601
    // Assert that the language switching block is displayed on the frontpage.
1602
    $this->drupalGet('');
1603
    $this->assertText(t('Languages'), 'Language switcher block found.');
1604

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

    
1637
/**
1638
 * Test browser language detection.
1639
 */
1640
class LocaleBrowserDetectionTest extends DrupalUnitTestCase {
1641

    
1642
  public static function getInfo() {
1643
    return array(
1644
      'name' => 'Browser language detection',
1645
      'description' => 'Tests for the browser language detection.',
1646
      'group' => 'Locale',
1647
    );
1648
  }
1649

    
1650
  /**
1651
   * Unit tests for the locale_language_from_browser() function.
1652
   */
1653
  function testLanguageFromBrowser() {
1654
    // Load the required functions.
1655
    require_once DRUPAL_ROOT . '/includes/locale.inc';
1656

    
1657
    $languages = array(
1658
      // In our test case, 'en' has priority over 'en-US'.
1659
      'en' => (object) array(
1660
        'language' => 'en',
1661
      ),
1662
      'en-US' => (object) array(
1663
        'language' => 'en-US',
1664
      ),
1665
      // But 'fr-CA' has priority over 'fr'.
1666
      'fr-CA' => (object) array(
1667
        'language' => 'fr-CA',
1668
      ),
1669
      'fr' => (object) array(
1670
        'language' => 'fr',
1671
      ),
1672
      // 'es-MX' is alone.
1673
      'es-MX' => (object) array(
1674
        'language' => 'es-MX',
1675
      ),
1676
      // 'pt' is alone.
1677
      'pt' => (object) array(
1678
        'language' => 'pt',
1679
      ),
1680
      // Language codes with more then one dash are actually valid.
1681
      // eh-oh-laa-laa is the official language code of the Teletubbies.
1682
      'eh-oh-laa-laa' => (object) array(
1683
        'language' => 'eh-oh-laa-laa',
1684
      ),
1685
    );
1686

    
1687
    $test_cases = array(
1688
      // Equal qvalue for each language, choose the site preferred one.
1689
      'en,en-US,fr-CA,fr,es-MX' => 'en',
1690
      'en-US,en,fr-CA,fr,es-MX' => 'en',
1691
      'fr,en' => 'en',
1692
      'en,fr' => 'en',
1693
      'en-US,fr' => 'en',
1694
      'fr,en-US' => 'en',
1695
      'fr,fr-CA' => 'fr-CA',
1696
      'fr-CA,fr' => 'fr-CA',
1697
      'fr' => 'fr-CA',
1698
      'fr;q=1' => 'fr-CA',
1699
      'fr,es-MX' => 'fr-CA',
1700
      'fr,es' => 'fr-CA',
1701
      'es,fr' => 'fr-CA',
1702
      'es-MX,de' => 'es-MX',
1703
      'de,es-MX' => 'es-MX',
1704

    
1705
      // Different cases and whitespace.
1706
      'en' => 'en',
1707
      'En' => 'en',
1708
      'EN' => 'en',
1709
      ' en' => 'en',
1710
      'en ' => 'en',
1711
      'en, fr' => 'en',
1712

    
1713
      // A less specific language from the browser matches a more specific one
1714
      // from the website, and the other way around for compatibility with
1715
      // some versions of Internet Explorer.
1716
      'es' => 'es-MX',
1717
      'es-MX' => 'es-MX',
1718
      'pt' => 'pt',
1719
      'pt-PT' => 'pt',
1720
      'pt-PT;q=0.5,pt-BR;q=1,en;q=0.7' => 'en',
1721
      'pt-PT;q=1,pt-BR;q=0.5,en;q=0.7' => 'en',
1722
      'pt-PT;q=0.4,pt-BR;q=0.1,en;q=0.7' => 'en',
1723
      'pt-PT;q=0.1,pt-BR;q=0.4,en;q=0.7' => 'en',
1724

    
1725
      // Language code with several dashes are valid. The less specific language
1726
      // from the browser matches the more specific one from the website.
1727
      'eh-oh-laa-laa' => 'eh-oh-laa-laa',
1728
      'eh-oh-laa' => 'eh-oh-laa-laa',
1729
      'eh-oh' => 'eh-oh-laa-laa',
1730
      'eh' => 'eh-oh-laa-laa',
1731

    
1732
      // Different qvalues.
1733
      'fr,en;q=0.5' => 'fr-CA',
1734
      'fr,en;q=0.5,fr-CA;q=0.25' => 'fr',
1735

    
1736
      // Silly wildcards are also valid.
1737
      '*,fr-CA;q=0.5' => 'en',
1738
      '*,en;q=0.25' => 'fr-CA',
1739
      'en,en-US;q=0.5,fr;q=0.25' => 'en',
1740
      'en-US,en;q=0.5,fr;q=0.25' => 'en-US',
1741

    
1742
      // Unresolvable cases.
1743
      '' => FALSE,
1744
      'de,pl' => FALSE,
1745
      'iecRswK4eh' => FALSE,
1746
      $this->randomName(10) => FALSE,
1747
    );
1748

    
1749
    foreach ($test_cases as $accept_language => $expected_result) {
1750
      $_SERVER['HTTP_ACCEPT_LANGUAGE'] = $accept_language;
1751
      $result = locale_language_from_browser($languages);
1752
      $this->assertIdentical($result, $expected_result, format_string("Language selection '@accept-language' selects '@result', result = '@actual'", array('@accept-language' => $accept_language, '@result' => $expected_result, '@actual' => isset($result) ? $result : 'none')));
1753
    }
1754
  }
1755
}
1756

    
1757
/**
1758
 * Functional tests for a user's ability to change their default language.
1759
 */
1760
class LocaleUserLanguageFunctionalTest extends DrupalWebTestCase {
1761
  public static function getInfo() {
1762
    return array(
1763
      'name' => 'User language settings',
1764
      'description' => "Tests user's ability to change their default language.",
1765
      'group' => 'Locale',
1766
    );
1767
  }
1768

    
1769
  function setUp() {
1770
    parent::setUp('locale');
1771
  }
1772

    
1773
  /**
1774
   * Test if user can change their default language.
1775
   */
1776
  function testUserLanguageConfiguration() {
1777
    global $base_url;
1778

    
1779
    // User to add and remove language.
1780
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
1781
    // User to change their default language.
1782
    $web_user = $this->drupalCreateUser();
1783

    
1784
    // Add custom language.
1785
    $this->drupalLogin($admin_user);
1786
    // Code for the language.
1787
    $langcode = 'xx';
1788
    // The English name for the language.
1789
    $name = $this->randomName(16);
1790
    // The native name for the language.
1791
    $native = $this->randomName(16);
1792
    // The domain prefix.
1793
    $prefix = 'xx';
1794
    $edit = array(
1795
      'langcode' => $langcode,
1796
      'name' => $name,
1797
      'native' => $native,
1798
      'prefix' => $prefix,
1799
      'direction' => '0',
1800
    );
1801
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1802

    
1803
    // Add custom language and disable it.
1804
    // Code for the language.
1805
    $langcode_disabled = 'xx-yy';
1806
    // The English name for the language. This will be translated.
1807
    $name_disabled = $this->randomName(16);
1808
    // The native name for the language.
1809
    $native_disabled = $this->randomName(16);
1810
    // The domain prefix.
1811
    $prefix_disabled = $langcode_disabled;
1812
    $edit = array(
1813
      'langcode' => $langcode_disabled,
1814
      'name' => $name_disabled,
1815
      'native' => $native_disabled,
1816
      'prefix' => $prefix_disabled,
1817
      'direction' => '0',
1818
    );
1819
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1820
    // Disable the language.
1821
    $edit = array(
1822
      'enabled[' . $langcode_disabled . ']' => FALSE,
1823
    );
1824
    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
1825
    $this->drupalLogout();
1826

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

    
1848
    $this->drupalLogout();
1849
  }
1850
}
1851

    
1852
/**
1853
 * Functional test for language handling during user creation.
1854
 */
1855
class LocaleUserCreationTest extends DrupalWebTestCase {
1856

    
1857
  public static function getInfo() {
1858
    return array(
1859
      'name' => 'User creation',
1860
      'description' => 'Tests whether proper language is stored for new users and access to language selector.',
1861
      'group' => 'Locale',
1862
    );
1863
  }
1864

    
1865
  function setUp() {
1866
    parent::setUp('locale');
1867
    variable_set('user_register', USER_REGISTER_VISITORS);
1868
  }
1869

    
1870
  /**
1871
   * Functional test for language handling during user creation.
1872
   */
1873
  function testLocalUserCreation() {
1874
    // User to add and remove language and create new users.
1875
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'administer users'));
1876
    $this->drupalLogin($admin_user);
1877

    
1878
    // Add predefined language.
1879
    $langcode = 'fr';
1880
    $edit = array(
1881
      'langcode' => 'fr',
1882
    );
1883
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
1884
    $this->assertText($langcode, 'Language added successfully.');
1885
    $this->assertEqual($this->getUrl(), url('admin/config/regional/language', array('absolute' => TRUE)), 'Correct page redirection.');
1886

    
1887
    // Set language negotiation.
1888
    $edit = array(
1889
      'language[enabled][locale-url]' => TRUE,
1890
    );
1891
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
1892
    $this->assertText(t('Language negotiation configuration saved.'), 'Set language negotiation.');
1893

    
1894
    // Check if the language selector is available on admin/people/create and
1895
    // set to the currently active language.
1896
    $this->drupalGet($langcode . '/admin/people/create');
1897
    $this->assertFieldChecked("edit-language-$langcode", 'Global language set in the language selector.');
1898

    
1899
    // Create a user with the admin/people/create form and check if the correct
1900
    // language is set.
1901
    $username = $this->randomName(10);
1902
    $edit = array(
1903
      'name' => $username,
1904
      'mail' => $this->randomName(4) . '@example.com',
1905
      'pass[pass1]' => $username,
1906
      'pass[pass2]' => $username,
1907
    );
1908

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

    
1911
    $user = user_load_by_name($username);
1912
    $this->assertEqual($user->language, $langcode, 'New user has correct language set.');
1913

    
1914
    // Register a new user and check if the language selector is hidden.
1915
    $this->drupalLogout();
1916

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

    
1920
    $username = $this->randomName(10);
1921
    $edit = array(
1922
      'name' => $username,
1923
      'mail' => $this->randomName(4) . '@example.com',
1924
    );
1925

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

    
1928
    $user = user_load_by_name($username);
1929
    $this->assertEqual($user->language, $langcode, 'New user has correct language set.');
1930

    
1931
    // Test if the admin can use the language selector and if the
1932
    // correct language is was saved.
1933
    $user_edit = $langcode . '/user/' . $user->uid . '/edit';
1934

    
1935
    $this->drupalLogin($admin_user);
1936
    $this->drupalGet($user_edit);
1937
    $this->assertFieldChecked("edit-language-$langcode", 'Language selector is accessible and correct language is selected.');
1938

    
1939
    // Set pass_raw so we can login the new user.
1940
    $user->pass_raw = $this->randomName(10);
1941
    $edit = array(
1942
      'pass[pass1]' => $user->pass_raw,
1943
      'pass[pass2]' => $user->pass_raw,
1944
    );
1945

    
1946
    $this->drupalPost($user_edit, $edit, t('Save'));
1947

    
1948
    $this->drupalLogin($user);
1949
    $this->drupalGet($user_edit);
1950
    $this->assertFieldChecked("edit-language-$langcode", 'Language selector is accessible and correct language is selected.');
1951
  }
1952
}
1953

    
1954
/**
1955
 * Functional tests for configuring a different path alias per language.
1956
 */
1957
class LocalePathFunctionalTest extends DrupalWebTestCase {
1958
  public static function getInfo() {
1959
    return array(
1960
      'name' => 'Path language settings',
1961
      'description' => 'Checks you can configure a language for individual URL aliases.',
1962
      'group' => 'Locale',
1963
    );
1964
  }
1965

    
1966
  function setUp() {
1967
    parent::setUp('locale', 'path');
1968
  }
1969

    
1970
  /**
1971
   * Test if a language can be associated with a path alias.
1972
   */
1973
  function testPathLanguageConfiguration() {
1974
    global $base_url;
1975

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

    
1979
    // Add custom language.
1980
    $this->drupalLogin($admin_user);
1981
    // Code for the language.
1982
    $langcode = 'xx';
1983
    // The English name for the language.
1984
    $name = $this->randomName(16);
1985
    // The native name for the language.
1986
    $native = $this->randomName(16);
1987
    // The domain prefix.
1988
    $prefix = $langcode;
1989
    $edit = array(
1990
      'langcode' => $langcode,
1991
      'name' => $name,
1992
      'native' => $native,
1993
      'prefix' => $prefix,
1994
      'direction' => '0',
1995
    );
1996
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
1997

    
1998
    // Check that the "xx" front page is not available when path prefixes are
1999
    // not enabled yet.
2000
    $this->drupalPost('admin/config/regional/language/configure', array(), t('Save settings'));
2001
    $this->drupalGet($prefix);
2002
    $this->assertResponse(404, 'The "xx" front page is not available yet.');
2003

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

    
2008
    // Create a node.
2009
    $node = $this->drupalCreateNode(array('type' => 'page'));
2010

    
2011
    // Create a path alias in default language (English).
2012
    $path = 'admin/config/search/path/add';
2013
    $english_path = $this->randomName(8);
2014
    $edit = array(
2015
      'source'   => 'node/' . $node->nid,
2016
      'alias'    => $english_path,
2017
      'language' => 'en',
2018
    );
2019
    $this->drupalPost($path, $edit, t('Save'));
2020

    
2021
    // Create a path alias in new custom language.
2022
    $custom_language_path = $this->randomName(8);
2023
    $edit = array(
2024
      'source'   => 'node/' . $node->nid,
2025
      'alias'    => $custom_language_path,
2026
      'language' => $langcode,
2027
    );
2028
    $this->drupalPost($path, $edit, t('Save'));
2029

    
2030
    // Confirm English language path alias works.
2031
    $this->drupalGet($english_path);
2032
    $this->assertText($node->title, 'English alias works.');
2033

    
2034
    // Confirm custom language path alias works.
2035
    $this->drupalGet($prefix . '/' . $custom_language_path);
2036
    $this->assertText($node->title, 'Custom language alias works.');
2037

    
2038
    // Create a custom path.
2039
    $custom_path = $this->randomName(8);
2040

    
2041
    // Check priority of language for alias by source path.
2042
    $edit = array(
2043
      'source'   => 'node/' . $node->nid,
2044
      'alias'    => $custom_path,
2045
      'language' => LANGUAGE_NONE,
2046
    );
2047
    path_save($edit);
2048
    $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, 'en');
2049
    $this->assertEqual($english_path, $lookup_path, 'English language alias has priority.');
2050
    // Same check for language 'xx'.
2051
    $lookup_path = drupal_lookup_path('alias', 'node/' . $node->nid, $prefix);
2052
    $this->assertEqual($custom_language_path, $lookup_path, 'Custom language alias has priority.');
2053
    path_delete($edit);
2054

    
2055
    // Create language nodes to check priority of aliases.
2056
    $first_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
2057
    $second_node = $this->drupalCreateNode(array('type' => 'article', 'promote' => 1));
2058

    
2059
    // Assign a custom path alias to the first node with the English language.
2060
    $edit = array(
2061
      'source'   => 'node/' . $first_node->nid,
2062
      'alias'    => $custom_path,
2063
      'language' => 'en',
2064
    );
2065
    path_save($edit);
2066

    
2067
    // Assign a custom path alias to second node with LANGUAGE_NONE.
2068
    $edit = array(
2069
      'source'   => 'node/' . $second_node->nid,
2070
      'alias'    => $custom_path,
2071
      'language' => LANGUAGE_NONE,
2072
    );
2073
    path_save($edit);
2074

    
2075
    // Test that both node titles link to our path alias.
2076
    $this->drupalGet('<front>');
2077
    $custom_path_url = base_path() . (variable_get('clean_url', 0) ? $custom_path : '?q=' . $custom_path);
2078
    $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $first_node->title));
2079
    $this->assertTrue(!empty($elements), 'First node links to the path alias.');
2080
    $elements = $this->xpath('//a[@href=:href and .=:title]', array(':href' => $custom_path_url, ':title' => $second_node->title));
2081
    $this->assertTrue(!empty($elements), 'Second node links to the path alias.');
2082

    
2083
    // Confirm that the custom path leads to the first node.
2084
    $this->drupalGet($custom_path);
2085
    $this->assertText($first_node->title, 'Custom alias returns first node.');
2086

    
2087
    // Confirm that the custom path with prefix leads to the second node.
2088
    $this->drupalGet($prefix . '/' . $custom_path);
2089
    $this->assertText($second_node->title, 'Custom alias with prefix returns second node.');
2090
  }
2091
}
2092

    
2093
/**
2094
 * Functional tests for multilingual support on nodes.
2095
 */
2096
class LocaleContentFunctionalTest extends DrupalWebTestCase {
2097
  public static function getInfo() {
2098
    return array(
2099
      'name' => 'Content language settings',
2100
      'description' => 'Checks you can enable multilingual support on content types and configure a language for a node.',
2101
      'group' => 'Locale',
2102
    );
2103
  }
2104

    
2105
  function setUp() {
2106
    parent::setUp('locale');
2107
  }
2108

    
2109
  /**
2110
   * Verifies that machine name fields are always LTR.
2111
   */
2112
  function testMachineNameLTR() {
2113
    // User to add and remove language.
2114
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
2115

    
2116
    // Log in as admin.
2117
    $this->drupalLogin($admin_user);
2118

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

    
2123
    // Install the Arabic language (which is RTL) and configure as the default.
2124
    $edit = array();
2125
    $edit['langcode'] = 'ar';
2126
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2127

    
2128
    $edit = array();
2129
    $edit['site_default'] = 'ar';
2130
    $this->drupalPost(NULL, $edit, t('Save configuration'));
2131

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

    
2137
  /**
2138
   * Test if a content type can be set to multilingual and language setting is
2139
   * present on node add and edit forms.
2140
   */
2141
  function testContentTypeLanguageConfiguration() {
2142
    global $base_url;
2143

    
2144
    // User to add and remove language.
2145
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages'));
2146
    // User to create a node.
2147
    $web_user = $this->drupalCreateUser(array('create article content', 'create page content', 'edit any page content'));
2148

    
2149
    // Add custom language.
2150
    $this->drupalLogin($admin_user);
2151
    // Code for the language.
2152
    $langcode = 'xx';
2153
    // The English name for the language.
2154
    $name = $this->randomName(16);
2155
    // The native name for the language.
2156
    $native = $this->randomName(16);
2157
    // The domain prefix.
2158
    $prefix = $langcode;
2159
    $edit = array(
2160
      'langcode' => $langcode,
2161
      'name' => $name,
2162
      'native' => $native,
2163
      'prefix' => $prefix,
2164
      'direction' => '0',
2165
    );
2166
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
2167

    
2168
    // Add disabled custom language.
2169
    // Code for the language.
2170
    $langcode_disabled = 'xx-yy';
2171
    // The English name for the language.
2172
    $name_disabled = $this->randomName(16);
2173
    // The native name for the language.
2174
    $native_disabled = $this->randomName(16);
2175
    // The domain prefix.
2176
    $prefix_disabled = $langcode_disabled;
2177
    $edit = array(
2178
      'langcode' => $langcode_disabled,
2179
      'name' => $name_disabled,
2180
      'native' => $native_disabled,
2181
      'prefix' => $prefix_disabled,
2182
      'direction' => '0',
2183
    );
2184
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add custom language'));
2185
    // Disable second custom language.
2186
    $path = 'admin/config/regional/language';
2187
    $edit = array(
2188
      'enabled[' . $langcode_disabled . ']' => FALSE,
2189
    );
2190
    $this->drupalPost($path, $edit, t('Save configuration'));
2191

    
2192
    // Set "Basic page" content type to use multilingual support.
2193
    $this->drupalGet('admin/structure/types/manage/page');
2194
    $this->assertText(t('Multilingual support'), 'Multilingual support fieldset present on content type configuration form.');
2195
    $edit = array(
2196
      'language_content_type' => 1,
2197
    );
2198
    $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
2199
    $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.');
2200
    $this->drupalLogout();
2201

    
2202
    // Verify language selection is not present on add article form.
2203
    $this->drupalLogin($web_user);
2204
    $this->drupalGet('node/add/article');
2205
    // Verify language select list is not present.
2206
    $this->assertNoFieldByName('language', NULL, 'Language select not present on add article form.');
2207

    
2208
    // Verify language selection appears on add "Basic page" form.
2209
    $this->drupalGet('node/add/page');
2210
    // Verify language select list is present.
2211
    $this->assertFieldByName('language', NULL, 'Language select present on add Basic page form.');
2212
    // Ensure enabled language appears.
2213
    $this->assertText($name, 'Enabled language present.');
2214
    // Ensure disabled language doesn't appear.
2215
    $this->assertNoText($name_disabled, 'Disabled language not present.');
2216

    
2217
    // Create "Basic page" content.
2218
    $node_title = $this->randomName();
2219
    $node_body =  $this->randomName();
2220
    $edit = array(
2221
      'type' => 'page',
2222
      'title' => $node_title,
2223
      'body' => array($langcode => array(array('value' => $node_body))),
2224
      'language' => $langcode,
2225
    );
2226
    $node = $this->drupalCreateNode($edit);
2227
    // Edit the content and ensure correct language is selected.
2228
    $path = 'node/' . $node->nid . '/edit';
2229
    $this->drupalGet($path);
2230
    $this->assertRaw('<option value="' . $langcode . '" selected="selected">' .  $name . '</option>', 'Correct language selected.');
2231
    // Ensure we can change the node language.
2232
    $edit = array(
2233
      'language' => 'en',
2234
    );
2235
    $this->drupalPost($path, $edit, t('Save'));
2236
    $this->assertRaw(t('%title has been updated.', array('%title' => $node_title)), 'Basic page content updated.');
2237

    
2238
    $this->drupalLogout();
2239
  }
2240
}
2241

    
2242
/**
2243
 * Test UI language negotiation
2244
 * 1. URL (PATH) > DEFAULT
2245
 *    UI Language base on URL prefix, browser language preference has no
2246
 *    influence:
2247
 *      admin/config
2248
 *        UI in site default language
2249
 *      zh-hans/admin/config
2250
 *        UI in Chinese
2251
 *      blah-blah/admin/config
2252
 *        404
2253
 * 2. URL (PATH) > BROWSER > DEFAULT
2254
 *        admin/config
2255
 *          UI in user's browser language preference if the site has that
2256
 *          language enabled, if not, the default language
2257
 *        zh-hans/admin/config
2258
 *          UI in Chinese
2259
 *        blah-blah/admin/config
2260
 *          404
2261
 * 3. URL (DOMAIN) > DEFAULT
2262
 *        http://example.com/admin/config
2263
 *          UI language in site default
2264
 *        http://example.cn/admin/config
2265
 *          UI language in Chinese
2266
 */
2267
class LocaleUILanguageNegotiationTest extends DrupalWebTestCase {
2268
  public static function getInfo() {
2269
    return array(
2270
      'name' => 'UI language negotiation',
2271
      'description' => 'Test UI language switching by URL path prefix and domain.',
2272
      'group' => 'Locale',
2273
    );
2274
  }
2275

    
2276
  function setUp() {
2277
    parent::setUp('locale', 'locale_test');
2278
    require_once DRUPAL_ROOT . '/includes/language.inc';
2279
    drupal_load('module', 'locale');
2280
    $admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages', 'administer blocks'));
2281
    $this->drupalLogin($admin_user);
2282
  }
2283

    
2284
  /**
2285
   * Tests for language switching by URL path.
2286
   */
2287
  function testUILanguageNegotiation() {
2288
    // A few languages to switch to.
2289
    // This one is unknown, should get the default lang version.
2290
    $language_unknown = 'blah-blah';
2291
    // For testing browser lang preference.
2292
    $language_browser_fallback = 'vi';
2293
    // For testing path prefix.
2294
    $language = 'zh-hans';
2295
    // For setting browser language preference to 'vi'.
2296
    $http_header_browser_fallback = array("Accept-Language: $language_browser_fallback;q=1");
2297
    // For setting browser language preference to some unknown.
2298
    $http_header_blah = array("Accept-Language: blah;q=1");
2299

    
2300
    // This domain should switch the UI to Chinese.
2301
    $language_domain = 'example.cn';
2302

    
2303
    // Setup the site languages by installing two languages.
2304
    require_once DRUPAL_ROOT . '/includes/locale.inc';
2305
    locale_add_language($language_browser_fallback);
2306
    locale_add_language($language);
2307

    
2308
    // We will look for this string in the admin/config screen to see if the
2309
    // corresponding translated string is shown.
2310
    $default_string = 'Configure languages for content and the user interface';
2311

    
2312
    // Set the default language in order for the translated string to be registered
2313
    // into database when seen by t(). Without doing this, our target string
2314
    // is for some reason not found when doing translate search. This might
2315
    // be some bug.
2316
    drupal_static_reset('language_list');
2317
    $languages = language_list('enabled');
2318
    variable_set('language_default', $languages[1]['vi']);
2319
    // First visit this page to make sure our target string is searchable.
2320
    $this->drupalGet('admin/config');
2321
    // Now the t()'ed string is in db so switch the language back to default.
2322
    variable_del('language_default');
2323

    
2324
    // Translate the string.
2325
    $language_browser_fallback_string = "In $language_browser_fallback In $language_browser_fallback In $language_browser_fallback";
2326
    $language_string = "In $language In $language In $language";
2327
    // Do a translate search of our target string.
2328
    $edit = array( 'string' => $default_string);
2329
    $this->drupalPost('admin/config/regional/translate/translate', $edit, t('Filter'));
2330
    // Should find the string and now click edit to post translated string.
2331
    $this->clickLink('edit');
2332
    $edit = array(
2333
      "translations[$language_browser_fallback]" => $language_browser_fallback_string,
2334
      "translations[$language]" => $language_string,
2335
    );
2336
    $this->drupalPost(NULL, $edit, t('Save translations'));
2337

    
2338
    // Configure URL language rewrite.
2339
    variable_set('locale_language_negotiation_url_type', LANGUAGE_TYPE_INTERFACE);
2340

    
2341
    $tests = array(
2342
      // Default, browser preference should have no influence.
2343
      array(
2344
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2345
        'path' => 'admin/config',
2346
        'expect' => $default_string,
2347
        'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2348
        'http_header' => $http_header_browser_fallback,
2349
        'message' => 'URL (PATH) > DEFAULT: no language prefix, UI language is default and the browser language preference setting is not used.',
2350
      ),
2351
      // Language prefix.
2352
      array(
2353
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2354
        'path' => "$language/admin/config",
2355
        'expect' => $language_string,
2356
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_URL,
2357
        'http_header' => $http_header_browser_fallback,
2358
        'message' => 'URL (PATH) > DEFAULT: with language prefix, UI language is switched based on path prefix',
2359
      ),
2360
      // Default, go by browser preference.
2361
      array(
2362
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER),
2363
        'path' => 'admin/config',
2364
        'expect' => $language_browser_fallback_string,
2365
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_BROWSER,
2366
        'http_header' => $http_header_browser_fallback,
2367
        'message' => 'URL (PATH) > BROWSER: no language prefix, UI language is determined by browser language preference',
2368
      ),
2369
      // Prefix, switch to the language.
2370
      array(
2371
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER),
2372
        'path' => "$language/admin/config",
2373
        'expect' => $language_string,
2374
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_URL,
2375
        'http_header' => $http_header_browser_fallback,
2376
        'message' => 'URL (PATH) > BROWSER: with langage prefix, UI language is based on path prefix',
2377
      ),
2378
      // Default, browser language preference is not one of site's lang.
2379
      array(
2380
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LOCALE_LANGUAGE_NEGOTIATION_BROWSER, LANGUAGE_NEGOTIATION_DEFAULT),
2381
        'path' => 'admin/config',
2382
        'expect' => $default_string,
2383
        'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2384
        'http_header' => $http_header_blah,
2385
        'message' => 'URL (PATH) > BROWSER > DEFAULT: no language prefix and browser language preference set to unknown language should use default language',
2386
      ),
2387
    );
2388

    
2389
    foreach ($tests as $test) {
2390
      $this->runTest($test);
2391
    }
2392

    
2393
    // Unknown language prefix should return 404.
2394
    variable_set('language_negotiation_' . LANGUAGE_TYPE_INTERFACE, locale_language_negotiation_info());
2395
    $this->drupalGet("$language_unknown/admin/config", array(), $http_header_browser_fallback);
2396
    $this->assertResponse(404, "Unknown language path prefix should return 404");
2397

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

    
2404
    $tests = array(
2405
      // Default domain, browser preference should have no influence.
2406
      array(
2407
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2408
        'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN,
2409
        'path' => 'admin/config',
2410
        'expect' => $default_string,
2411
        'expected_provider' => LANGUAGE_NEGOTIATION_DEFAULT,
2412
        'http_header' => $http_header_browser_fallback,
2413
        'message' => 'URL (DOMAIN) > DEFAULT: default domain should get default language',
2414
      ),
2415
      // Language domain specific URL, we set the $_SERVER['HTTP_HOST'] in
2416
      // locale_test.module hook_boot() to simulate this.
2417
      array(
2418
        'language_negotiation' => array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT),
2419
        'locale_language_negotiation_url_part' => LOCALE_LANGUAGE_NEGOTIATION_URL_DOMAIN,
2420
        'locale_test_domain' => $language_domain . ':88',
2421
        'path' => 'admin/config',
2422
        'expect' => $language_string,
2423
        'expected_provider' => LOCALE_LANGUAGE_NEGOTIATION_URL,
2424
        'http_header' => $http_header_browser_fallback,
2425
        'message' => 'URL (DOMAIN) > DEFAULT: domain example.cn should switch to Chinese',
2426
      ),
2427
    );
2428

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

    
2434
  private function runTest($test) {
2435
    if (!empty($test['language_negotiation'])) {
2436
      $negotiation = array_flip($test['language_negotiation']);
2437
      language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation);
2438
    }
2439
    if (!empty($test['locale_language_negotiation_url_part'])) {
2440
      variable_set('locale_language_negotiation_url_part', $test['locale_language_negotiation_url_part']);
2441
    }
2442
    if (!empty($test['locale_test_domain'])) {
2443
      variable_set('locale_test_domain', $test['locale_test_domain']);
2444
    }
2445
    $this->drupalGet($test['path'], array(), $test['http_header']);
2446
    $this->assertText($test['expect'], $test['message']);
2447
    $this->assertText(t('Language negotiation provider: @name', array('@name' => $test['expected_provider'])));
2448
  }
2449

    
2450
  /**
2451
   * Test URL language detection when the requested URL has no language.
2452
   */
2453
  function testUrlLanguageFallback() {
2454
    // Add the Italian language.
2455
    $language_browser_fallback = 'it';
2456
    locale_add_language($language_browser_fallback);
2457
    $languages = language_list();
2458

    
2459
    // Enable the path prefix for the default language: this way any unprefixed
2460
    // URL must have a valid fallback value.
2461
    $edit = array('prefix' => 'en');
2462
    $this->drupalPost('admin/config/regional/language/edit/en', $edit, t('Save language'));
2463

    
2464
    // Enable browser and URL language detection.
2465
    $edit = array(
2466
      'language[enabled][locale-browser]' => TRUE,
2467
      'language[enabled][locale-url]' => TRUE,
2468
      'language[weight][locale-browser]' => -8,
2469
      'language[weight][locale-url]' => -10,
2470
    );
2471
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2472
    $this->drupalGet('admin/config/regional/language/configure');
2473

    
2474
    // Enable the language switcher block.
2475
    $edit = array('blocks[locale_language][region]' => 'sidebar_first');
2476
    $this->drupalPost('admin/structure/block', $edit, t('Save blocks'));
2477

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

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

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

    
2494
  /**
2495
   * Tests url() when separate domains are used for multiple languages.
2496
   */
2497
  function testLanguageDomain() {
2498
    // Add the Italian language, without protocol.
2499
    $langcode = 'it';
2500
    locale_add_language($langcode, 'Italian', 'Italian', LANGUAGE_LTR, 'it.example.com', '', TRUE, FALSE);
2501

    
2502
    // Add the French language, with protocol.
2503
    $langcode = 'fr';
2504
    locale_add_language($langcode, 'French', 'French', LANGUAGE_LTR, 'http://fr.example.com', '', TRUE, FALSE);
2505

    
2506
    // Enable language URL detection.
2507
    $negotiation = array_flip(array(LOCALE_LANGUAGE_NEGOTIATION_URL, LANGUAGE_NEGOTIATION_DEFAULT));
2508
    language_negotiation_set(LANGUAGE_TYPE_INTERFACE, $negotiation);
2509

    
2510
    variable_set('locale_language_negotiation_url_part', 1);
2511

    
2512
    global $is_https;
2513
    $languages = language_list();
2514

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

    
2519
      // Test URL in another language.
2520
      // Base path gives problems on the testbot, so $correct_link is hard-coded.
2521
      // @see UrlAlterFunctionalTest::assertUrlOutboundAlter (path.test).
2522
      $url = url('admin', array('language' => $languages[$langcode]));
2523
      $url_scheme = ($is_https) ? 'https://' : 'http://';
2524
      $correct_link = $url_scheme . $link;
2525
      $this->assertTrue($url == $correct_link, format_string('The url() function returns the right url (@url) in accordance with the chosen language', array('@url' => $url . " == " . $correct_link)));
2526

    
2527
      // Test HTTPS via options.
2528
      variable_set('https', TRUE);
2529
      $url = url('admin', array('https' => TRUE, 'language' => $languages[$langcode]));
2530
      $correct_link = 'https://' . $link;
2531
      $this->assertTrue($url == $correct_link, format_string('The url() function returns the right https url (via options) (@url) in accordance with the chosen language', array('@url' => $url . " == " . $correct_link)));
2532
      variable_set('https', FALSE);
2533

    
2534
      // Test HTTPS via current URL scheme.
2535
      $temp_https = $is_https;
2536
      $is_https = TRUE;
2537
      $url = url('admin', array('language' => $languages[$langcode]));
2538
      $correct_link = 'https://' . $link;
2539
      $this->assertTrue($url == $correct_link, 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)));
2540
      $is_https = $temp_https;
2541
    }
2542
  }
2543
}
2544

    
2545
/**
2546
 * Test that URL rewriting works as expected.
2547
 */
2548
class LocaleUrlRewritingTest extends DrupalWebTestCase {
2549
  public static function getInfo() {
2550
    return array(
2551
      'name' => 'URL rewriting',
2552
      'description' => 'Test that URL rewriting works as expected.',
2553
      'group' => 'Locale',
2554
    );
2555
  }
2556

    
2557
  function setUp() {
2558
    parent::setUp('locale');
2559

    
2560
    // Create and login user.
2561
    $this->web_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
2562
    $this->drupalLogin($this->web_user);
2563

    
2564
    // Install French language.
2565
    $edit = array();
2566
    $edit['langcode'] = 'fr';
2567
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2568

    
2569
    // Install Italian language.
2570
    $edit = array();
2571
    $edit['langcode'] = 'it';
2572
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2573

    
2574
    // Disable Italian language.
2575
    $edit = array('enabled[it]' => FALSE);
2576
    $this->drupalPost('admin/config/regional/language', $edit, t('Save configuration'));
2577

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

    
2582
    // Reset static caching.
2583
    drupal_static_reset('language_list');
2584
    drupal_static_reset('locale_url_outbound_alter');
2585
    drupal_static_reset('locale_language_url_rewrite_url');
2586
  }
2587

    
2588
  /**
2589
   * Check that disabled or non-installed languages are not considered.
2590
   */
2591
  function testUrlRewritingEdgeCases() {
2592
    // Check URL rewriting with a disabled language.
2593
    $languages = language_list();
2594
    $this->checkUrl($languages['it'], 'Path language is ignored if language is disabled.', 'URL language negotiation does not work with disabled languages');
2595

    
2596
    // Check URL rewriting with a non-installed language.
2597
    $non_existing = language_default();
2598
    $non_existing->language = $this->randomName();
2599
    $non_existing->prefix = $this->randomName();
2600
    $this->checkUrl($non_existing, 'Path language is ignored if language is not installed.', 'URL language negotiation does not work with non-installed languages');
2601
  }
2602

    
2603
  /**
2604
   * Check URL rewriting for the given language.
2605
   *
2606
   * The test is performed with a fixed URL (the default front page) to simply
2607
   * check that language prefixes are not added to it and that the prefixed URL
2608
   * is actually not working.
2609
   *
2610
   * @param string $language
2611
   *   The language prefix, e.g. 'es'.
2612
   * @param string $message1
2613
   *   Message to display in assertion that language prefixes are not added.
2614
   * @param string $message2
2615
   *   The message to display confirming prefixed URL is not working.
2616
   */
2617
  private function checkUrl($language, $message1, $message2) {
2618
    $options = array('language' => $language);
2619
    $base_path = trim(base_path(), '/');
2620
    $rewritten_path = trim(str_replace(array('?q=', $base_path), '', url('node', $options)), '/');
2621
    $segments = explode('/', $rewritten_path, 2);
2622
    $prefix = $segments[0];
2623
    $path = isset($segments[1]) ? $segments[1] : $prefix;
2624
    // If the rewritten URL has not a language prefix we pick the right one from
2625
    // the language object so we can always check the prefixed URL.
2626
    if ($this->assertNotEqual($language->prefix, $prefix, $message1)) {
2627
      $prefix = $language->prefix;
2628
    }
2629
    $this->drupalGet("$prefix/$path");
2630
    $this->assertResponse(404, $message2);
2631
  }
2632
}
2633

    
2634
/**
2635
 * Functional test for multilingual fields.
2636
 */
2637
class LocaleMultilingualFieldsFunctionalTest extends DrupalWebTestCase {
2638
  public static function getInfo() {
2639
    return array(
2640
      'name' => 'Multilingual fields',
2641
      'description' => 'Test multilingual support for fields.',
2642
      'group' => 'Locale',
2643
    );
2644
  }
2645

    
2646
  function setUp() {
2647
    parent::setUp('locale');
2648
    // Setup users.
2649
    $admin_user = $this->drupalCreateUser(array('administer languages', 'administer content types', 'access administration pages', 'create page content', 'edit own page content'));
2650
    $this->drupalLogin($admin_user);
2651

    
2652
    // Add a new language.
2653
    require_once DRUPAL_ROOT . '/includes/locale.inc';
2654
    locale_add_language('it', 'Italian', 'Italiano', LANGUAGE_LTR, '', '', TRUE, FALSE);
2655

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

    
2660
    // Set "Basic page" content type to use multilingual support.
2661
    $edit = array(
2662
      'language_content_type' => 1,
2663
    );
2664
    $this->drupalPost('admin/structure/types/manage/page', $edit, t('Save content type'));
2665
    $this->assertRaw(t('The content type %type has been updated.', array('%type' => 'Basic page')), 'Basic page content type has been updated.');
2666

    
2667
    // Make node body translatable.
2668
    $field = field_info_field('body');
2669
    $field['translatable'] = TRUE;
2670
    field_update_field($field);
2671
  }
2672

    
2673
  /**
2674
   * Test if field languages are correctly set through the node form.
2675
   */
2676
  function testMultilingualNodeForm() {
2677
    // Create "Basic page" content.
2678
    $langcode = LANGUAGE_NONE;
2679
    $title_key = "title";
2680
    $title_value = $this->randomName(8);
2681
    $body_key = "body[$langcode][0][value]";
2682
    $body_value = $this->randomName(16);
2683

    
2684
    // Create node to edit.
2685
    $edit = array();
2686
    $edit[$title_key] = $title_value;
2687
    $edit[$body_key] = $body_value;
2688
    $edit['language'] = 'en';
2689
    $this->drupalPost('node/add/page', $edit, t('Save'));
2690

    
2691
    // Check that the node exists in the database.
2692
    $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2693
    $this->assertTrue($node, 'Node found in database.');
2694

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

    
2698
    // Change node language.
2699
    $this->drupalGet("node/$node->nid/edit");
2700
    $edit = array(
2701
      $title_key => $this->randomName(8),
2702
      'language' => 'it'
2703
    );
2704
    $this->drupalPost(NULL, $edit, t('Save'));
2705
    $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2706
    $this->assertTrue($node, 'Node found in database.');
2707

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

    
2711
    // Enable content language URL detection.
2712
    language_negotiation_set(LANGUAGE_TYPE_CONTENT, array(LOCALE_LANGUAGE_NEGOTIATION_URL => 0));
2713

    
2714
    // Test multilingual field language fallback logic.
2715
    $this->drupalGet("it/node/$node->nid");
2716
    $this->assertRaw($body_value, 'Body correctly displayed using Italian as requested language');
2717

    
2718
    $this->drupalGet("node/$node->nid");
2719
    $this->assertRaw($body_value, 'Body correctly displayed using English as requested language');
2720
  }
2721

    
2722
  /*
2723
   * Test multilingual field display settings.
2724
   */
2725
  function testMultilingualDisplaySettings() {
2726
    // Create "Basic page" content.
2727
    $langcode = LANGUAGE_NONE;
2728
    $title_key = "title";
2729
    $title_value = $this->randomName(8);
2730
    $body_key = "body[$langcode][0][value]";
2731
    $body_value = $this->randomName(16);
2732

    
2733
    // Create node to edit.
2734
    $edit = array();
2735
    $edit[$title_key] = $title_value;
2736
    $edit[$body_key] = $body_value;
2737
    $edit['language'] = 'en';
2738
    $this->drupalPost('node/add/page', $edit, t('Save'));
2739

    
2740
    // Check that the node exists in the database.
2741
    $node = $this->drupalGetNodeByTitle($edit[$title_key]);
2742
    $this->assertTrue($node, 'Node found in database.');
2743

    
2744
    // Check if node body is showed.
2745
    $this->drupalGet("node/$node->nid");
2746
    $body = $this->xpath('//div[@id=:id]//div[@property="content:encoded"]/p', array(':id' => 'node-' . $node->nid));
2747
    $this->assertEqual(current($body), $node->body['en'][0]['value'], 'Node body is correctly showed.');
2748
  }
2749
}
2750

    
2751
/**
2752
 * Functional tests for comment language.
2753
 */
2754
class LocaleCommentLanguageFunctionalTest extends DrupalWebTestCase {
2755

    
2756
  public static function getInfo() {
2757
    return array(
2758
      'name' => 'Comment language',
2759
      'description' => 'Tests for comment language.',
2760
      'group' => 'Locale',
2761
    );
2762
  }
2763

    
2764
  function setUp() {
2765
    parent::setUp('locale', 'locale_test');
2766

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

    
2771
    // Add language.
2772
    $edit = array('langcode' => 'fr');
2773
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2774

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

    
2779
    // Enable content language negotiation UI.
2780
    variable_set('locale_test_content_language_type', TRUE);
2781

    
2782
    // Set interface language detection to user and content language detection
2783
    // to URL. Disable inheritance from interface language to ensure content
2784
    // language will fall back to the default language if no URL language can be
2785
    // detected.
2786
    $edit = array(
2787
      'language[enabled][locale-user]' => TRUE,
2788
      'language_content[enabled][locale-url]' => TRUE,
2789
      'language_content[enabled][locale-interface]' => FALSE,
2790
    );
2791
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2792

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

    
2798
    // Make comment body translatable.
2799
    $field = field_info_field('comment_body');
2800
    $field['translatable'] = TRUE;
2801
    field_update_field($field);
2802
    $this->assertTrue(field_is_translatable('comment', $field), 'Comment body is translatable.');
2803
  }
2804

    
2805
  /**
2806
   * Test that comment language is properly set.
2807
   */
2808
  function testCommentLanguage() {
2809
    drupal_static_reset('language_list');
2810

    
2811
    // Create two nodes, one for english and one for french, and comment each
2812
    // node using both english and french as content language by changing URL
2813
    // language prefixes. Meanwhile interface language is always French, which
2814
    // is the user language preference. This way we can ensure that node
2815
    // language and interface language do not influence comment language, as
2816
    // only content language has to.
2817
    foreach (language_list() as $node_langcode => $node_language) {
2818
      $language_none = LANGUAGE_NONE;
2819

    
2820
      // Create "Article" content.
2821
      $title = $this->randomName();
2822
      $edit = array(
2823
        "title" => $title,
2824
        "body[$language_none][0][value]" => $this->randomName(),
2825
        "language" => $node_langcode,
2826
      );
2827
      $this->drupalPost("node/add/article", $edit, t('Save'));
2828
      $node = $this->drupalGetNodeByTitle($title);
2829

    
2830
      foreach (language_list() as $langcode => $language) {
2831
        // Post a comment with content language $langcode.
2832
        $prefix = empty($language->prefix) ? '' : $language->prefix . '/';
2833
        $comment_values[$node_langcode][$langcode] = $this->randomName();
2834
        // Initially field form widgets have no language.
2835
        $edit = array(
2836
          'subject' => $this->randomName(),
2837
          "comment_body[$language_none][0][value]" => $comment_values[$node_langcode][$langcode],
2838
        );
2839
        $this->drupalPost("{$prefix}node/{$node->nid}", $edit, t('Preview'));
2840
        // After the first submit the submitted entity language is taken into
2841
        // account.
2842
        $edit = array(
2843
          'subject' => $edit['subject'],
2844
          "comment_body[$langcode][0][value]" => $comment_values[$node_langcode][$langcode],
2845
        );
2846
        $this->drupalPost(NULL, $edit, t('Save'));
2847

    
2848
        // Check that comment language matches the current content language.
2849
        $cid = db_select('comment', 'c')
2850
          ->fields('c', array('cid'))
2851
          ->condition('nid', $node->nid)
2852
          ->orderBy('cid', 'DESC')
2853
          ->range(0, 1)
2854
          ->execute()
2855
          ->fetchField();
2856
        $comment = comment_load($cid);
2857
        $comment_langcode = entity_language('comment', $comment);
2858
        $args = array('%node_language' => $node_langcode, '%comment_language' => $comment_langcode, '%langcode' => $langcode);
2859
        $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));
2860
        $this->assertEqual($comment->comment_body[$langcode][0]['value'], $comment_values[$node_langcode][$langcode], 'Comment body correctly stored.');
2861
      }
2862
    }
2863

    
2864
    // Check that comment bodies appear in the administration UI.
2865
    $this->drupalGet('admin/content/comment');
2866
    foreach ($comment_values as $node_values) {
2867
      foreach ($node_values as $value) {
2868
        $this->assertRaw($value);
2869
      }
2870
    }
2871
  }
2872

    
2873
}
2874

    
2875
/**
2876
 * Functional tests for localizing date formats.
2877
 */
2878
class LocaleDateFormatsFunctionalTest extends DrupalWebTestCase {
2879

    
2880
  public static function getInfo() {
2881
    return array(
2882
      'name' => 'Localize date formats',
2883
      'description' => 'Tests for the localization of date formats.',
2884
      'group' => 'Locale',
2885
    );
2886
  }
2887

    
2888
  function setUp() {
2889
    parent::setUp('locale');
2890

    
2891
    // Create and login user.
2892
    $admin_user = $this->drupalCreateUser(array('administer site configuration', 'administer languages', 'access administration pages', 'create article content'));
2893
    $this->drupalLogin($admin_user);
2894
  }
2895

    
2896
  /**
2897
   * Functional tests for localizing date formats.
2898
   */
2899
  function testLocalizeDateFormats() {
2900
    // Add language.
2901
    $edit = array(
2902
      'langcode' => 'fr',
2903
    );
2904
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
2905

    
2906
    // Set language negotiation.
2907
    $language_type = LANGUAGE_TYPE_INTERFACE;
2908
    $edit = array(
2909
      "{$language_type}[enabled][locale-url]" => TRUE,
2910
    );
2911
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2912

    
2913
    // Configure date formats.
2914
    $this->drupalGet('admin/config/regional/date-time/locale');
2915
    $this->assertText('Français', 'Configured languages appear.');
2916
    $edit = array(
2917
      'date_format_long' => 'd.m.Y - H:i',
2918
      'date_format_medium' => 'd.m.Y - H:i',
2919
      'date_format_short' => 'd.m.Y - H:i',
2920
    );
2921
    $this->drupalPost('admin/config/regional/date-time/locale/fr/edit', $edit, t('Save configuration'));
2922
    $this->assertText(t('Configuration saved.'), 'French date formats updated.');
2923
    $edit = array(
2924
      'date_format_long' => 'j M Y - g:ia',
2925
      'date_format_medium' => 'j M Y - g:ia',
2926
      'date_format_short' => 'j M Y - g:ia',
2927
    );
2928
    $this->drupalPost('admin/config/regional/date-time/locale/en/edit', $edit, t('Save configuration'));
2929
    $this->assertText(t('Configuration saved.'), 'English date formats updated.');
2930

    
2931
    // Create node content.
2932
    $node = $this->drupalCreateNode(array('type' => 'article'));
2933

    
2934
    // Configure format for the node posted date changes with the language.
2935
    $this->drupalGet('node/' . $node->nid);
2936
    $english_date = format_date($node->created, 'custom', 'j M Y');
2937
    $this->assertText($english_date, 'English date format appears');
2938
    $this->drupalGet('fr/node/' . $node->nid);
2939
    $french_date = format_date($node->created, 'custom', 'd.m.Y');
2940
    $this->assertText($french_date, 'French date format appears');
2941
  }
2942
}
2943

    
2944
/**
2945
 * Functional test for language types/negotiation info.
2946
 */
2947
class LocaleLanguageNegotiationInfoFunctionalTest extends DrupalWebTestCase {
2948

    
2949
  public static function getInfo() {
2950
    return array(
2951
      'name' => 'Language negotiation info',
2952
      'description' => 'Tests alterations to language types/negotiation info.',
2953
      'group' => 'Locale',
2954
    );
2955
  }
2956

    
2957
  function setUp() {
2958
    parent::setUp('locale');
2959
    require_once DRUPAL_ROOT .'/includes/language.inc';
2960
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'view the administration theme'));
2961
    $this->drupalLogin($admin_user);
2962
    $this->drupalPost('admin/config/regional/language/add', array('langcode' => 'it'), t('Add language'));
2963
  }
2964

    
2965
  /**
2966
   * Tests alterations to language types/negotiation info.
2967
   */
2968
  function testInfoAlterations() {
2969
    // Enable language type/negotiation info alterations.
2970
    variable_set('locale_test_language_types', TRUE);
2971
    variable_set('locale_test_language_negotiation_info', TRUE);
2972
    $this->languageNegotiationUpdate();
2973

    
2974
    // Check that fixed language types are properly configured without the need
2975
    // of saving the language negotiation settings.
2976
    $this->checkFixedLanguageTypes();
2977

    
2978
    // Make the content language type configurable by updating the language
2979
    // negotiation settings with the proper flag enabled.
2980
    variable_set('locale_test_content_language_type', TRUE);
2981
    $this->languageNegotiationUpdate();
2982
    $type = LANGUAGE_TYPE_CONTENT;
2983
    $language_types = variable_get('language_types', drupal_language_types());
2984
    $this->assertTrue($language_types[$type], 'Content language type is configurable.');
2985

    
2986
    // Enable some core and custom language providers. The test language type is
2987
    // supposed to be configurable.
2988
    $test_type = 'test_language_type';
2989
    $provider = LOCALE_LANGUAGE_NEGOTIATION_INTERFACE;
2990
    $test_provider = 'test_language_provider';
2991
    $form_field = $type . '[enabled]['. $provider .']';
2992
    $edit = array(
2993
      $form_field => TRUE,
2994
      $type . '[enabled][' . $test_provider . ']' => TRUE,
2995
      $test_type . '[enabled][' . $test_provider . ']' => TRUE,
2996
    );
2997
    $this->drupalPost('admin/config/regional/language/configure', $edit, t('Save settings'));
2998

    
2999
    // Remove the interface language provider by updating the language
3000
    // negotiation settings with the proper flag enabled.
3001
    variable_set('locale_test_language_negotiation_info_alter', TRUE);
3002
    $this->languageNegotiationUpdate();
3003
    $negotiation = variable_get("language_negotiation_$type", array());
3004
    $this->assertFalse(isset($negotiation[$provider]), 'Interface language provider removed from the stored settings.');
3005
    $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, 'Interface language provider unavailable.');
3006

    
3007
    // Check that type-specific language providers can be assigned only to the
3008
    // corresponding language types.
3009
    foreach (language_types_configurable() as $type) {
3010
      $form_field = $type . '[enabled][test_language_provider_ts]';
3011
      if ($type == $test_type) {
3012
        $this->assertFieldByXPath("//input[@name=\"$form_field\"]", NULL, format_string('Type-specific test language provider available for %type.', array('%type' => $type)));
3013
      }
3014
      else {
3015
        $this->assertNoFieldByXPath("//input[@name=\"$form_field\"]", NULL, format_string('Type-specific test language provider unavailable for %type.', array('%type' => $type)));
3016
      }
3017
    }
3018

    
3019
    // Check language negotiation results.
3020
    $this->drupalGet('');
3021
    $last = variable_get('locale_test_language_negotiation_last', array());
3022
    foreach (language_types() as $type) {
3023
      $langcode = $last[$type];
3024
      $value = $type == LANGUAGE_TYPE_CONTENT || strpos($type, 'test') !== FALSE ? 'it' : 'en';
3025
      $this->assertEqual($langcode, $value, format_string('The negotiated language for %type is %language', array('%type' => $type, '%language' => $langcode)));
3026
    }
3027

    
3028
    // Disable locale_test and check that everything is set back to the original
3029
    // status.
3030
    $this->languageNegotiationUpdate('disable');
3031

    
3032
    // Check that only the core language types are available.
3033
    foreach (language_types() as $type) {
3034
      $this->assertTrue(strpos($type, 'test') === FALSE, format_string('The %type language is still available', array('%type' => $type)));
3035
    }
3036

    
3037
    // Check that fixed language types are properly configured, even those
3038
    // previously set to configurable.
3039
    $this->checkFixedLanguageTypes();
3040

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

    
3046
    // Check that configuration page presents the correct options and settings.
3047
    $this->assertNoRaw(t('Test language detection'), 'No test language type configuration available.');
3048
    $this->assertNoRaw(t('This is a test language provider'), 'No test language provider available.');
3049
  }
3050

    
3051
  /**
3052
   * Update language types/negotiation information.
3053
   *
3054
   * Manually invoke locale_modules_enabled()/locale_modules_disabled() since
3055
   * they would not be invoked after enabling/disabling locale_test the first
3056
   * time.
3057
   */
3058
  private function languageNegotiationUpdate($op = 'enable') {
3059
    static $last_op = NULL;
3060
    $modules = array('locale_test');
3061

    
3062
    // Enable/disable locale_test only if we did not already before.
3063
    if ($last_op != $op) {
3064
      $function = "module_{$op}";
3065
      $function($modules);
3066
      // Reset hook implementation cache.
3067
      module_implements(NULL, FALSE, TRUE);
3068
    }
3069

    
3070
    drupal_static_reset('language_types_info');
3071
    drupal_static_reset('language_negotiation_info');
3072
    $function = "locale_modules_{$op}d";
3073
    if (function_exists($function)) {
3074
      $function($modules);
3075
    }
3076

    
3077
    $this->drupalGet('admin/config/regional/language/configure');
3078
  }
3079

    
3080
  /**
3081
   * Check that language negotiation for fixed types matches the stored one.
3082
   */
3083
  private function checkFixedLanguageTypes() {
3084
    drupal_static_reset('language_types_info');
3085
    foreach (language_types_info() as $type => $info) {
3086
      if (isset($info['fixed'])) {
3087
        $negotiation = variable_get("language_negotiation_$type", array());
3088
        $equal = count($info['fixed']) == count($negotiation);
3089
        while ($equal && list($id) = each($negotiation)) {
3090
          list(, $info_id) = each($info['fixed']);
3091
          $equal = $info_id == $id;
3092
        }
3093
        $this->assertTrue($equal, format_string('language negotiation for %type is properly set up', array('%type' => $type)));
3094
      }
3095
    }
3096
  }
3097
}
3098

    
3099
/**
3100
 * Functional tests for CSS alter functions.
3101
 */
3102
class LocaleCSSAlterTest extends DrupalWebTestCase {
3103
  public static function getInfo() {
3104
    return array(
3105
      'name' => 'CSS altering',
3106
      'description' => 'Test CSS alter functions.',
3107
      'group' => 'Locale',
3108
    );
3109
  }
3110

    
3111
  function setUp() {
3112
    parent::setUp('locale');
3113
  }
3114

    
3115
  /**
3116
   * Verifies that -rtl.css file is added directly after LTR .css file.
3117
   */
3118
  function testCSSFilesOrderInRTLMode() {
3119
    global $base_url;
3120

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

    
3124
    // Log in as admin.
3125
    $this->drupalLogin($admin_user);
3126

    
3127
    // Install the Arabic language (which is RTL) and configure as the default.
3128
    $edit = array();
3129
    $edit['langcode'] = 'ar';
3130
    $this->drupalPost('admin/config/regional/language/add', $edit, t('Add language'));
3131

    
3132
    $edit = array();
3133
    $edit['site_default'] = 'ar';
3134
    $this->drupalPost(NULL, $edit, t('Save configuration'));
3135

    
3136
    // Verify that the -rtl.css file is added directly after LTR file.
3137
    $this->drupalGet('');
3138
    $query_string = '?' . variable_get('css_js_query_string', '0');
3139
    $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.');
3140
    $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.');
3141
    $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.');
3142
  }
3143
}