locale.test 52.3 KB
Newer Older
1
2
3
<?php
// $Id$

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

21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163

/**
 * Functional tests for the language configuration forms.
 */
class LocaleConfigurationTest extends DrupalWebTestCase {
  function getInfo() {
    return array(
      'name' => t('Language configuration'),
      'description' => t('Adds a new locale and tests changing its status and the default language.'),
      'group' => t('Locale'),
    );
  }

  function setUp() {
    parent::setUp('locale');
  }

  /**
   * Functional tests for adding, editing and deleting languages.
   */
  function testLanguageConfiguration() {
    global $base_url;

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

    // Add predefined language.
    $edit = array(
      'langcode' => 'fr',
    );
    $this->drupalPost('admin/settings/language/add', $edit, t('Add language'));
    $this->assertText('fr', t('Language added successfully.'));
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));

    // Add custom language.
    // Code for the language.
    $langcode = $this->randomName(6, 'si-');
    // The English name for the language.
    $name = $this->randomName(16);
    // The native name for the language.
    $native = $this->randomName(16);
    // The domain prefix.
    $prefix = strtolower(str_replace('si-', '', $langcode));
    $edit = array(
      'langcode' => $langcode,
      'name' => $name,
      'native' => $native,
      'prefix' => $prefix,
      'direction' => '0',
    );
    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));
    $this->assertText($langcode, t('Language code found.'));
    $this->assertText($name, t('Name found.'));
    $this->assertText($native, t('Native found.'));
    $this->assertText($native, t('Test language added.'));

    // Check if we can change the default language.
    $path = 'admin/settings/language';
    $this->drupalGet($path);
    // Set up the raw HTML strings we need to search for.
    $elements = $this->xpath('//input[@id="edit-site-default-en"]');
    $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('English is the default language.'));
    // Change the default language.
    $edit = array(
      'site_default' => $langcode,
    );
    $this->drupalPost($path, $edit, t('Save configuration'));
    $elements = $this->xpath('//input[@id="edit-site-default-en"]');
    $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), t('Default language updated.'));
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));

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

    // Check if we can disable a language.
    $edit = array(
      'enabled[en]' => FALSE,
    );
    $this->drupalPost($path, $edit, t('Save configuration'));
    $elements = $this->xpath('//input[@id="edit-enabled-en"]');
    $this->assertTrue(isset($elements[0]) && empty($elements[0]['checked']), t('Language disabled.'));

    // Set disabled language to be the default and ensure it is re-enabled.
    $edit = array(
      'site_default' => 'en',
    );
    $this->drupalPost($path, $edit, t('Save configuration'));
    $elements = $this->xpath('//input[@id="edit-enabled-en"]');
    $this->assertTrue(isset($elements[0]) && !empty($elements[0]['checked']), t('Default language re-enabled.'));

    // Ensure 'edit' link works.
    $this->clickLink(t('edit'));
    $this->assertTitle(t('Edit language | Drupal'), t('Page title is "Edit language".'));
    // Edit a language.
    $path = 'admin/settings/language/edit/' . $langcode;
    $name = $this->randomName(16);
    $edit = array(
      'name' => $name,
    );
    $this->drupalPost($path, $edit, t('Save language'));
    $this->assertRaw($name, t('The language has been updated.'));
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));

    // Ensure 'delete' link works.
    $path = 'admin/settings/language';
    $this->drupalGet($path);
    $this->clickLink(t('delete'));
    $this->assertText(t('Are you sure you want to delete the language'), t('"delete" link is correct.'));
    // Delete the language.
    $path = 'admin/settings/language/delete/' . $langcode;
    $this->drupalGet($path);
    // First test the 'cancel' link.
    $this->clickLink(t('Cancel'));
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));
    $this->assertRaw($name, t('The language was not deleted.'));
    // Delete the language for real. This a confirm form, we do not need any
    // fields changed.
    $this->drupalPost($path, array(), t('Delete'));
    // We need raw here because %locale will add HTML.
    $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), t('The test language has been removed.'));
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));
    // Reload to remove $name.
    $this->drupalGet($path);
    $this->assertNoText($langcode, t('Language code not found.'));
    $this->assertNoText($name, t('Name not found.'));
    $this->assertNoText($native, t('Native not found.'));

    // Ensure we can't delete the English language.
    $path = 'admin/settings/language/delete/en';
    $this->drupalGet($path);
    $this->assertEqual($this->getUrl(), url('admin/settings/language', array('absolute' => TRUE)), t('Correct page redirection.'));
    $this->assertText(t('The English language cannot be deleted.'), t('Failed to delete English language.'));

    $this->drupalLogout();
  }

}

164
165
166
167
/**
 * Functional test for string translation and validation.
 */
class LocaleTranslationFunctionalTest extends DrupalWebTestCase {
168
169
  function getInfo() {
    return array(
170
171
172
      'name' => t('String translate, search and validate'),
      'description' => t('Adds a new locale and translates its name. Checks the validation of translation strings and search results.'),
      'group' => t('Locale'),
173
174
175
176
177
178
179
    );
  }

  function setUp() {
    parent::setUp('locale');
  }

180
181
182
183
  /**
   * Adds a language and tests string translation by users with the appropriate permissions.
   */
  function testStringTranslation() {
184
185
186
187
188
189
190
    global $base_url;

    // User to add and remove language.
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages'));
    // User to translate and delete string.
    $translate_user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
    // Code for the language.
191
    $langcode = $this->randomName(6, 'si-');
192
193
194
195
    // The English name for the language. This will be translated.
    $name = $this->randomName(16);
    // The native name for the language.
    $native = $this->randomName(16);
196
    // The domain prefix.
197
198
199
200
201
202
203
    $prefix = strtolower(str_replace('si-', '', $langcode));
    // This is the language indicator on the translation search screen for
    // untranslated strings. Copied straight from locale.inc.
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
    // This will be the translation of $name.
    $translation = $this->randomName(16);

204
    // Add custom language.
205
    $this->drupalLogin($admin_user);
206
    $edit = array(
207
208
209
210
211
212
213
214
215
216
217
      'langcode' => $langcode,
      'name' => $name,
      'native' => $native,
      'prefix' => $prefix,
      'direction' => '0',
    );
    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
    // Add string.
    t($name, array(), $langcode);
    // Reset locale cache.
    locale(NULL, NULL, TRUE);
218
219
220
    $this->assertText($langcode, t('Language code found.'));
    $this->assertText($name, t('Name found.'));
    $this->assertText($native, t('Native found.'));
221
222
    // No t() here, we do not want to add this string to the database and it's
    // surely not translated yet.
223
    $this->assertText($native, t('Test language added.'));
224
    $this->drupalLogout();
225
226
227

    // Search for the name and translate it.
    $this->drupalLogin($translate_user);
228
    $search = array(
229
230
231
232
233
      'string' => $name,
      'language' => 'all',
      'translation' => 'all',
      'group' => 'all',
    );
234
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
235
    // assertText() seems to remove the input field where $name always could be
236
237
    // found, so this is not a false assert. See how assertNoText succeeds
    // later.
238
239
240
    $this->assertText($name, t('Search found the name.'));
    $this->assertRaw($language_indicator, t('Name is untranslated.'));
    // Assume this is the only result, given the random name.
241
242
    $this->clickLink(t('edit'));
    // We save the lid from the path.
243
244
245
    $matches = array();
    preg_match('!admin/build/translate/edit/(\d)+!', $this->getUrl(), $matches);
    $lid = $matches[1];
246
    // No t() here, it's surely not translated yet.
247
248
    $this->assertText($name, t('name found on edit screen.'));
    $edit = array(
249
250
251
      "translations[$langcode]" => $translation,
    );
    $this->drupalPost(NULL, $edit, t('Save translations'));
252
253
254
    $this->assertText(t('The string has been saved.'), t('The string has been saved.'));
    $this->assertEqual($this->getUrl(), url('admin/build/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
    $this->assertTrue($name != $translation && t($name, array(), $langcode) == $translation, t('t() works.'));
255
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
256
    // The indicator should not be here.
257
258
259
260
261
262
263
264
    $this->assertNoRaw($language_indicator, t('String is translated.'));

    // Try to edit a non-existent string and ensure we're redirected correctly.
    // Assuming we don't have 999,999 strings already.
    $random_lid = 999999;
    $this->drupalGet('admin/build/translate/edit/' . $random_lid);
    $this->assertText(t('String not found'), t('String not found.'));
    $this->assertEqual($this->getUrl(), url('admin/build/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
265
    $this->drupalLogout();
266

267
    // Delete the language.
268
    $this->drupalLogin($admin_user);
269
    $path = 'admin/settings/language/delete/' . $langcode;
270
271
272
    // This a confirm form, we do not need any fields changed.
    $this->drupalPost($path, array(), t('Delete'));
    // We need raw here because %locale will add HTML.
273
    $this->assertRaw(t('The language %locale has been removed.', array('%locale' => $name)), t('The test language has been removed.'));
274
275
    // Reload to remove $name.
    $this->drupalGet($path);
276
277
278
    $this->assertNoText($langcode, t('Language code not found.'));
    $this->assertNoText($name, t('Name not found.'));
    $this->assertNoText($native, t('Native not found.'));
279
    $this->drupalLogout();
280

281
    // Delete the string.
282
    $this->drupalLogin($translate_user);
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
    $search = array(
      'string' => $name,
      'language' => 'all',
      'translation' => 'all',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    // Assume this is the only result, given the random name.
    $this->clickLink(t('delete'));
    $this->assertText(t('Are you sure you want to delete the string'), t('"delete" link is correct.'));
    // Delete the string.
    $path = 'admin/build/translate/delete/' . $lid;
    $this->drupalGet($path);
    // First test the 'cancel' link.
    $this->clickLink(t('Cancel'));
    $this->assertEqual($this->getUrl(), url('admin/build/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
    $this->assertRaw($name, t('The string was not deleted.'));
    // Delete the name string.
301
    $this->drupalPost('admin/build/translate/delete/' . $lid, array(), t('Delete'));
302
303
    $this->assertText(t('The string has been removed.'), t('The string has been removed message.'));
    $this->assertEqual($this->getUrl(), url('admin/build/translate/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
304
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
305
    $this->assertNoText($name, t('Search now can not find the name.'));
306
  }
307

308
309
310
311
  /**
   * Tests the validation of the translation input.
   */
  function testStringValidation() {
312
313
    global $base_url;

314
    // User to add language and strings.
315
316
    $admin_user = $this->drupalCreateUser(array('administer languages', 'access administration pages', 'translate interface'));
    $this->drupalLogin($admin_user);
317
    $langcode = $this->randomName(6, 'si-');
318
319
320
321
    // The English name for the language. This will be translated.
    $name = $this->randomName(16);
    // The native name for the language.
    $native = $this->randomName(16);
322
    // The domain prefix.
323
324
325
326
327
328
329
330
331
332
333
334
335
336
    $prefix = strtolower(str_replace('si-', '', $langcode));
    // This is the language indicator on the translation search screen for
    // untranslated strings. Copied straight from locale.inc.
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
    // These will be the invalid translations of $name.
    $key = $this->randomName(16);
    $bad_translations[$key] = "<script>alert('xss');</script>" . $key;
    $key = $this->randomName(16);
    $bad_translations[$key] = '<img SRC="javascript:alert(\'xss\');">' . $key;
    $key = $this->randomName(16);
    $bad_translations[$key] = '<<SCRIPT>alert("xss");//<</SCRIPT>' . $key;
    $key = $this->randomName(16);
    $bad_translations[$key] ="<BODY ONLOAD=alert('xss')>" . $key;

337
338
    // Add custom language.
    $edit = array(
339
340
341
342
343
344
345
346
347
348
      'langcode' => $langcode,
      'name' => $name,
      'native' => $native,
      'prefix' => $prefix,
      'direction' => '0',
    );
    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
    // Add string.
    t($name, array(), $langcode);
    // Reset locale cache.
349
    $search = array(
350
351
352
353
354
      'string' => $name,
      'language' => 'all',
      'translation' => 'all',
      'group' => 'all',
    );
355
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
356
    // Find the edit path.
357
    $content = $this->drupalGetContent();
358
    $this->assertTrue(preg_match('@(admin/build/translate/edit/[0-9]+)@', $content, $matches), t('Found the edit path.'));
359
360
    $path = $matches[0];
    foreach ($bad_translations as $key => $translation) {
361
      $edit = array(
362
363
364
365
366
367
368
369
370
        "translations[$langcode]" => $translation,
      );
      $this->drupalPost($path, $edit, t('Save translations'));
      // Check for a form error on the textarea.
      $form_class = $this->xpath('//form[@id="locale-translate-edit-form"]//textarea/@class');
      $this->assertNotIdentical(FALSE, strpos($form_class[0], 'error'), t('The string was rejected as unsafe.'));
      $this->assertNoText(t('The string has been saved.'), t('The string was not saved.'));
    }
  }
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524

  /**
   * Tests translation search form.
   */
  function testStringSearch() {
    global $base_url;

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

    // Code for the language.
    $langcode = $this->randomName(6, 'si-');
    // The English name for the language. This will be translated.
    $name = $this->randomName(16);
    // The native name for the language.
    $native = $this->randomName(16);
    // The domain prefix.
    $prefix = strtolower(str_replace('si-', '', $langcode));
    // This is the language indicator on the translation search screen for
    // untranslated strings. Copied straight from locale.inc.
    $language_indicator = "<em class=\"locale-untranslated\">$langcode</em> ";
    // This will be the translation of $name.
    $translation = $this->randomName(16);

    // Add custom language.
    $this->drupalLogin($admin_user);
    $edit = array(
      'langcode' => $langcode,
      'name' => $name,
      'native' => $native,
      'prefix' => $prefix,
      'direction' => '0',
    );
    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));
    // Add string.
    t($name, array(), $langcode);
    // Reset locale cache.
    locale(NULL, NULL, TRUE);
    $this->drupalLogout();

    // Search for the name.
    $this->drupalLogin($translate_user);
    $search = array(
      'string' => $name,
      'language' => 'all',
      'translation' => 'all',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    // assertText() seems to remove the input field where $name always could be
    // found, so this is not a false assert. See how assertNoText succeeds
    // later.
    $this->assertText($name, t('Search found the string.'));

    // Ensure untranslated string doesn't appear if searching on 'only
    // translated strings'.
    $search = array(
      'string' => $name,
      'language' => 'all',
      'translation' => 'translated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertText(t('No strings found for your search.'), t("Search didn't find the string."));

    // Ensure untranslated string appears if searching on 'only untranslated
    // strings'.
    $search = array(
      'string' => $name,
      'language' => 'all',
      'translation' => 'untranslated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertNoText(t('No strings found for your search.'), t('Search found the string.'));

    // Add translation.
    // Assume this is the only result, given the random name.
    $this->clickLink(t('edit'));
    // We save the lid from the path.
    $matches = array();
    preg_match('!admin/build/translate/edit/(\d)+!', $this->getUrl(), $matches);
    $lid = $matches[1];
    $edit = array(
      "translations[$langcode]" => $translation,
    );
    $this->drupalPost(NULL, $edit, t('Save translations'));

    // Ensure translated string does appear if searching on 'only
    // translated strings'.
    $search = array(
      'string' => $translation,
      'language' => 'all',
      'translation' => 'translated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertNoText(t('No strings found for your search.'), t('Search found the translation.'));

    // Ensure translated source string doesn't appear if searching on 'only
    // untranslated strings'.
    $search = array(
      'string' => $name,
      'language' => 'all',
      'translation' => 'untranslated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertText(t('No strings found for your search.'), t("Search didn't find the source string."));

    // Ensure translated string doesn't appear if searching on 'only
    // untranslated strings'.
    $search = array(
      'string' => $translation,
      'language' => 'all',
      'translation' => 'untranslated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertText(t('No strings found for your search.'), t("Search didn't find the translation."));

    // Ensure translated string does appear if searching on the custom language.
    $search = array(
      'string' => $translation,
      'language' => $langcode,
      'translation' => 'all',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertNoText(t('No strings found for your search.'), t('Search found the translation.'));

    // Ensure translated string doesn't appear if searching on English.
    $search = array(
      'string' => $translation,
      'language' => 'en',
      'translation' => 'all',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertText(t('No strings found for your search.'), t("Search didn't find the translation."));

    // Search for a string that isn't in the system.
    $unavailable_string = $this->randomName(16);
    $search = array(
      'string' => $unavailable_string,
      'language' => 'all',
      'translation' => 'all',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertText(t('No strings found for your search.'), t("Search didn't find the invalid string."));
  }
525
}
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544

/**
 * Functional tests for the import of translation files.
 */
class LocaleImportFunctionalTest extends DrupalWebTestCase {
  function getInfo() {
    return array(
      'name' => t('Translation import'),
      'description' => t('Tests the importation of locale files.'),
      'group' => t('Locale'),
    );
  }

  /**
   * A user able to create languages and import translations.
   */
  protected $admin_user = NULL;

  function setUp() {
545
    parent::setUp('locale', 'locale_test');
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563

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

  /**
   * Test importation of standalone .po files.
   */
  function testStandalonePoFile() {
    // Try importing a .po file.
    $name = tempnam(file_directory_temp(), "po_");
    file_put_contents($name, $this->getPoFile());
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
    ), t('Import'));
    unlink($name);

564
565
566
567
568
    // The import should automatically create the corresponding language.
    $this->assertRaw(t('The language %language has been created.', array('%language' => 'French')), t('The language has been automatically created.'));

    // The import should have created 7 strings.
    $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' => 7, '%update' => 0, '%delete' => 0)), t('The translation file was successfully imported.'));
569

570
571
    // Ensure we were redirected correctly.
    $this->assertEqual($this->getUrl(), url('admin/build/translate', array('absolute' => TRUE)), t('Correct page redirection.'));
572

573
    // Try importing a .po file with invalid tags in the default text group.
574
575
576
577
578
579
580
    $name = tempnam(file_directory_temp(), "po_");
    file_put_contents($name, $this->getBadPoFile());
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
    ), t('Import'));
    unlink($name);
581
    // The import should have created 1 string and rejected 2.
582
583
584
585
    $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)), t('The translation file was successfully imported.'));
    $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.');
    $this->assertRaw($skip_message, t('Unsafe strings were skipped.'));

586
587
588
589
590
591
592
593
594
    // Try importing a .po file with invalid tags in a non default text group.
    $name = tempnam(file_directory_temp(), "po_");
    file_put_contents($name, $this->getBadPoFile());
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
      'group' => 'custom',
    ), t('Import'));
    unlink($name);
595
    // The import should have created 3 strings.
596
    $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)), t('The translation file was successfully imported.'));
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700

    // Try importing a .po file which doesn't exist.
    $name = $this->randomName(16);
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
      'group' => 'custom',
    ), t('Import'));
    $this->assertEqual($this->getUrl(), url('admin/build/translate/import', array('absolute' => TRUE)), t('Correct page redirection.'));
    $this->assertText(t('File to import not found.'), t('File to import not found message.'));

    // Try importing a .po file with overriding strings, and ensure existing
    // strings are kept.
    $name = tempnam(file_directory_temp(), "po_");
    file_put_contents($name, $this->getOverwritePoFile());
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
      'mode' => 1, // Existing strings are kept, only new strings are added.
    ), t('Import'));
    unlink($name);
    // The import should have created 1 string.
    $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)), t('The translation file was successfully imported.'));
    // Ensure string wasn't overwritten.
    $search = array(
      'string' => 'Montag',
      'language' => 'fr',
      'translation' => 'translated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertText(t('No strings found for your search.'), t('String not overwritten by imported string.'));

    // Try importing a .po file with overriding strings, and ensure existing
    // strings are overwritten.
    $name = tempnam(file_directory_temp(), "po_");
    file_put_contents($name, $this->getOverwritePoFile());
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
      'mode' => 0, // Strings in the uploaded file replace existing ones, new ones are added.
    ), t('Import'));
    unlink($name);
    // The import should have updated 2 strings.
    $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)), t('The translation file was successfully imported.'));
    // Ensure string was overwritten.
    $search = array(
      'string' => 'Montag',
      'language' => 'fr',
      'translation' => 'translated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertNoText(t('No strings found for your search.'), t('String overwritten by imported string.'));
  }

  /**
   * Test automatic importation of a module's translation files when a language
   * is enabled.
   */
  function testAutomaticModuleTranslationImportLanguageEnable() {
    // Code for the language.
    $langcode = $this->randomName(6, 'si-');
    // The English name for the language.
    $name = $this->randomName(16);
    // The native name for the language.
    $native = $this->randomName(16);
    // The domain prefix.
    $prefix = strtolower(str_replace('si-', '', $langcode));

    // Create a .po file.
    $translations_dir = drupal_get_path('module', 'locale_test') .  '/translations/';
    if (!file_exists($translations_dir)) {
      mkdir($translations_dir);
    }
    $filename = $translations_dir . $langcode . '.po';
    file_put_contents($filename, $this->getPoFile());

    // Create a custom language.
    $edit = array(
      'langcode' => $langcode,
      'name' => $name,
      'native' => $native,
      'prefix' => $prefix,
      'direction' => '0',
    );
    $this->drupalPost('admin/settings/language/add', $edit, t('Add custom language'));

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

    // Ensure strings were successfully imported.
    $search = array(
      'string' => 'lundi',
      'language' => $langcode,
      'translation' => 'translated',
      'group' => 'all',
    );
    $this->drupalPost('admin/build/translate/translate', $search, t('Filter'));
    $this->assertNoText(t('No strings found for your search.'), t('String successfully imported.'));

    // Remove our temporary .po file.
    unlink($filename);
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
  }

  /**
   * Helper function that returns a proper .po file.
   */
  function getPoFile() {
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"

msgid "Monday"
msgstr "lundi"

msgid "Tuesday"
msgstr "mardi"

msgid "Wednesday"
msgstr "mercredi"

msgid "Thursday"
msgstr "jeudi"

msgid "Friday"
msgstr "vendredi"

msgid "Saturday"
msgstr "samedi"

msgid "Sunday"
msgstr "dimanche"
736
737
738
739
EOF;
  }

  /**
740
   * Helper function that returns a bad .po file.
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
   */
  function getBadPoFile() {
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"

msgid "Save configuration"
msgstr "Enregistrer la configuration"

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

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

761
762
EOF;
  }
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867

  /**
   * Helper function that returns a proper .po file, for testing overwriting
   * existing translations.
   */
  function getOverwritePoFile() {
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"

msgid "Monday"
msgstr "Montag"

msgid "Day"
msgstr "Jour"
EOF;
  }

}

/**
 * Functional tests for the export of translation files.
 */
class LocaleExportFunctionalTest extends DrupalWebTestCase {
  function getInfo() {
    return array(
      'name' => t('Translation export'),
      'description' => t('Tests the exportation of locale files.'),
      'group' => t('Locale'),
    );
  }

  /**
   * A user able to create languages and export translations.
   */
  protected $admin_user = NULL;

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

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

  /**
   * Test exportation of translations.
   */
  function testExportTranslation() {
    // First import some known translations.
    // This will also automatically enable the 'fr' language.
    $name = tempnam(file_directory_temp(), "po_");
    file_put_contents($name, $this->getPoFile());
    $this->drupalPost('admin/build/translate/import', array(
      'langcode' => 'fr',
      'files[file]' => $name,
    ), t('Import'));
    unlink($name);

    // Get the French translations.
    $this->drupalPost('admin/build/translate/export', array(
      'langcode' => 'fr',
    ), t('Export'));

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

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

  /**
   * Helper function that returns a proper .po file.
   */
  function getPoFile() {
    return <<< EOF
msgid ""
msgstr ""
"Project-Id-Version: Drupal 6\\n"
"MIME-Version: 1.0\\n"
"Content-Type: text/plain; charset=UTF-8\\n"
"Content-Transfer-Encoding: 8bit\\n"
"Plural-Forms: nplurals=2; plural=(n > 1);\\n"

msgid "Monday"
msgstr "lundi"
EOF;
  }

868
}
869

870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
/**
 * Locale uninstall with English UI functional test.
 */
class LocaleUninstallFunctionalTest extends DrupalWebTestCase {
  function getInfo() {
    return array(
      'name' => t('Locale uninstall (EN)'),
      'description' => t('Tests the uninstall process using the built-in UI language.'),
      'group' => t('Locale'),
    );
  }

  /**
   * The default language set for the UI before uninstall.
   */
  protected $ui_language;
886

887
888
889
890
  function setUp() {
    parent::setUp('locale');
    $this->ui_language = 'en';
  }
891

892
893
894
895
896
  /**
   * Check if the values of the Locale variables are correct after uninstall.
   */
  function testUninstallProcess() {
    $locale_module = array('locale');
897

898
899
900
    // Add a new language and optionally set it as default.
    require_once DRUPAL_ROOT . '/includes/locale.inc';
    locale_add_language('fr', 'French', 'Français', LANGUAGE_LTR, '', '', TRUE, $this->ui_language == 'fr');
901

902
903
904
905
906
907
908
    // Check the UI language.
    drupal_init_language();
    global $language;
    $this->assertEqual($language->language, $this->ui_language, t('Current language: %lang', array('%lang' => $language->language)));

    // Change language negotiation options.
    variable_set('language_negotiation', LANGUAGE_NEGOTIATION_PATH_DEFAULT);
909

910
911
    // Enable multilingual workflow option for articles.
    variable_set('language_content_type_article', 1);
912

913
914
    // Change JavaScript translations directory.
    variable_set('locale_js_directory', 'js_translations');
915

916
917
918
919
920
921
    // Build the JavaScript translation file for French.
    $user = $this->drupalCreateUser(array('translate interface', 'access administration pages'));
    $this->drupalLogin($user);
    $this->drupalGet('admin/build/translate/translate');
    $string = db_fetch_object(db_query('SELECT min(lid) AS lid FROM {locales_source} WHERE location LIKE \'%.js%\' AND textgroup = \'default\''));
    $edit = array('translations[fr]' => 'french translation');
922
    $this->drupalPost('admin/build/translate/edit/' . $string->lid, $edit, t('Save translations'));
923
924
    _locale_rebuild_js('fr');
    $file = db_fetch_object(db_query('SELECT javascript FROM {languages} WHERE language = \'fr\''));
925
    $js_file = file_create_path(variable_get('locale_js_directory', 'languages')) . '/fr_' . $file->javascript . '.js';
926
    $this->assertTrue($result = file_exists($js_file), t('JavaScript file created: %file', array('%file' => $result ? $js_file : t('none'))));
927

928
929
    // Disable string caching.
    variable_set('locale_cache_strings', 0);
930

931
932
933
    // Uninstall Locale.
    module_disable($locale_module);
    drupal_uninstall_modules($locale_module);
934

935
936
937
938
939
940
    // Visit the front page.
    $this->drupalGet('');

    // Check the init language logic.
    drupal_init_language();
    $this->assertEqual($language->language, 'en', t('Language after uninstall: %lang', array('%lang' => $language->language)));
941

942
943
    // Check JavaScript files deletion.
    $this->assertTrue($result = !file_exists($js_file), t('JavaScript file deleted: %file', array('%file' => $result ? $js_file : t('found'))));
944

945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
    // Check language count.
    $language_count = variable_get('language_count', 1);
    $this->assertEqual($language_count, 1, t('Language count: %count', array('%count' => $language_count)));

    // Check language negotiation.
    $language_negotiation = variable_get('language_negotiation', LANGUAGE_NEGOTIATION_NONE) == LANGUAGE_NEGOTIATION_NONE;
    $this->assertTrue($language_negotiation, t('Language negotiation: %setting', array('%setting' => t($language_negotiation ? 'none' : 'set'))));

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

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

961
962
963
    // Check JavaScript translations directory.
    $locale_js_directory = variable_get('locale_js_directory', 'languages');
    $this->assertEqual($locale_js_directory, 'languages', t('JavaScript translations directory: %dir', array('%dir' => $locale_js_directory)));
964

965
966
    // Check string caching.
    $locale_cache_strings = variable_get('locale_cache_strings', 1);
967
    $this->assertEqual($locale_cache_strings, 1, t('String caching: %status', array('%status' => t($locale_cache_strings ? 'enabled': 'disabled'))));
968
969
970
971
972
  }
}

/**
 * Locale uninstall with French UI functional test.
973
 *
974
975
976
977
978
979
980
981
982
983
984
985
986
 * Because this class extends LocaleUninstallFunctionalTest, it doesn't require a new
 * test of its own. Rather, it switches the default UI language in setUp and then
 * runs the testUninstallProcess (which it inherits from LocaleUninstallFunctionalTest)
 * to test with this new language.
 */
class LocaleUninstallFrenchFunctionalTest extends LocaleUninstallFunctionalTest {
  function getInfo() {
    return array(
      'name' => t('Locale uninstall (FR)'),
      'description' => t('Tests the uninstall process using French as UI language.'),
      'group' => t('Locale'),
    );
  }
987

988
989
990
991
992
993
994
  function setUp() {
    parent::setUp();
    $this->ui_language = 'fr';
  }
}


995
996
997
998
999
1000
/**
 * Functional tests for the language switching feature.
 */
class LanguageSwitchingFunctionalTest extends DrupalWebTestCase {

  function getInfo() {
For faster browsing, not all history is shown. View entire blame