1<?php
2
3namespace Drupal\Tests\Core\Site;
4
5use Drupal\Core\Site\Settings;
6use Drupal\Tests\UnitTestCase;
7use org\bovigo\vfs\vfsStream;
8
9/**
10 * @coversDefaultClass \Drupal\Core\Site\Settings
11 * @runTestsInSeparateProcesses
12 * @group Site
13 */
14class SettingsTest extends UnitTestCase {
15
16  /**
17   * Simple settings array to test against.
18   *
19   * @var array
20   */
21  protected $config = [];
22
23  /**
24   * The class under test.
25   *
26   * @var \Drupal\Core\Site\Settings
27   */
28  protected $settings;
29
30  /**
31   * @covers ::__construct
32   */
33  protected function setUp(): void {
34    $this->config = [
35      'one' => '1',
36      'two' => '2',
37      'hash_salt' => $this->randomMachineName(),
38    ];
39    $this->settings = new Settings($this->config);
40  }
41
42  /**
43   * @covers ::get
44   */
45  public function testGet() {
46    // Test stored settings.
47    $this->assertEquals($this->config['one'], Settings::get('one'), 'The correct setting was not returned.');
48    $this->assertEquals($this->config['two'], Settings::get('two'), 'The correct setting was not returned.');
49
50    // Test setting that isn't stored with default.
51    $this->assertEquals('3', Settings::get('three', '3'), 'Default value for a setting not properly returned.');
52    $this->assertNull(Settings::get('four'), 'Non-null value returned for a setting that should not exist.');
53  }
54
55  /**
56   * @covers ::getAll
57   */
58  public function testGetAll() {
59    $this->assertEquals($this->config, Settings::getAll());
60  }
61
62  /**
63   * @covers ::getInstance
64   */
65  public function testGetInstance() {
66    $singleton = $this->settings->getInstance();
67    $this->assertEquals($singleton, $this->settings);
68  }
69
70  /**
71   * Tests Settings::getHashSalt().
72   *
73   * @covers ::getHashSalt
74   */
75  public function testGetHashSalt() {
76    $this->assertSame($this->config['hash_salt'], $this->settings->getHashSalt());
77  }
78
79  /**
80   * Tests Settings::getHashSalt() with no hash salt value.
81   *
82   * @covers ::getHashSalt
83   *
84   * @dataProvider providerTestGetHashSaltEmpty
85   */
86  public function testGetHashSaltEmpty(array $config) {
87    // Re-create settings with no 'hash_salt' key.
88    $settings = new Settings($config);
89    $this->expectException(\RuntimeException::class);
90    $settings->getHashSalt();
91  }
92
93  /**
94   * Data provider for testGetHashSaltEmpty.
95   *
96   * @return array
97   */
98  public function providerTestGetHashSaltEmpty() {
99    return [
100      [[]],
101      [['hash_salt' => '']],
102      [['hash_salt' => NULL]],
103    ];
104  }
105
106  /**
107   * Ensures settings cannot be serialized.
108   *
109   * @covers ::__sleep
110   */
111  public function testSerialize() {
112    $this->expectException(\LogicException::class);
113    serialize(new Settings([]));
114  }
115
116  /**
117   * Tests Settings::getApcuPrefix().
118   *
119   * @covers ::getApcuPrefix
120   */
121  public function testGetApcuPrefix() {
122    $settings = new Settings([
123      'hash_salt' => 123,
124      'apcu_ensure_unique_prefix' => TRUE,
125    ]);
126    $this->assertNotEquals($settings::getApcuPrefix('cache_test', '/test/a'), $settings::getApcuPrefix('cache_test', '/test/b'));
127
128    $settings = new Settings([
129      'hash_salt' => 123,
130      'apcu_ensure_unique_prefix' => FALSE,
131    ]);
132    $this->assertNotEquals($settings::getApcuPrefix('cache_test', '/test/a'), $settings::getApcuPrefix('cache_test', '/test/b'));
133  }
134
135  /**
136   * Tests that an exception is thrown when settings are not initialized yet.
137   *
138   * @covers ::getInstance
139   */
140  public function testGetInstanceReflection() {
141    $settings = new Settings([]);
142
143    $class = new \ReflectionClass(Settings::class);
144    $instance_property = $class->getProperty("instance");
145    $instance_property->setAccessible(TRUE);
146    $instance_property->setValue(NULL);
147
148    $this->expectException(\BadMethodCallException::class);
149    $settings->getInstance();
150  }
151
152  /**
153   * Tests deprecation messages and values when using fake deprecated settings.
154   *
155   * Note: Tests for real deprecated settings should not be added to this test
156   * or provider. This test is only for the general deprecated settings API
157   * itself.
158   *
159   * @see self::testRealDeprecatedSettings()
160   * @see self::providerTestRealDeprecatedSettings()
161   *
162   * @param string[] $settings_config
163   *   Array of settings to put in the settings.php file for testing.
164   * @param string $setting_name
165   *   The name of the setting this case should use for Settings::get().
166   * @param string $expected_value
167   *   The expected value of the setting.
168   * @param bool $expect_deprecation_message
169   *   Should the case expect a deprecation message? Defaults to TRUE.
170   *
171   * @dataProvider providerTestFakeDeprecatedSettings
172   *
173   * @covers ::handleDeprecations
174   * @covers ::initialize
175   *
176   * @group legacy
177   */
178  public function testFakeDeprecatedSettings(array $settings_config, string $setting_name, string $expected_value, bool $expect_deprecation_message = TRUE): void {
179
180    $settings_file_content = "<?php\n";
181    foreach ($settings_config as $name => $value) {
182      $settings_file_content .= "\$settings['$name'] = '$value';\n";
183    }
184    $class_loader = NULL;
185    $vfs_root = vfsStream::setup('root');
186    $sites_directory = vfsStream::newDirectory('sites')->at($vfs_root);
187    vfsStream::newFile('settings.php')
188      ->at($sites_directory)
189      ->setContent($settings_file_content);
190
191    // This is the deprecated setting used by all cases for this test method.
192    $deprecated_setting = [
193      'replacement' => 'happy_replacement',
194      'message' => 'The settings key "deprecated_legacy" is deprecated in drupal:9.1.0 and will be removed in drupal:10.0.0. Use "happy_replacement" instead. See https://www.drupal.org/node/3163226.',
195    ];
196
197    $class = new \ReflectionClass(Settings::class);
198    $instance_property = $class->getProperty('deprecatedSettings');
199    $instance_property->setAccessible(TRUE);
200    $deprecated_settings = $instance_property->getValue();
201    $deprecated_settings['deprecated_legacy'] = $deprecated_setting;
202    $instance_property->setValue($deprecated_settings);
203
204    if ($expect_deprecation_message) {
205      $this->expectDeprecation($deprecated_setting['message']);
206    }
207
208    Settings::initialize(vfsStream::url('root'), 'sites', $class_loader);
209    $this->assertEquals($expected_value, Settings::get($setting_name));
210  }
211
212  /**
213   * Provides data for testFakeDeprecatedSettings().
214   *
215   * Note: Tests for real deprecated settings should not be added here.
216   *
217   * @see self::providerTestRealDeprecatedSettings()
218   */
219  public function providerTestFakeDeprecatedSettings(): array {
220
221    $only_legacy = [
222      'deprecated_legacy' => 'old',
223    ];
224    $only_replacement = [
225      'happy_replacement' => 'new',
226    ];
227    $both_settings = [
228      'deprecated_legacy' => 'old',
229      'happy_replacement' => 'new',
230    ];
231
232    return [
233      'Only legacy defined, get legacy' => [
234        $only_legacy,
235        'deprecated_legacy',
236        'old',
237      ],
238      'Only legacy defined, get replacement' => [
239        $only_legacy,
240        'happy_replacement',
241        // Since the new setting isn't yet defined, use the old value.
242        'old',
243        // Since the old setting is there, we should see a deprecation message.
244      ],
245      'Both legacy and replacement defined, get legacy' => [
246        $both_settings,
247        'deprecated_legacy',
248        // Since the replacement is already defined, that should be used.
249        'new',
250      ],
251      'Both legacy and replacement defined, get replacement' => [
252        $both_settings,
253        'happy_replacement',
254        'new',
255        // Should see the deprecation, since the legacy setting is defined.
256      ],
257      'Only replacement defined, get legacy' => [
258        $only_replacement,
259        'deprecated_legacy',
260        // Should get the new value.
261        'new',
262        // But we should see a deprecation message for accessing the old name.
263      ],
264      'Only replacement defined, get replacement' => [
265        $only_replacement,
266        'happy_replacement',
267        // Should get the new value.
268        'new',
269        // No deprecation since the old name is neither used nor defined.
270        FALSE,
271      ],
272    ];
273  }
274
275  /**
276   * Tests deprecation messages for real deprecated settings.
277   *
278   * @param string $legacy_setting
279   *   The legacy name of the setting to test.
280   * @param string $expected_deprecation
281   *   The expected deprecation message.
282   *
283   * @dataProvider providerTestRealDeprecatedSettings
284   * @group legacy
285   */
286  public function testRealDeprecatedSettings(string $legacy_setting, string $expected_deprecation): void {
287
288    $settings_file_content = "<?php\n\$settings['$legacy_setting'] = 'foo';\n";
289    $class_loader = NULL;
290    $vfs_root = vfsStream::setup('root');
291    $sites_directory = vfsStream::newDirectory('sites')->at($vfs_root);
292    vfsStream::newFile('settings.php')
293      ->at($sites_directory)
294      ->setContent($settings_file_content);
295
296    $this->expectDeprecation($expected_deprecation);
297
298    // Presence of the old name in settings.php is enough to trigger messages.
299    Settings::initialize(vfsStream::url('root'), 'sites', $class_loader);
300  }
301
302  /**
303   * Provides data for testRealDeprecatedSettings().
304   */
305  public function providerTestRealDeprecatedSettings(): array {
306    return [
307      [
308        'sanitize_input_whitelist',
309        'The "sanitize_input_whitelist" setting is deprecated in drupal:9.1.0 and will be removed in drupal:10.0.0. Use Drupal\Core\Security\RequestSanitizer::SANITIZE_INPUT_SAFE_KEYS instead. See https://www.drupal.org/node/3163148.',
310      ],
311      [
312        'twig_sandbox_whitelisted_classes',
313        'The "twig_sandbox_whitelisted_classes" setting is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use "twig_sandbox_allowed_classes" instead. See https://www.drupal.org/node/3162897.',
314      ],
315      [
316        'twig_sandbox_whitelisted_methods',
317        'The "twig_sandbox_whitelisted_methods" setting is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use "twig_sandbox_allowed_methods" instead. See https://www.drupal.org/node/3162897.',
318      ],
319      [
320        'twig_sandbox_whitelisted_prefixes',
321        'The "twig_sandbox_whitelisted_prefixes" setting is deprecated in drupal:9.1.0 and is removed from drupal:10.0.0. Use "twig_sandbox_allowed_prefixes" instead. See https://www.drupal.org/node/3162897.',
322      ],
323    ];
324  }
325
326}
327