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