78fac8eadacf33bdec4c4d7ae877421c030645b5
[yaffs-website] / web / core / modules / system / src / Tests / System / ThemeTest.php
1 <?php
2
3 namespace Drupal\system\Tests\System;
4
5 use Drupal\Core\StreamWrapper\PublicStream;
6 use Drupal\simpletest\WebTestBase;
7
8 /**
9  * Tests the theme interface functionality by enabling and switching themes, and
10  * using an administration theme.
11  *
12  * @group system
13  */
14 class ThemeTest extends WebTestBase {
15
16   /**
17    * A user with administrative permissions.
18    *
19    * @var \Drupal\user\UserInterface
20    */
21   protected $adminUser;
22
23   /**
24    * Modules to enable.
25    *
26    * @var array
27    */
28   public static $modules = ['node', 'block', 'file'];
29
30   protected function setUp() {
31     parent::setUp();
32
33     $this->drupalCreateContentType(['type' => 'page', 'name' => 'Basic page']);
34
35     $this->adminUser = $this->drupalCreateUser(['access administration pages', 'view the administration theme', 'administer themes', 'bypass node access', 'administer blocks']);
36     $this->drupalLogin($this->adminUser);
37     $this->node = $this->drupalCreateNode();
38     $this->drupalPlaceBlock('local_tasks_block');
39   }
40
41   /**
42    * Test the theme settings form.
43    */
44   public function testThemeSettings() {
45     // Ensure invalid theme settings form URLs return a proper 404.
46     $this->drupalGet('admin/appearance/settings/bartik');
47     $this->assertResponse(404, 'The theme settings form URL for a uninstalled theme could not be found.');
48     $this->drupalGet('admin/appearance/settings/' . $this->randomMachineName());
49     $this->assertResponse(404, 'The theme settings form URL for a non-existent theme could not be found.');
50     $this->assertTrue(\Drupal::service('theme_installer')->install(['stable']));
51     $this->drupalGet('admin/appearance/settings/stable');
52     $this->assertResponse(404, 'The theme settings form URL for a hidden theme is unavailable.');
53
54     // Specify a filesystem path to be used for the logo.
55     $file = current($this->drupalGetTestFiles('image'));
56     $file_relative = strtr($file->uri, ['public:/' => PublicStream::basePath()]);
57     $default_theme_path = 'core/themes/classy';
58
59     $supported_paths = [
60       // Raw stream wrapper URI.
61       $file->uri => [
62         'form' => file_uri_target($file->uri),
63         'src' => file_url_transform_relative(file_create_url($file->uri)),
64       ],
65       // Relative path within the public filesystem.
66       file_uri_target($file->uri) => [
67         'form' => file_uri_target($file->uri),
68         'src' => file_url_transform_relative(file_create_url($file->uri)),
69       ],
70       // Relative path to a public file.
71       $file_relative => [
72         'form' => $file_relative,
73         'src' => file_url_transform_relative(file_create_url($file->uri)),
74       ],
75       // Relative path to an arbitrary file.
76       'core/misc/druplicon.png' => [
77         'form' => 'core/misc/druplicon.png',
78         'src' => base_path() . 'core/misc/druplicon.png',
79       ],
80       // Relative path to a file in a theme.
81       $default_theme_path . '/logo.svg' => [
82         'form' => $default_theme_path . '/logo.svg',
83         'src' => base_path() . $default_theme_path . '/logo.svg',
84       ],
85     ];
86     foreach ($supported_paths as $input => $expected) {
87       $edit = [
88         'default_logo' => FALSE,
89         'logo_path' => $input,
90       ];
91       $this->drupalPostForm('admin/appearance/settings', $edit, t('Save configuration'));
92       $this->assertNoText('The custom logo path is invalid.');
93       $this->assertFieldByName('logo_path', $expected['form']);
94
95       // Verify logo path examples.
96       $elements = $this->xpath('//div[contains(@class, :item)]/div[@class=:description]/code', [
97         ':item' => 'js-form-item-logo-path',
98         ':description' => 'description',
99       ]);
100       // Expected default values (if all else fails).
101       $implicit_public_file = 'logo.svg';
102       $explicit_file = 'public://logo.svg';
103       $local_file = $default_theme_path . '/logo.svg';
104       // Adjust for fully qualified stream wrapper URI in public filesystem.
105       if (file_uri_scheme($input) == 'public') {
106         $implicit_public_file = file_uri_target($input);
107         $explicit_file = $input;
108         $local_file = strtr($input, ['public:/' => PublicStream::basePath()]);
109       }
110       // Adjust for fully qualified stream wrapper URI elsewhere.
111       elseif (file_uri_scheme($input) !== FALSE) {
112         $explicit_file = $input;
113       }
114       // Adjust for relative path within public filesystem.
115       elseif ($input == file_uri_target($file->uri)) {
116         $implicit_public_file = $input;
117         $explicit_file = 'public://' . $input;
118         $local_file = PublicStream::basePath() . '/' . $input;
119       }
120       $this->assertEqual((string) $elements[0], $implicit_public_file);
121       $this->assertEqual((string) $elements[1], $explicit_file);
122       $this->assertEqual((string) $elements[2], $local_file);
123
124       // Verify the actual 'src' attribute of the logo being output in a site
125       // branding block.
126       $this->drupalPlaceBlock('system_branding_block', ['region' => 'header']);
127       $this->drupalGet('');
128       $elements = $this->xpath('//header//a[@rel=:rel]/img', [
129           ':rel' => 'home',
130         ]
131       );
132       $this->assertEqual((string) $elements[0]['src'], $expected['src']);
133     }
134     $unsupported_paths = [
135       // Stream wrapper URI to non-existing file.
136       'public://whatever.png',
137       'private://whatever.png',
138       'temporary://whatever.png',
139       // Bogus stream wrapper URIs.
140       'public:/whatever.png',
141       '://whatever.png',
142       ':whatever.png',
143       'public://',
144       // Relative path within the public filesystem to non-existing file.
145       'whatever.png',
146       // Relative path to non-existing file in public filesystem.
147       PublicStream::basePath() . '/whatever.png',
148       // Semi-absolute path to non-existing file in public filesystem.
149       '/' . PublicStream::basePath() . '/whatever.png',
150       // Relative path to arbitrary non-existing file.
151       'core/misc/whatever.png',
152       // Semi-absolute path to arbitrary non-existing file.
153       '/core/misc/whatever.png',
154       // Absolute paths to any local file (even if it exists).
155       \Drupal::service('file_system')->realpath($file->uri),
156     ];
157     $this->drupalGet('admin/appearance/settings');
158     foreach ($unsupported_paths as $path) {
159       $edit = [
160         'default_logo' => FALSE,
161         'logo_path' => $path,
162       ];
163       $this->drupalPostForm(NULL, $edit, t('Save configuration'));
164       $this->assertText('The custom logo path is invalid.');
165     }
166
167     // Upload a file to use for the logo.
168     $edit = [
169       'default_logo' => FALSE,
170       'logo_path' => '',
171       'files[logo_upload]' => \Drupal::service('file_system')->realpath($file->uri),
172     ];
173     $this->drupalPostForm('admin/appearance/settings', $edit, t('Save configuration'));
174
175     $fields = $this->xpath($this->constructFieldXpath('name', 'logo_path'));
176     $uploaded_filename = 'public://' . $fields[0]['value'];
177
178     $this->drupalPlaceBlock('system_branding_block', ['region' => 'header']);
179     $this->drupalGet('');
180     $elements = $this->xpath('//header//a[@rel=:rel]/img', [
181         ':rel' => 'home',
182       ]
183     );
184     $this->assertEqual($elements[0]['src'], file_url_transform_relative(file_create_url($uploaded_filename)));
185
186     $this->container->get('theme_handler')->install(['bartik']);
187
188     // Ensure only valid themes are listed in the local tasks.
189     $this->drupalPlaceBlock('local_tasks_block', ['region' => 'header']);
190     $this->drupalGet('admin/appearance/settings');
191     $theme_handler = \Drupal::service('theme_handler');
192     $this->assertLink($theme_handler->getName('classy'));
193     $this->assertLink($theme_handler->getName('bartik'));
194     $this->assertNoLink($theme_handler->getName('stable'));
195
196     // If a hidden theme is an admin theme it should be viewable.
197     \Drupal::configFactory()->getEditable('system.theme')->set('admin', 'stable')->save();
198     \Drupal::service('router.builder')->rebuildIfNeeded();
199     $this->drupalPlaceBlock('local_tasks_block', ['region' => 'header', 'theme' => 'stable']);
200     $this->drupalGet('admin/appearance/settings');
201     $this->assertLink($theme_handler->getName('stable'));
202     $this->drupalGet('admin/appearance/settings/stable');
203     $this->assertResponse(200, 'The theme settings form URL for a hidden theme that is the admin theme is available.');
204
205     // Ensure default logo and favicons are not triggering custom path
206     // validation errors if their custom paths are set on the form.
207     $edit = [
208       'default_logo' => TRUE,
209       'logo_path' => 'public://whatever.png',
210       'default_favicon' => TRUE,
211       'favicon_path' => 'public://whatever.ico',
212     ];
213     $this->drupalPostForm('admin/appearance/settings', $edit, 'Save configuration');
214     $this->assertNoText('The custom logo path is invalid.');
215     $this->assertNoText('The custom favicon path is invalid.');
216   }
217
218   /**
219    * Test the theme settings logo form.
220    */
221   public function testThemeSettingsLogo() {
222     // Visit Bartik's theme settings page to replace the logo.
223     $this->container->get('theme_handler')->install(['bartik']);
224     $this->drupalGet('admin/appearance/settings/bartik');
225     $edit = [
226       'default_logo' => FALSE,
227       'logo_path' => 'core/misc/druplicon.png',
228     ];
229     $this->drupalPostForm('admin/appearance/settings/bartik', $edit, t('Save configuration'));
230     $this->assertFieldByName('default_logo', FALSE);
231     $this->assertFieldByName('logo_path', 'core/misc/druplicon.png');
232
233     // Make sure the logo and favicon settings are not available when the file
234     // module is not enabled.
235     \Drupal::service('module_installer')->uninstall(['file']);
236     $this->drupalGet('admin/appearance/settings');
237     $this->assertNoText('Logo image settings');
238     $this->assertNoText('Shortcut icon settings');
239   }
240
241   /**
242    * Test the administration theme functionality.
243    */
244   public function testAdministrationTheme() {
245     $this->container->get('theme_handler')->install(['seven']);
246
247     // Install an administration theme and show it on the node admin pages.
248     $edit = [
249       'admin_theme' => 'seven',
250       'use_admin_theme' => TRUE,
251     ];
252     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
253
254     $this->drupalGet('admin/config');
255     $this->assertRaw('core/themes/seven', 'Administration theme used on an administration page.');
256
257     $this->drupalGet('node/' . $this->node->id());
258     $this->assertRaw('core/themes/classy', 'Site default theme used on node page.');
259
260     $this->drupalGet('node/add');
261     $this->assertRaw('core/themes/seven', 'Administration theme used on the add content page.');
262
263     $this->drupalGet('node/' . $this->node->id() . '/edit');
264     $this->assertRaw('core/themes/seven', 'Administration theme used on the edit content page.');
265
266     // Disable the admin theme on the node admin pages.
267     $edit = [
268       'use_admin_theme' => FALSE,
269     ];
270     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
271
272     $this->drupalGet('admin/config');
273     $this->assertRaw('core/themes/seven', 'Administration theme used on an administration page.');
274
275     // Ensure that the admin theme is also visible on the 403 page.
276     $normal_user = $this->drupalCreateUser(['view the administration theme']);
277     $this->drupalLogin($normal_user);
278     $this->drupalGet('admin/config');
279     $this->assertResponse(403);
280     $this->assertRaw('core/themes/seven', 'Administration theme used on an administration page.');
281     $this->drupalLogin($this->adminUser);
282
283     $this->drupalGet('node/add');
284     $this->assertRaw('core/themes/classy', 'Site default theme used on the add content page.');
285
286     // Reset to the default theme settings.
287     $edit = [
288       'admin_theme' => '0',
289       'use_admin_theme' => FALSE,
290     ];
291     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
292
293     $this->drupalGet('admin');
294     $this->assertRaw('core/themes/classy', 'Site default theme used on administration page.');
295
296     $this->drupalGet('node/add');
297     $this->assertRaw('core/themes/classy', 'Site default theme used on the add content page.');
298   }
299
300   /**
301    * Test switching the default theme.
302    */
303   public function testSwitchDefaultTheme() {
304     /** @var \Drupal\Core\Extension\ThemeHandlerInterface $theme_handler */
305     $theme_handler = \Drupal::service('theme_handler');
306     // First, install Stark and set it as the default theme programmatically.
307     $theme_handler->install(['stark']);
308     $this->config('system.theme')->set('default', 'stark')->save();
309
310     // Install Bartik and set it as the default theme.
311     $theme_handler->install(['bartik']);
312     $this->drupalGet('admin/appearance');
313     $this->clickLink(t('Set as default'));
314     $this->assertEqual($this->config('system.theme')->get('default'), 'bartik');
315
316     // Test the default theme on the secondary links (blocks admin page).
317     $this->drupalGet('admin/structure/block');
318     $this->assertText('Bartik(' . t('active tab') . ')', 'Default local task on blocks admin page is the default theme.');
319     // Switch back to Stark and test again to test that the menu cache is cleared.
320     $this->drupalGet('admin/appearance');
321     // Stark is the first 'Set as default' link.
322     $this->clickLink(t('Set as default'));
323     $this->drupalGet('admin/structure/block');
324     $this->assertText('Stark(' . t('active tab') . ')', 'Default local task on blocks admin page has changed.');
325   }
326
327   /**
328    * Test themes can't be installed when the base theme or engine is missing.
329    *
330    * Include test for themes that have a missing base theme somewhere further up
331    * the chain than the immediate base theme.
332    */
333   public function testInvalidTheme() {
334     // theme_page_test_system_info_alter() un-hides all hidden themes.
335     $this->container->get('module_installer')->install(['theme_page_test']);
336     // Clear the system_list() and theme listing cache to pick up the change.
337     $this->container->get('theme_handler')->reset();
338     $this->drupalGet('admin/appearance');
339     $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', ['@base_theme' => 'not_real_test_basetheme']));
340     $this->assertText(t('This theme requires the base theme @base_theme to operate correctly.', ['@base_theme' => 'test_invalid_basetheme']));
341     $this->assertText(t('This theme requires the theme engine @theme_engine to operate correctly.', ['@theme_engine' => 'not_real_engine']));
342     // Check for the error text of a theme with the wrong core version.
343     $this->assertText("This theme is not compatible with Drupal 8.x. Check that the .info.yml file contains the correct 'core' value.");
344     // Check for the error text of a theme without a content region.
345     $this->assertText("This theme is missing a 'content' region.");
346   }
347
348   /**
349    * Test uninstalling of themes works.
350    */
351   public function testUninstallingThemes() {
352     // Install Bartik and set it as the default theme.
353     \Drupal::service('theme_handler')->install(['bartik']);
354     // Set up seven as the admin theme.
355     \Drupal::service('theme_handler')->install(['seven']);
356     $edit = [
357       'admin_theme' => 'seven',
358       'use_admin_theme' => TRUE,
359     ];
360     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
361     $this->drupalGet('admin/appearance');
362     $this->clickLink(t('Set as default'));
363
364     // Check that seven cannot be uninstalled as it is the admin theme.
365     $this->assertNoRaw('Uninstall Seven theme', 'A link to uninstall the Seven theme does not appear on the theme settings page.');
366     // Check that bartik cannot be uninstalled as it is the default theme.
367     $this->assertNoRaw('Uninstall Bartik theme', 'A link to uninstall the Bartik theme does not appear on the theme settings page.');
368     // Check that the classy theme cannot be uninstalled as it is a base theme
369     // of seven and bartik.
370     $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
371
372     // Install Stark and set it as the default theme.
373     \Drupal::service('theme_handler')->install(['stark']);
374
375     $edit = [
376       'admin_theme' => 'stark',
377       'use_admin_theme' => TRUE,
378     ];
379     $this->drupalPostForm('admin/appearance', $edit, t('Save configuration'));
380
381     // Check that seven can be uninstalled now.
382     $this->assertRaw('Uninstall Seven theme', 'A link to uninstall the Seven theme does appear on the theme settings page.');
383     // Check that the classy theme still cannot be uninstalled as it is a
384     // base theme of bartik.
385     $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
386
387     // Change the default theme to stark, stark is second in the list.
388     $this->clickLink(t('Set as default'), 1);
389
390     // Check that bartik can be uninstalled now.
391     $this->assertRaw('Uninstall Bartik theme', 'A link to uninstall the Bartik theme does appear on the theme settings page.');
392
393     // Check that the classy theme still can't be uninstalled as neither of its
394     // base themes have been.
395     $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
396
397     // Uninstall each of the three themes starting with Bartik.
398     $this->clickLink(t('Uninstall'));
399     $this->assertRaw('The <em class="placeholder">Bartik</em> theme has been uninstalled');
400     // Seven is the second in the list.
401     $this->clickLink(t('Uninstall'));
402     $this->assertRaw('The <em class="placeholder">Seven</em> theme has been uninstalled');
403
404     // Check that the classy theme still can't be uninstalled as it is hidden.
405     $this->assertNoRaw('Uninstall Classy theme', 'A link to uninstall the Classy theme does not appear on the theme settings page.');
406   }
407
408   /**
409    * Tests installing a theme and setting it as default.
410    */
411   public function testInstallAndSetAsDefault() {
412     $this->drupalGet('admin/appearance');
413     // Bartik is uninstalled in the test profile and has the third "Install and
414     // set as default" link.
415     $this->clickLink(t('Install and set as default'), 2);
416     // Test the confirmation message.
417     $this->assertText('Bartik is now the default theme.');
418     // Make sure Bartik is now set as the default theme in config.
419     $this->assertEqual($this->config('system.theme')->get('default'), 'bartik');
420
421     // This checks for a regression. See https://www.drupal.org/node/2498691.
422     $this->assertNoText('The bartik theme was not found.');
423
424     $themes = \Drupal::service('theme_handler')->rebuildThemeData();
425     $version = $themes['bartik']->info['version'];
426
427     // Confirm Bartik is indicated as the default theme.
428     $this->assertTextPattern('/Bartik ' . preg_quote($version) . '\s{2,}\(default theme\)/');
429   }
430
431 }