Issues (947)

engine/classes/Elgg/Config.php (2 issues)

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						Maximum php execution time for actions (in seconds)
16
 * @property int           $action_token_timeout
17
 * @property bool          $allow_phpinfo							Allow access tot PHPInfo
18
 * @property bool          $allow_registration						Is registration enabled
19
 * @property string        $allow_user_default_access				Are users allowed to set their own default access level
20
 * @property string        $assetroot            					Path of asset (views) simplecache with trailing "/"
21
 * @property bool          $auto_disable_plugins					Are unbootable plugins automatically disabled
22
 * @property int           $batch_run_time_in_secs					Max time for a single upgrade loop
23
 * @property-read int      $bootdata_plugin_settings_limit			Max amount of plugin settings to determine if plugin will be cached
24
 * @property bool          $boot_complete
25
 * @property int           $boot_cache_ttl
26
 * @property array         $breadcrumbs
27
 * @property string        $cacheroot            					Path of cache storage with trailing "/"
28
 * @property bool          $can_change_username						Is user allowed to change the username
29
 * @property array         $css_compiler_options 					Options passed to CssCrush during CSS compilation
30
 * @property string        $dataroot             					Path of data storage with trailing "/"
31
 * @property bool          $data_dir_override
32
 * @property string        $date_format          					Preferred PHP date format
33
 * @property string        $date_format_datepicker 					Preferred jQuery datepicker date format
34
 * @property array         $db
35
 * @property string        $dbencoding
36
 * @property string        $dbname
37
 * @property string        $dbuser
38
 * @property string        $dbhost
39
 * @property int           $dbport
40
 * @property string        $dbpass
41
 * @property string        $dbprefix
42
 * @property bool          $db_disable_query_cache
43
 * @property string        $debug
44
 * @property int           $default_access							Default access
45
 * @property int           $default_limit							The default "limit" used in listings and queries
46
 * @property array         $default_widget_info
47
 * @property bool          $disable_rss 							Is RSS disabled
48
 * @property bool          $elgg_config_locks 						The application will lock some settings (default true)
49
 * @property string[]      $elgg_cron_periods
50
 * @property array         $elgg_lazy_hover_menus
51
 * @property bool          $elgg_load_sync_code
52
 * @property bool          $elgg_maintenance_mode
53
 * @property string        $elgg_settings_file
54
 * @property bool          $elgg_config_set_secret
55
 * @property bool          $enable_profiling
56
 * @property mixed         $embed_tab
57
 * @property string        $exception_include						This is an optional script used to override Elgg's default handling of uncaught exceptions.
58
 * @property string[]      $group
59
 * @property bool          $i18n_loaded_from_cache
60
 * @property array         $icon_sizes
61
 * @property string        $image_processor
62
 * @property string        $installed 								Is the site fully installed?
63
 * @property bool          $installer_running
64
 * @property string        $language                   				Site language code
65
 * @property string[]      $language_to_locale_mapping 				A language to locale mapping (eg. 'en' => ['en_US'] or 'nl' => ['nl_NL'])
66
 * @property int           $lastcache								The last cache timestamp
67
 * @property bool          $memcache
68
 * @property string        $memcache_namespace_prefix
69
 * @property array         $memcache_servers
70
 * @property array         $menus
71
 * @property int           $min_password_length
72
 * @property int           $minusername
73
 * @property string[]      $pages
74
 * @property-read string   $path         							Path of composer install with trailing "/"
75
 * @property-read string   $pluginspath  							Alias of plugins_path
76
 * @property-read string   $plugins_path 							Path of project "mod/" directory where the plugins are stored
77
 * @property array         $profile_custom_fields
78
 * @property array         $profile_fields
79
 * @property string        $profiling_minimum_percentage
80
 * @property bool          $profiling_sql
81
 * @property array         $processed_upgrades
82
 * @property bool          $redis
83
 * @property array         $redis_servers
84
 * @property string[]      $registered_entities						A list of registered entities and subtypes. Used in search.
85
 * @property bool          $remove_branding 						Is Elgg branding disabled
86
 * @property bool          $security_disable_password_autocomplete
87
 * @property bool          $security_email_require_password
88
 * @property bool          $security_notify_admins
89
 * @property bool          $security_notify_user_admin
90
 * @property bool          $security_notify_user_ban
91
 * @property bool          $security_protect_cron
92
 * @property bool          $security_protect_upgrade
93
 * @property array		   $servicehandler							Holds the service handlers as registered by the webservice plugin
94
 * @property string        $seeder_local_image_folder 				Path to a local folder containing images used for seeding
95
 * @property bool          $simplecache_enabled						Is simplecache enabled?
96
 * @property int           $simplecache_lastupdate
97
 * @property bool          $simplecache_minify_css
98
 * @property bool          $simplecache_minify_js
99
 * @property \ElggSite     $site 									The site entity
100
 * @property string        $sitedescription							The site description
101
 * @property string        $sitename 								The name of the site
102
 * @property string[]      $site_custom_menu_items
103
 * @property string[]      $site_featured_menu_names
104
 * @property-read int      $site_guid 								The guid of the site object
105
 * @property bool          $system_cache_enabled					Is the system cache enabled?
106
 * @property bool          $system_cache_loaded
107
 * @property string        $time_format  							Preferred PHP time format
108
 * @property string        $url          							Alias of "wwwroot"
109
 * @property int           $version
110
 * @property string        $view         							Default viewtype (usually not set)
111
 * @property bool          $walled_garden							Is current site in walled garden mode?
112
 * @property string        $wwwroot      							Site URL
113
 * @property string        $x_sendfile_type
114
 * @property string        $x_accel_mapping
115
 * @property bool          $_boot_cache_hit
116
 * @property bool          $_elgg_autofeed
117
 *
118
 * @property bool          $_service_boot_complete
119
 * @property bool          $_plugins_boot_complete
120
 * @property bool          $_application_boot_complete
121
 *
122
 * @internal
123
 */
124
class Config {
125
	use Loggable;
126
127
	/**
128
	 * @var array Configuration storage
129
	 */
130
	private $values;
131
132
	/**
133
	 * @var array
134
	 */
135
	private $initial_values;
136
137
	/**
138
	 * @var bool
139
	 */
140
	private $cookies_configured = false;
141
142
	/**
143
	 * @var array
144
	 */
145
	private $cookies = [];
146
147
	/**
148
	 * @var ConfigTable Do not use directly. Use getConfigTable().
149
	 */
150
	private $config_table;
151
152
	/**
153
	 * @var array
154
	 */
155
	private $locked = [];
156
157
	/**
158
	 * @var string
159
	 */
160
	private $settings_path;
161
162
	/**
163
	 * Constructor
164
	 *
165
	 * @param array $values Initial config values from Env/settings file
166
	 */
167 5001
	public function __construct(array $values = []) {
168 5001
		$this->values = $values;
169
170
		// Don't keep copies of these in case config gets dumped
171
		$sensitive_props = [
172 5001
			'__site_secret__',
173
			'db',
174
			'dbhost',
175
			'dbport',
176
			'dbuser',
177
			'dbpass',
178
			'dbname',
179
			'profiler_secret_get_var'
180
		];
181 5001
		foreach ($sensitive_props as $name) {
182 5001
			unset($values[$name]);
183
		}
184 5001
		$this->initial_values = $values;
185 5001
	}
186
187
	/**
188
	 * Build a config from default settings locations
189
	 *
190
	 * @param string $settings_path Path of settings file
191
	 * @param bool   $try_env       If path not given, try $_ENV['ELGG_SETTINGS_FILE']
192
	 * @return Config
193
	 *
194
	 * @throws ConfigurationException
195
	 */
196 8
	public static function factory($settings_path = '', $try_env = true) {
197 8
		$reason1 = '';
198
199 8
		$settings_path = self::resolvePath($settings_path, $try_env);
200
201 8
		$config = self::fromFile($settings_path, $reason1);
202
203 8
		if (!$config) {
204
			$msg = __METHOD__ . ": Reading configs failed: $reason1";
205
			throw new ConfigurationException($msg);
206
		}
207
208 8
		$config->settings_path = $settings_path;
209
210 8
		return $config;
211
	}
212
213
	/**
214
	 * Build a config from a file
215
	 *
216
	 * @param string $path   Path of settings.php
217
	 * @param string $reason Returned reason for failure
218
	 *
219
	 * @return bool|Config false on failure
220
	 */
221 12
	public static function fromFile($path, &$reason = '') {
222 12
		if (!is_file($path)) {
223
			$reason = "File $path not present.";
224
			return false;
225
		}
226
227 12
		if (!is_readable($path)) {
228
			$reason = "File $path not readable.";
229
			return false;
230
		}
231
232
		// legacy loading. If $CONFIG doesn't exist, remove it after the
233
		// settings file is read.
234 12
		if (isset($GLOBALS['CONFIG'])) {
235
			// don't overwrite it
236 11
			$global = $GLOBALS['CONFIG'];
237 11
			unset($GLOBALS['CONFIG']);
238
		} else {
239 1
			$global = null;
240
		}
241
242 12
		Includer::requireFile($path);
243
244 12
		$get_db = function() {
245
			// try to migrate settings to the file
246
			$db_conf = new \Elgg\Database\DbConfig($GLOBALS['CONFIG']);
247
			$cache = new \Elgg\Cache\QueryCache(50, true);
248
			return new Database($db_conf, $cache);
249 12
		};
250
251 12
		if (empty($GLOBALS['CONFIG']->dataroot)) {
252
			$dataroot = (new DatarootSettingMigrator($get_db(), $path))->migrate();
253
			if (!isset($dataroot)) {
254
				$reason = 'The Elgg settings file is missing $CONFIG->dataroot.';
255
				return false;
256
			}
257
258
			$GLOBALS['CONFIG']->dataroot = $dataroot;
259
260
			// just try this one time to migrate wwwroot
261
			if (!isset($GLOBALS['CONFIG']->wwwroot)) {
262
				$wwwroot = (new WwwrootSettingMigrator($get_db(), $path))->migrate();
263
				if (isset($wwwroot)) {
264
					$GLOBALS['CONFIG']->wwwroot = $wwwroot;
265
				}
266
			}
267
		}
268
269 12
		$config = new self(get_object_vars($GLOBALS['CONFIG']));
270
271 12
		if ($global !== null) {
272
			// restore
273 11
			$GLOBALS['CONFIG'] = $global;
274
		} else {
275 1
			unset($GLOBALS['CONFIG']);
276
		}
277
278 12
		if ($config->{'X-Sendfile-Type'}) {
279
			$config->{'x_sendfile_type'} = $config->{'X-Sendfile-Type'};
280
			unset($config->{'X-Sendfile-Type'});
281
		}
282 12
		if ($config->{'X-Accel-Mapping'}) {
283
			$config->{'x_accel_mapping'} = $config->{'X-Accel-Mapping'};
284
			unset($config->{'X-Accel-Mapping'});
285
		}
286
287 12
		$config->elgg_settings_file = $path;
288 12
		$config->lock('elgg_settings_file');
289
290 12
		return $config;
291
	}
292
293
	/**
294
	 * Resolve settings path
295
	 *
296
	 * @param string $settings_path Path of settings file
297
	 * @param bool   $try_env       If path not given, try $_ENV['ELGG_SETTINGS_FILE']
298
	 * @return string
299
	 */
300 15
	public static function resolvePath($settings_path = '', $try_env = true) {
301 15
		if (!$settings_path) {
302 15
			if ($try_env && !empty($_ENV['ELGG_SETTINGS_FILE'])) {
303 9
				$settings_path = $_ENV['ELGG_SETTINGS_FILE'];
304 6
			} else if (!$settings_path) {
305 6
				$settings_path = Paths::settingsFile(Paths::SETTINGS_PHP);
306
			}
307
		}
308
309 15
		return \Elgg\Project\Paths::sanitize($settings_path, false);
310
	}
311
312
	/**
313
	 * Set an array of values
314
	 *
315
	 * @param array $values Values
316
	 * @return void
317
	 */
318 5459
	public function mergeValues(array $values) {
319 5459
		foreach ($values as $name => $value) {
320 5459
			$this->__set($name, $value);
321
		}
322 5459
	}
323
324
	/**
325
	 * Get all values
326
	 *
327
	 * @return array
328
	 */
329 2
	public function getValues() {
330 2
		return $this->values;
331
	}
332
333
	/**
334
	 * Set up and return the cookie configuration array resolved from settings
335
	 *
336
	 * @return array
337
	 */
338 5502
	public function getCookieConfig() {
339 5502
		if ($this->cookies_configured) {
340 5502
			return $this->cookies;
341
		}
342
343 4999
		$cookies = [];
344 4999
		if ($this->hasInitialValue('cookies')) {
345 1
			$cookies = $this->getInitialValue('cookies');
346
		}
347
348
		// session cookie config
349 4999
		if (!isset($cookies['session'])) {
350 4999
			$cookies['session'] = [];
351
		}
352 4999
		$session_defaults = session_get_cookie_params();
353 4999
		$session_defaults['name'] = 'Elgg';
354 4999
		$cookies['session'] = array_merge($session_defaults, $cookies['session']);
355
		
356
		// remember me cookie config
357 4999
		if (!isset($cookies['remember_me'])) {
358 4999
			$cookies['remember_me'] = [];
359
		}
360 4999
		$session_defaults['name'] = 'elggperm';
361 4999
		$session_defaults['expire'] = strtotime("+30 days");
362 4999
		$cookies['remember_me'] = array_merge($session_defaults, $cookies['remember_me']);
363
364 4999
		$this->cookies = $cookies;
365 4999
		$this->cookies_configured = true;
366
367 4999
		return $cookies;
368
	}
369
370
	/**
371
	 * Get an Elgg configuration value if it's been set or loaded during the boot process.
372
	 *
373
	 * Before \Elgg\BootService::boot, values from the database will not be present.
374
	 *
375
	 * @param string $name Name
376
	 *
377
	 * @return mixed null if does not exist
378
	 */
379 6237
	public function __get($name) {
380
		switch ($name) {
381 6237
			case 'group_tool_options':
382
				elgg_deprecated_notice("'$name' config option is no longer in use. Use elgg()->group_tools->all()", '3.0');
383
				return elgg()->group_tools->all();
384
		}
385
386 6237
		if (isset($this->values[$name])) {
387 6237
			return $this->values[$name];
388
		}
389
390 5562
		return null;
391
	}
392
393
	/**
394
	 * Test if we have a set value
395
	 *
396
	 * @param string $name Name
397
	 *
398
	 * @return bool
399
	 */
400 2449
	public function hasValue($name) {
401 2449
		return isset($this->values[$name]);
402
	}
403
404
	/**
405
	 * Get a value set at construction time
406
	 *
407
	 * @param string $name Name
408
	 * @return mixed null = not set
409
	 */
410 5459
	public function getInitialValue($name) {
411 5459
		return isset($this->initial_values[$name]) ? $this->initial_values[$name] : null;
412
	}
413
414
	/**
415
	 * Was a value available at construction time? (From settings.php)
416
	 *
417
	 * @param string $name Name
418
	 *
419
	 * @return bool
420
	 */
421 5459
	public function hasInitialValue($name) {
422 5459
		return isset($this->initial_values[$name]);
423
	}
424
425
	/**
426
	 * Make a value read-only
427
	 *
428
	 * @param string $name Name
429
	 * @return void
430
	 */
431 5001
	public function lock($name) {
432 5001
		$this->locked[$name] = true;
433 5001
	}
434
435
	/**
436
	 * Is this value locked?
437
	 *
438
	 * @param string $name Name
439
	 *
440
	 * @return bool
441
	 */
442
	public function isLocked($name) {
443
		return isset($this->locked[$name]);
444
	}
445
446
	/**
447
	 * Set an Elgg configuration value
448
	 *
449
	 * @warning This does not persist the configuration setting. Use elgg_save_config()
450
	 *
451
	 * @param string $name  Name
452
	 * @param mixed  $value Value
453
	 * @return void
454
	 */
455 5509
	public function __set($name, $value) {
456 5509
		if ($this->wasWarnedLocked($name)) {
457 4919
			return;
458
		}
459 5509
		$this->values[$name] = $value;
460 5509
	}
461
462
	/**
463
	 * Handle isset()
464
	 *
465
	 * @param string $name Name
466
	 * @return bool
467
	 */
468 5460
	public function __isset($name) {
469 5460
		return $this->__get($name) !== null;
470
	}
471
472
	/**
473
	 * Handle unset()
474
	 *
475
	 * @param string $name Name
476
	 * @return void
477
	 */
478 5460
	public function __unset($name) {
479 5460
		if ($this->wasWarnedLocked($name)) {
480
			return;
481
		}
482
483 5460
		unset($this->values[$name]);
484 5460
	}
485
486
	/**
487
	 * Save a configuration setting to the database
488
	 *
489
	 * @param string $name  Name (cannot be greater than 255 characters)
490
	 * @param mixed  $value Value
491
	 *
492
	 * @return bool
493
	 */
494 36
	public function save($name, $value) {
495 36
		if ($this->wasWarnedLocked($name)) {
496
			return false;
497
		}
498
499 36
		if (strlen($name) > 255) {
500 1
			if ($this->logger) {
501
				$this->logger->error("The name length for configuration variables cannot be greater than 255");
502
			}
503 1
			return false;
504
		}
505
506 35
		$result = $this->getConfigTable()->set($name, $value);
507
508 35
		$this->__set($name, $value);
509
	
510 35
		return $result;
511
	}
512
513
	/**
514
	 * Removes a configuration setting from the database
515
	 *
516
	 * @param string $name Configuration name
517
	 *
518
	 * @return bool
519
	 */
520 7
	public function remove($name) {
521 7
		if ($this->wasWarnedLocked($name)) {
522
			return false;
523
		}
524
525 7
		$result = $this->getConfigTable()->remove($name);
526
527 7
		unset($this->values[$name]);
528
	
529 7
		return $result;
530
	}
531
532
	/**
533
	 * Log a read-only warning if the name is read-only
534
	 *
535
	 * @param string $name Name
536
	 * @return bool
537
	 */
538 5510
	private function wasWarnedLocked($name) {
0 ignored issues
show
Private method name "Config::wasWarnedLocked" must be prefixed with an underscore
Loading history...
539 5510
		if (!isset($this->locked[$name])) {
540 5510
			return false;
541
		}
542
543 4919
		if ($this->logger) {
544
			$this->logger->warning("The property $name is read-only.");
545
		}
546 4919
		return true;
547
	}
548
549
	/**
550
	 * Set the config table service (must be set)
551
	 *
552
	 * This is a necessary evil until we refactor so that the service provider has no dependencies.
553
	 *
554
	 * @param ConfigTable $table the config table service
555
	 * @return void
556
	 */
557
	public function setConfigTable(ConfigTable $table) {
558
		$this->config_table = $table;
559
	}
560
561
	/**
562
	 * Get the core entity types
563
	 *
564
	 * @return string[]
565
	 */
566 6154
	public static function getEntityTypes() {
567 6154
		return ['group', 'object', 'site', 'user'];
568
	}
569
570
	/**
571
	 * Get the config table API
572
	 *
573
	 * @return ConfigTable
574
	 */
575 37
	private function getConfigTable() {
0 ignored issues
show
Private method name "Config::getConfigTable" must be prefixed with an underscore
Loading history...
576 37
		if (!$this->config_table) {
577 18
			if (!function_exists('_elgg_services')) {
578
				throw new \RuntimeException('setConfigTable() must be called before using API that' .
579
					' uses the database.');
580
			}
581
582 18
			$this->config_table = _elgg_services()->configTable;
583
		}
584
585 37
		return $this->config_table;
586
	}
587
}
588