Passed
Push — master ( c0a3a7...3b84a4 )
by Jeroen
58:51
created

Config   C

Complexity

Total Complexity 55

Size/Duplication

Total Lines 457
Duplicated Lines 0 %

Test Coverage

Coverage 73.15%

Importance

Changes 0
Metric Value
dl 0
loc 457
ccs 109
cts 149
cp 0.7315
rs 6
c 0
b 0
f 0
wmc 55

22 Methods

Rating   Name   Duplication   Size   Complexity  
A isLocked() 0 2 1
A __set() 0 5 2
A lock() 0 2 1
A __get() 0 6 2
B resolvePath() 0 10 5
A setConfigTable() 0 2 1
B getCookieConfig() 0 27 5
A save() 0 17 4
A __unset() 0 6 2
C fromFile() 0 69 11
A getConfigTable() 0 11 3
A factory() 0 16 2
A hasInitialValue() 0 2 1
A remove() 0 10 2
A getValues() 0 2 1
A __construct() 0 17 2
A __isset() 0 2 1
A wasWarnedLocked() 0 9 3
A mergeValues() 0 3 2
A hasValue() 0 2 1
A getEntityTypes() 0 2 1
A getInitialValue() 0 2 2

How to fix   Complexity   

Complex Class

Complex classes like Config often do a lot of different things. To break such a class down, we need to identify a cohesive component within that class. A common approach to find such a component is to look for fields/methods that share the same prefixes, or suffixes.

Once you have determined the fields that belong together, you can apply the Extract Class refactoring. If the component makes sense as a sub-class, Extract Subclass is also a candidate, and is often faster.

While breaking up the class, it is a good idea to analyze how other classes use Config, and based on these observations, apply Extract Interface, too.

1
<?php
2
namespace Elgg;
3
4
use Elgg\Config\DatarootSettingMigrator;
5
use Elgg\Config\WwwrootSettingMigrator;
6
use Elgg\Database\ConfigTable;
7
use ConfigurationException;
8
use Elgg\Project\Paths;
9
10
/**
11
 * Access to configuration values
12
 *
13
 * @since 1.10.0
14
 *
15
 * @property int           $action_time_limit
16
 * @property int           $action_token_timeout
17
 * @property bool          $allow_registration
18
 * @property string        $allow_user_default_access
19
 * @property bool          $auto_disable_plugins
20
 * @property int           $batch_run_time_in_secs
21
 * @property bool          $boot_complete
22
 * @property int           $boot_cache_ttl
23
 * @property array         $breadcrumbs
24
 * @property string        $cacheroot            Path of cache storage with trailing "/"
25
 * @property array         $css_compiler_options Options passed to CssCrush during CSS compilation
26
 * @property string        $dataroot             Path of data storage with trailing "/"
27
 * @property bool          $data_dir_override
28
 * @property array         $db
29
 * @property string        $dbencoding
30
 * @property string        $dbname
31
 * @property string        $dbuser
32
 * @property string        $dbhost
33
 * @property string        $dbpass
34
 * @property string        $dbprefix
35
 * @property string        $debug
36
 * @property int           $default_access
37
 * @property int           $default_limit
38
 * @property array         $default_widget_info
39
 * @property bool          $disable_rss Is RSS disabled
40
 * @property bool          $elgg_config_locks The application will lock some settings (default true)
41
 * @property string[]      $elgg_cron_periods
42
 * @property array         $elgg_lazy_hover_menus
43
 * @property bool          $elgg_load_sync_code
44
 * @property bool          $elgg_maintenance_mode
45
 * @property string        $elgg_settings_file
46
 * @property bool          $elgg_config_set_secret
47
 * @property bool          $enable_profiling
48
 * @property mixed         $embed_tab
49
 * @property string        $exception_include
50
 * @property string[]      $group
51
 * @property array         $group_tool_options
52
 * @property bool          $i18n_loaded_from_cache
53
 * @property array         $icon_sizes
54
 * @property string        $image_processor
55
 * @property string        $installed
56
 * @property bool          $installer_running
57
 * @property string        $language     Site language code
58
 * @property int           $lastcache
59
 * @property \ElggLogCache $log_cache
60
 * @property array         $libraries
61
 * @property bool          $memcache
62
 * @property string        $memcache_namespace_prefix
63
 * @property array         $memcache_servers
64
 * @property array         $menus
65
 * @property int           $min_password_length
66
 * @property string[]      $pages
67
 * @property-read string   $path         Path of composer install with trailing "/"
68
 * @property-read string   $pluginspath  Alias of plugins_path
69
 * @property-read string   $plugins_path Path of project "mod/" directory
70
 * @property array         $profile_custom_fields
71
 * @property array         $profile_fields
72
 * @property string        $profiling_minimum_percentage
73
 * @property bool          $profiling_sql
74
 * @property array         $processed_upgrades
75
 * @property bool          $redis
76
 * @property array         $redis_servers
77
 * @property string[]      $registered_entities
78
 * @property bool          $remove_branding Is Elgg branding disabled
79
 * @property bool          $security_disable_password_autocomplete
80
 * @property bool          $security_email_require_password
81
 * @property bool          $security_notify_admins
82
 * @property bool          $security_notify_user_admin
83
 * @property bool          $security_notify_user_ban
84
 * @property bool          $security_protect_cron
85
 * @property bool          $security_protect_upgrade
86
 * @property int           $simplecache_enabled
87
 * @property int           $simplecache_lastupdate
88
 * @property bool          $simplecache_minify_css
89
 * @property bool          $simplecache_minify_js
90
 * @property \ElggSite     $site
91
 * @property string        $sitedescription
92
 * @property string        $sitename
93
 * @property string[]      $site_custom_menu_items
94
 * @property string[]      $site_featured_menu_names
95
 * @property-read int      $site_guid
96
 * @property bool          $system_cache_enabled
97
 * @property bool          $system_cache_loaded
98
 * @property string        $url          Alias of "wwwroot"
99
 * @property int           $version
100
 * @property string        $view         Default viewtype (usually not set)
101
 * @property bool          $walled_garden
102
 * @property string        $wwwroot      Site URL
103
 * @property string        $x_sendfile_type
104
 * @property string        $x_accel_mapping
105
 * @property bool          $_boot_cache_hit
106
 * @property bool          $_elgg_autofeed
107
 */
108
class Config {
109
	use Loggable;
110
111
	/**
112
	 * @var array Configuration storage
113
	 */
114
	private $values;
115
116
	/**
117
	 * @var array
118
	 */
119
	private $initial_values;
120
121
	/**
122
	 * @var bool
123
	 */
124
	private $cookies_configured = false;
125
126
	/**
127
	 * @var array
128
	 */
129
	private $cookies;
130
131
	/**
132
	 * @var ConfigTable Do not use directly. Use getConfigTable().
133
	 */
134
	private $config_table;
135
136
	/**
137
	 * @var array
138
	 */
139
	private $locked = [];
140
141
	/**
142
	 * @var string
143
	 */
144
	private $settings_path;
145
146
	/**
147
	 * Constructor
148
	 *
149
	 * @param array $values Initial config values from Env/settings file
150
	 * @internal Do not use
151
	 * @access private
152
	 */
153 4417
	public function __construct(array $values = []) {
154 4417
		$this->values = $values;
155
156
		// Don't keep copies of these in case config gets dumped
157
		$sensitive_props = [
158 4417
			'__site_secret__',
159
			'db',
160
			'dbhost',
161
			'dbuser',
162
			'dbpass',
163
			'dbname',
164
			'profiler_secret_get_var'
165
		];
166 4417
		foreach ($sensitive_props as $name) {
167 4417
			unset($values[$name]);
168
		}
169 4417
		$this->initial_values = $values;
170 4417
	}
171
172
	/**
173
	 * Build a config from default settings locations
174
	 *
175
	 * @param string $settings_path Path of settings file
176
	 * @param bool   $try_env       If path not given, try $_ENV['ELGG_SETTINGS_FILE']
177
	 * @return Config
178
	 *
179
	 * @throws ConfigurationException
180
	 */
181 2
	public static function factory($settings_path = '', $try_env = true) {
182 2
		$reason1 = '';
183 2
		$reason2 = '';
184
185 2
		$settings_path = self::resolvePath($settings_path, $try_env);
186
187 2
		$config = self::fromFile($settings_path, $reason1);
0 ignored issues
show
Bug introduced by
$settings_path of type Elgg\Config is incompatible with the type string expected by parameter $path of Elgg\Config::fromFile(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

187
		$config = self::fromFile(/** @scrutinizer ignore-type */ $settings_path, $reason1);
Loading history...
188
189 2
		if (!$config) {
190
			$msg = __METHOD__ . ": Reading configs failed: $reason1 $reason2";
191
			throw new ConfigurationException($msg);
192
		}
193
194 2
		$config->settings_path = $settings_path;
195
196 2
		return $config;
197
	}
198
199
	/**
200
	 * Build a config from a file
201
	 *
202
	 * @param string $path   Path of settings.php
203
	 * @param string $reason Returned reason for failure
204
	 *
205
	 * @return bool|Config false on failure
206
	 */
207 6
	public static function fromFile($path, &$reason = '') {
208 6
		if (!is_file($path)) {
209
			$reason = "File $path not present.";
210
			return false;
211
		}
212
213 6
		if (!is_readable($path)) {
214
			$reason = "File $path not readable.";
215
			return false;
216
		}
217
218
		// legacy loading. If $CONFIG doesn't exist, remove it after the
219
		// settings file is read.
220 6
		if (isset($GLOBALS['CONFIG'])) {
221
			// don't overwrite it
222 6
			$global = $GLOBALS['CONFIG'];
223 6
			unset($GLOBALS['CONFIG']);
224
		} else {
225
			$global = null;
226
		}
227
228 6
		Includer::requireFile($path);
229
230 6
		$get_db = function() {
231
			// try to migrate settings to the file
232
			$db_conf = new \Elgg\Database\DbConfig($GLOBALS['CONFIG']);
233
			return new Database($db_conf);
234 6
		};
235
236 6
		if (empty($GLOBALS['CONFIG']->dataroot)) {
237
			$dataroot = (new DatarootSettingMigrator($get_db(), $path))->migrate();
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $dataroot is correct as new Elgg\Config\Dataroot...db(), $path)->migrate() targeting Elgg\Config\DatarootSettingMigrator::migrate() seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
238
			if (!isset($dataroot)) {
239
				$reason = 'The Elgg settings file is missing $CONFIG->dataroot.';
240
				return false;
241
			}
242
243
			$GLOBALS['CONFIG']->dataroot = $dataroot;
244
245
			// just try this one time to migrate wwwroot
246
			if (!isset($GLOBALS['CONFIG']->wwwroot)) {
247
				$wwwroot = (new WwwrootSettingMigrator($get_db(), $path))->migrate();
248
				if (isset($wwwroot)) {
249
					$GLOBALS['CONFIG']->wwwroot = $wwwroot;
250
				}
251
			}
252
		}
253
254 6
		$config = new self(get_object_vars($GLOBALS['CONFIG']));
255
256 6
		if ($global !== null) {
257
			// restore
258 6
			$GLOBALS['CONFIG'] = $global;
259
		} else {
260
			unset($GLOBALS['CONFIG']);
261
		}
262
263 6
		if ($config->{'X-Sendfile-Type'}) {
264
			$config->{'x_sendfile_type'} = $config->{'X-Sendfile-Type'};
265
			unset($config->{'X-Sendfile-Type'});
266
		}
267 6
		if ($config->{'X-Accel-Mapping'}) {
268
			$config->{'x_accel_mapping'} = $config->{'X-Accel-Mapping'};
269
			unset($config->{'X-Accel-Mapping'});
270
		}
271
272 6
		$config->elgg_settings_file = $path;
273 6
		$config->lock('elgg_settings_file');
274
275 6
		return $config;
276
	}
277
278
	/**
279
	 * Resolve settings path
280
	 *
281
	 * @param string $settings_path Path of settings file
282
	 * @param bool   $try_env       If path not given, try $_ENV['ELGG_SETTINGS_FILE']
283
	 * @return Config
284
	 */
285 9
	public static function resolvePath($settings_path = '', $try_env = true) {
286 9
		if (!$settings_path) {
287 9
			if ($try_env && !empty($_ENV['ELGG_SETTINGS_FILE'])) {
288 9
				$settings_path = $_ENV['ELGG_SETTINGS_FILE'];
289
			} else if (!$settings_path) {
290
				$settings_path = Paths::settingsFile(Paths::SETTINGS_PHP);
291
			}
292
		}
293
294 9
		return \Elgg\Project\Paths::sanitize($settings_path, false);
0 ignored issues
show
Bug Best Practice introduced by
The expression return Elgg\Project\Path...($settings_path, false) returns the type string which is incompatible with the documented return type Elgg\Config.
Loading history...
295
	}
296
297
	/**
298
	 * Set an array of values
299
	 *
300
	 * @param array $values Values
301
	 * @return void
302
	 */
303 4777
	public function mergeValues(array $values) {
304 4777
		foreach ($values as $name => $value) {
305 4777
			$this->__set($name, $value);
306
		}
307 4777
	}
308
309
	/**
310
	 * Get all values
311
	 *
312
	 * @return array
313
	 */
314 2
	public function getValues() {
315 2
		return $this->values;
316
	}
317
318
	/**
319
	 * Set up and return the cookie configuration array resolved from settings
320
	 *
321
	 * @return array
322
	 */
323 4801
	public function getCookieConfig() {
324 4801
		if ($this->cookies_configured) {
325 4801
			return $this->cookies;
326
		}
327
328 4417
		$cookies = $this->cookies;
329 4417
		if (!is_array($cookies)) {
330 4417
			$cookies = [];
331
		}
332
333 4417
		if (!isset($cookies['session'])) {
334 4417
			$cookies['session'] = [];
335
		}
336 4417
		$session_defaults = session_get_cookie_params();
337 4417
		$session_defaults['name'] = 'Elgg';
338 4417
		$cookies['session'] = array_merge($session_defaults, $cookies['session']);
339 4417
		if (!isset($cookies['remember_me'])) {
340 4417
			$cookies['remember_me'] = [];
341
		}
342 4417
		$session_defaults['name'] = 'elggperm';
343 4417
		$session_defaults['expire'] = strtotime("+30 days");
344 4417
		$cookies['remember_me'] = array_merge($session_defaults, $cookies['remember_me']);
345
346 4417
		$this->cookies = $cookies;
347 4417
		$this->cookies_configured = true;
348
349 4417
		return $cookies;
350
	}
351
352
	/**
353
	 * Get an Elgg configuration value if it's been set or loaded during the boot process.
354
	 *
355
	 * Before \Elgg\BootService::boot, values from the database will not be present.
356
	 *
357
	 * @param string $name Name
358
	 *
359
	 * @return mixed null if does not exist
360
	 */
361 5390
	public function __get($name) {
362 5390
		if (isset($this->values[$name])) {
363 5390
			return $this->values[$name];
364
		}
365
366 4859
		return null;
367
	}
368
369
	/**
370
	 * Test if we have a set value
371
	 *
372
	 * @param string $name Name
373
	 *
374
	 * @return bool
375
	 */
376 1919
	public function hasValue($name) {
377 1919
		return isset($this->values[$name]);
378
	}
379
380
	/**
381
	 * Get a value set at construction time
382
	 *
383
	 * @param string $name Name
384
	 * @return mixed null = not set
385
	 */
386 4777
	public function getInitialValue($name) {
387 4777
		return isset($this->initial_values[$name]) ? $this->initial_values[$name] : null;
388
	}
389
390
	/**
391
	 * Was a value available at construction time? (From settings.php)
392
	 *
393
	 * @param string $name Name
394
	 *
395
	 * @return bool
396
	 */
397 4777
	public function hasInitialValue($name) {
398 4777
		return isset($this->initial_values[$name]);
399
	}
400
401
	/**
402
	 * Make a value read-only
403
	 *
404
	 * @param string $name Name
405
	 * @return void
406
	 */
407 4417
	public function lock($name) {
408 4417
		$this->locked[$name] = true;
409 4417
	}
410
411
	/**
412
	 * Is this value locked?
413
	 *
414
	 * @param string $name Name
415
	 *
416
	 * @return bool
417
	 */
418
	public function isLocked($name) {
419
		return isset($this->locked[$name]);
420
	}
421
422
	/**
423
	 * Set an Elgg configuration value
424
	 *
425
	 * @warning This does not persist the configuration setting. Use elgg_save_config()
426
	 *
427
	 * @param string $name  Name
428
	 * @param mixed  $value Value
429
	 * @return void
430
	 */
431 4795
	public function __set($name, $value) {
432 4795
		if ($this->wasWarnedLocked($name)) {
433
			return;
434
		}
435 4795
		$this->values[$name] = $value;
436 4795
	}
437
438
	/**
439
	 * Handle isset()
440
	 *
441
	 * @param string $name Name
442
	 * @return bool
443
	 */
444 4942
	public function __isset($name) {
445 4942
		return $this->__get($name) !== null;
446
	}
447
448
	/**
449
	 * Handle unset()
450
	 *
451
	 * @param string $name Name
452
	 * @return void
453
	 */
454 4778
	public function __unset($name) {
455 4778
		if ($this->wasWarnedLocked($name)) {
456
			return;
457
		}
458
459 4778
		unset($this->values[$name]);
460 4778
	}
461
462
	/**
463
	 * Save a configuration setting to the database
464
	 *
465
	 * @param string $name  Name (cannot be greater than 255 characters)
466
	 * @param mixed  $value Value
467
	 *
468
	 * @return bool
469
	 */
470 14
	public function save($name, $value) {
471 14
		if ($this->wasWarnedLocked($name)) {
472
			return false;
473
		}
474
475 14
		if (strlen($name) > 255) {
476 1
			if ($this->logger) {
477
				$this->logger->error("The name length for configuration variables cannot be greater than 255");
478
			}
479 1
			return false;
480
		}
481
482 13
		$result = $this->getConfigTable()->set($name, $value);
483
484 13
		$this->__set($name, $value);
485
	
486 13
		return $result;
487
	}
488
489
	/**
490
	 * Removes a configuration setting from the database
491
	 *
492
	 * @param string $name Configuration name
493
	 *
494
	 * @return bool
495
	 */
496 7
	public function remove($name) {
497 7
		if ($this->wasWarnedLocked($name)) {
498
			return false;
499
		}
500
501 7
		$result = $this->getConfigTable()->remove($name);
502
503 7
		unset($this->values[$name]);
504
	
505 7
		return $result;
506
	}
507
508
	/**
509
	 * Log a read-only warning if the name is read-only
510
	 *
511
	 * @param string $name Name
512
	 * @return bool
513
	 */
514 4797
	private function wasWarnedLocked($name) {
515 4797
		if (!isset($this->locked[$name])) {
516 4797
			return false;
517
		}
518
519
		if ($this->logger) {
520
			$this->logger->warn("The property $name is read-only.");
521
		}
522
		return true;
523
	}
524
525
	/**
526
	 * Set the config table service (must be set)
527
	 *
528
	 * This is a necessary evil until we refactor so that the service provider has no dependencies.
529
	 *
530
	 * @param ConfigTable $table the config table service
531
	 * @return void
532
	 *
533
	 * @access private
534
	 * @internal
535
	 */
536
	public function setConfigTable(ConfigTable $table) {
537
		$this->config_table = $table;
538
	}
539
540
	/**
541
	 * Get the core entity types
542
	 *
543
	 * @return string[]
544
	 */
545 5355
	public static function getEntityTypes() {
546 5355
		return ['group', 'object', 'site', 'user'];
547
	}
548
549
	/**
550
	 * Get the config table API
551
	 *
552
	 * @return ConfigTable
553
	 */
554 15
	private function getConfigTable() {
555 15
		if (!$this->config_table) {
556 9
			if (!function_exists('_elgg_services')) {
557
				throw new \RuntimeException('setConfigTable() must be called before using API that' .
558
					' uses the database.');
559
			}
560
561 9
			$this->config_table = _elgg_services()->configTable;
562
		}
563
564 15
		return $this->config_table;
565
	}
566
}
567