Projet

Général

Profil

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

root / htmltest / modules / locale / locale.test @ 85ad3d82

1 85ad3d82 Assos Assos
<?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 the the languages that have a different
1206
    // form for the full name and the abbreviated name for the month May.
1207
    return <<< EOF
1208
msgid ""
1209
msgstr ""
1210
"Project-Id-Version: Drupal 7\\n"
1211
"MIME-Version: 1.0\\n"
1212
"Content-Type: text/plain; charset=UTF-8\\n"
1213
"Content-Transfer-Encoding: 8bit\\n"
1214
"Plural-Forms: nplurals=3; plural=n%10==1 && n%100!=11 ? 0 : n%10>=2 && n%10<=4 && (n%100<10 || n%100>=20) ? 1 : 2;\\n"
1215
1216
msgctxt "Long month name"
1217
msgid "May"
1218
msgstr "Svibanj"
1219
1220
msgid "May"
1221
msgstr "Svi."
1222
EOF;
1223
  }
1224
1225
  /**
1226
   * Helper function that returns a .po file with an empty last item.
1227
   */
1228
  function getPoFileWithEmptyMsgstr() {
1229
    return <<< EOF
1230
msgid ""
1231
msgstr ""
1232
"Project-Id-Version: Drupal 7\\n"
1233
"MIME-Version: 1.0\\n"
1234
"Content-Type: text/plain; charset=UTF-8\\n"
1235
"Content-Transfer-Encoding: 8bit\\n"
1236
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1237
1238
msgid "Operations"
1239
msgstr ""
1240
1241
EOF;
1242
  }
1243
  /**
1244
   * Helper function that returns a .po file with an empty last item.
1245
   */
1246
  function getPoFileWithMsgstr() {
1247
    return <<< EOF
1248
msgid ""
1249
msgstr ""
1250
"Project-Id-Version: Drupal 7\\n"
1251
"MIME-Version: 1.0\\n"
1252
"Content-Type: text/plain; charset=UTF-8\\n"
1253
"Content-Transfer-Encoding: 8bit\\n"
1254
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"
1255
1256
msgid "Operations"
1257
msgstr "Műveletek"
1258
1259
msgid "Will not appear in Drupal core, so we can ensure the test passes"
1260
msgstr ""
1261
1262
EOF;
1263
  }
1264
1265
1266
  /**
1267
   * Returns a .po file with a missing plural formula.
1268
   */
1269
  function getPoFileWithMissingPlural() {
1270
    return <<< EOF
1271
msgid ""
1272
msgstr ""
1273
"Project-Id-Version: Drupal 7\\n"
1274
"MIME-Version: 1.0\\n"
1275
"Content-Type: text/plain; charset=UTF-8\\n"
1276
"Content-Transfer-Encoding: 8bit\\n"
1277
1278
msgid "Monday"
1279
msgstr "Ponedjeljak"
1280
EOF;
1281
  }
1282
1283
  /**
1284
   * Returns a .po file with a broken plural formula.
1285
   */
1286
  function getPoFileWithBrokenPlural() {
1287
    return <<< EOF
1288
msgid ""
1289
msgstr ""
1290
"Project-Id-Version: Drupal 7\\n"
1291
"MIME-Version: 1.0\\n"
1292
"Content-Type: text/plain; charset=UTF-8\\n"
1293
"Content-Transfer-Encoding: 8bit\\n"
1294
"Plural-Forms: broken, will not parse\\n"
1295
1296
msgid "Monday"
1297
msgstr "lundi"
1298
EOF;
1299
  }
1300
1301
}
1302
1303
/**
1304
 * Functional tests for the export of translation files.
1305
 */
1306
class LocaleExportFunctionalTest extends DrupalWebTestCase {
1307
  public static function getInfo() {
1308
    return array(
1309
      'name' => 'Translation export',
1310
      'description' => 'Tests the exportation of locale files.',
1311
      'group' => 'Locale',
1312
    );
1313
  }
1314
1315
  /**
1316
   * A user able to create languages and export translations.
1317
   */
1318
  protected $admin_user = NULL;
1319
1320
  function setUp() {
1321
    parent::setUp('locale', 'locale_test');
1322
1323
    $this->admin_user = $this->drupalCreateUser(array('administer languages', 'translate interface', 'access administration pages'));
1324
    $this->drupalLogin($this->admin_user);
1325
  }
1326
1327
  /**
1328
   * Test exportation of translations.
1329
   */
1330
  function testExportTranslation() {
1331
    // First import some known translations.
1332
    // This will also automatically enable the 'fr' language.
1333
    $name = tempnam('temporary://', "po_") . '.po';
1334
    file_put_contents($name, $this->getPoFile());
1335
    $this->drupalPost('admin/config/regional/translate/import', array(
1336
      'langcode' => 'fr',
1337
      'files[file]' => $name,
1338
    ), t('Import'));
1339
    drupal_unlink($name);
1340
1341
    // Get the French translations.
1342
    $this->drupalPost('admin/config/regional/translate/export', array(
1343
      'langcode' => 'fr',
1344
    ), t('Export'));
1345
1346
    // Ensure we have a translation file.
1347
    $this->assertRaw('# French translation of Drupal', '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
}