Passed
Push — release-2.1 ( fa03a5...8f939c )
by Mathias
06:50
created

sm_temp_dir()   B

Complexity

Conditions 11
Paths 25

Size

Total Lines 60
Code Lines 26

Duplication

Lines 0
Ratio 0 %

Importance

Changes 3
Bugs 0 Features 0
Metric Value
cc 11
eloc 26
c 3
b 0
f 0
nop 0
dl 0
loc 60
rs 7.3166
nc 25

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file contains functions that are specifically done by administrators.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Get a list of versions that are currently installed on the server.
21
 *
22
 * @param array $checkFor An array of what to check versions for - can contain one or more of 'gd', 'imagemagick', 'db_server', 'phpa', 'memcache', 'xcache', 'apc', 'php' or 'server'
23
 * @return array An array of versions (keys are same as what was in $checkFor, values are the versions)
24
 */
25
function getServerVersions($checkFor)
26
{
27
	global $txt, $db_connection, $_PHPA, $smcFunc, $cache_accelerator, $cache_memcached, $cacheAPI, $modSettings;
28
29
	loadLanguage('Admin');
30
31
	$versions = array();
32
33
	// Is GD available?  If it is, we should show version information for it too.
34
	if (in_array('gd', $checkFor) && function_exists('gd_info'))
35
	{
36
		$temp = gd_info();
37
		$versions['gd'] = array('title' => $txt['support_versions_gd'], 'version' => $temp['GD Version']);
38
	}
39
40
	// Why not have a look at ImageMagick? If it's installed, we should show version information for it too.
41
	if (in_array('imagemagick', $checkFor) && (class_exists('Imagick') || function_exists('MagickGetVersionString')))
42
	{
43
		if (class_exists('Imagick'))
44
		{
45
			$temp = New Imagick;
46
			$temp2 = $temp->getVersion();
47
			$im_version = $temp2['versionString'];
48
			$extension_version = 'Imagick ' . phpversion('Imagick');
49
		}
50
		else
51
		{
52
			$im_version = MagickGetVersionString();
53
			$extension_version = 'MagickWand ' . phpversion('MagickWand');
54
		}
55
56
		// We already know it's ImageMagick and the website isn't needed...
57
		$im_version = str_replace(array('ImageMagick ', ' https://www.imagemagick.org'), '', $im_version);
58
		$versions['imagemagick'] = array('title' => $txt['support_versions_imagemagick'], 'version' => $im_version . ' (' . $extension_version . ')');
59
	}
60
61
	// Now lets check for the Database.
62
	if (in_array('db_server', $checkFor))
63
	{
64
		db_extend();
65
		if (!isset($db_connection) || $db_connection === false)
66
			trigger_error('getServerVersions(): you need to be connected to the database in order to get its server version', E_USER_NOTICE);
67
		else
68
		{
69
			$versions['db_engine'] = array('title' => sprintf($txt['support_versions_db_engine'], $smcFunc['db_title']), 'version' => '');
70
			$versions['db_engine']['version'] = $smcFunc['db_get_vendor']();
71
72
			$versions['db_server'] = array('title' => sprintf($txt['support_versions_db'], $smcFunc['db_title']), 'version' => '');
73
			$versions['db_server']['version'] = $smcFunc['db_get_version']();
74
		}
75
	}
76
77
	// If we're using memcache we need the server info.
78
	$memcache_version = '???';
79
	if (!empty($cache_accelerator) && ($cache_accelerator == 'memcached' || $cache_accelerator == 'memcache') && !empty($cache_memcached) && !empty($cacheAPI))
80
		$memcache_version = $cacheAPI->getVersion();
81
82
	// Check to see if we have any accelerators installed...
83
	if (in_array('phpa', $checkFor) && isset($_PHPA))
84
		$versions['phpa'] = array('title' => 'ionCube PHP-Accelerator', 'version' => $_PHPA['VERSION']);
85
	if (in_array('apc', $checkFor) && extension_loaded('apc'))
86
		$versions['apc'] = array('title' => 'Alternative PHP Cache', 'version' => phpversion('apc'));
87
	if (in_array('memcache', $checkFor) && function_exists('memcache_set'))
88
		$versions['memcache'] = array('title' => 'Memcached', 'version' => $memcache_version);
89
	if (in_array('xcache', $checkFor) && function_exists('xcache_set'))
90
		$versions['xcache'] = array('title' => 'XCache', 'version' => XCACHE_VERSION);
0 ignored issues
show
Bug introduced by
The constant XCACHE_VERSION was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
91
92
	if (in_array('php', $checkFor))
93
		$versions['php'] = array('title' => 'PHP', 'version' => PHP_VERSION, 'more' => '?action=admin;area=serversettings;sa=phpinfo');
94
95
	if (in_array('server', $checkFor))
96
		$versions['server'] = array('title' => $txt['support_versions_server'], 'version' => $_SERVER['SERVER_SOFTWARE']);
97
98
	return $versions;
99
}
100
101
/**
102
 * Search through source, theme and language files to determine their version.
103
 * Get detailed version information about the physical SMF files on the server.
104
 *
105
 * - the input parameter allows to set whether to include SSI.php and whether
106
 *   the results should be sorted.
107
 * - returns an array containing information on source files, templates and
108
 *   language files found in the default theme directory (grouped by language).
109
 *
110
 * @param array &$versionOptions An array of options. Can contain one or more of 'include_ssi', 'include_subscriptions', 'include_tasks' and 'sort_results'
111
 * @return array An array of file version info.
112
 */
113
function getFileVersions(&$versionOptions)
114
{
115
	global $boarddir, $sourcedir, $settings, $tasksdir;
116
117
	// Default place to find the languages would be the default theme dir.
118
	$lang_dir = $settings['default_theme_dir'] . '/languages';
119
120
	$version_info = array(
121
		'file_versions' => array(),
122
		'default_template_versions' => array(),
123
		'template_versions' => array(),
124
		'default_language_versions' => array(),
125
		'tasks_versions' => array(),
126
	);
127
128
	// Find the version in SSI.php's file header.
129
	if (!empty($versionOptions['include_ssi']) && file_exists($boarddir . '/SSI.php'))
130
	{
131
		$fp = fopen($boarddir . '/SSI.php', 'rb');
132
		$header = fread($fp, 4096);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fread() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

132
		$header = fread(/** @scrutinizer ignore-type */ $fp, 4096);
Loading history...
133
		fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

133
		fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
134
135
		// The comment looks rougly like... that.
136
		if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
137
			$version_info['file_versions']['SSI.php'] = $match[1];
138
		// Not found!  This is bad.
139
		else
140
			$version_info['file_versions']['SSI.php'] = '??';
141
	}
142
143
	// Do the paid subscriptions handler?
144
	if (!empty($versionOptions['include_subscriptions']) && file_exists($boarddir . '/subscriptions.php'))
145
	{
146
		$fp = fopen($boarddir . '/subscriptions.php', 'rb');
147
		$header = fread($fp, 4096);
148
		fclose($fp);
149
150
		// Found it?
151
		if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
152
			$version_info['file_versions']['subscriptions.php'] = $match[1];
153
		// If we haven't how do we all get paid?
154
		else
155
			$version_info['file_versions']['subscriptions.php'] = '??';
156
	}
157
158
	// Load all the files in the Sources directory, except this file and the redirect.
159
	$sources_dir = dir($sourcedir);
160
	while ($entry = $sources_dir->read())
161
	{
162
		if (substr($entry, -4) === '.php' && !is_dir($sourcedir . '/' . $entry) && $entry !== 'index.php')
163
		{
164
			// Read the first 4k from the file.... enough for the header.
165
			$fp = fopen($sourcedir . '/' . $entry, 'rb');
166
			$header = fread($fp, 4096);
167
			fclose($fp);
168
169
			// Look for the version comment in the file header.
170
			if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
171
				$version_info['file_versions'][$entry] = $match[1];
172
			// It wasn't found, but the file was... show a '??'.
173
			else
174
				$version_info['file_versions'][$entry] = '??';
175
		}
176
	}
177
	$sources_dir->close();
178
179
	// Load all the files in the tasks directory.
180
	if (!empty($versionOptions['include_tasks']))
181
	{
182
		$tasks_dir = dir($tasksdir);
183
		while ($entry = $tasks_dir->read())
184
		{
185
			if (substr($entry, -4) === '.php' && !is_dir($tasksdir . '/' . $entry) && $entry !== 'index.php')
186
			{
187
				// Read the first 4k from the file.... enough for the header.
188
				$fp = fopen($tasksdir . '/' . $entry, 'rb');
189
				$header = fread($fp, 4096);
190
				fclose($fp);
191
192
				// Look for the version comment in the file header.
193
				if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
194
					$version_info['tasks_versions'][$entry] = $match[1];
195
				// It wasn't found, but the file was... show a '??'.
196
				else
197
					$version_info['tasks_versions'][$entry] = '??';
198
			}
199
		}
200
		$tasks_dir->close();
201
	}
202
203
	// Load all the files in the default template directory - and the current theme if applicable.
204
	$directories = array('default_template_versions' => $settings['default_theme_dir']);
205
	if ($settings['theme_id'] != 1)
206
		$directories += array('template_versions' => $settings['theme_dir']);
207
208
	foreach ($directories as $type => $dirname)
209
	{
210
		$this_dir = dir($dirname);
211
		while ($entry = $this_dir->read())
212
		{
213
			if (substr($entry, -12) == 'template.php' && !is_dir($dirname . '/' . $entry))
214
			{
215
				// Read the first 768 bytes from the file.... enough for the header.
216
				$fp = fopen($dirname . '/' . $entry, 'rb');
217
				$header = fread($fp, 768);
218
				fclose($fp);
219
220
				// Look for the version comment in the file header.
221
				if (preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $header, $match) == 1)
222
					$version_info[$type][$entry] = $match[1];
223
				// It wasn't found, but the file was... show a '??'.
224
				else
225
					$version_info[$type][$entry] = '??';
226
			}
227
		}
228
		$this_dir->close();
229
	}
230
231
	// Load up all the files in the default language directory and sort by language.
232
	$this_dir = dir($lang_dir);
233
	while ($entry = $this_dir->read())
234
	{
235
		if (substr($entry, -4) == '.php' && $entry != 'index.php' && !is_dir($lang_dir . '/' . $entry))
236
		{
237
			// Read the first 768 bytes from the file.... enough for the header.
238
			$fp = fopen($lang_dir . '/' . $entry, 'rb');
239
			$header = fread($fp, 768);
240
			fclose($fp);
241
242
			// Split the file name off into useful bits.
243
			list ($name, $language) = explode('.', $entry);
244
245
			// Look for the version comment in the file header.
246
			if (preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*' . preg_quote($name, '~') . '(?:[\s]{2}|\*/)~i', $header, $match) == 1)
247
				$version_info['default_language_versions'][$language][$name] = $match[1];
248
			// It wasn't found, but the file was... show a '??'.
249
			else
250
				$version_info['default_language_versions'][$language][$name] = '??';
251
		}
252
	}
253
	$this_dir->close();
254
255
	// Sort the file versions by filename.
256
	if (!empty($versionOptions['sort_results']))
257
	{
258
		ksort($version_info['file_versions']);
259
		ksort($version_info['default_template_versions']);
260
		ksort($version_info['template_versions']);
261
		ksort($version_info['default_language_versions']);
262
		ksort($version_info['tasks_versions']);
263
264
		// For languages sort each language too.
265
		foreach ($version_info['default_language_versions'] as $language => $dummy)
266
			ksort($version_info['default_language_versions'][$language]);
267
	}
268
	return $version_info;
269
}
270
271
/**
272
 * Update the Settings.php file.
273
 *
274
 * The most important function in this file for mod makers happens to be the
275
 * updateSettingsFile() function, but it shouldn't be used often anyway.
276
 *
277
 * - Updates the Settings.php file with the changes supplied in config_vars.
278
 *
279
 * - Expects config_vars to be an associative array, with the keys as the
280
 *   variable names in Settings.php, and the values the variable values.
281
 *
282
 * - Correctly formats the values using smf_var_export().
283
 *
284
 * - Restores standard formatting of the file, if $rebuild is true.
285
 *
286
 * - Checks for changes to db_last_error and passes those off to a separate
287
 *   handler.
288
 *
289
 * - Creates a backup file and will use it should the writing of the
290
 *   new settings file fail.
291
 *
292
 * - Tries to intelligently trim quotes and remove slashes from string values.
293
 *   This is done for backwards compatibility purposes (old versions of this
294
 *   function expected strings to have been manually escaped and quoted). This
295
 *   behaviour can be controlled by the $keep_quotes parameter.
296
 *
297
 * @param array $config_vars An array of one or more variables to update.
298
 * @param bool|null $keep_quotes Whether to strip slashes & trim quotes from string values. Defaults to auto-detection.
299
 * @param bool $rebuild If true, attempts to rebuild with standard format. Default false.
300
 * @return bool True on success, false on failure.
301
 */
302
function updateSettingsFile($config_vars, $keep_quotes = null, $rebuild = false)
303
{
304
	// In this function we intentionally don't declare any global variables.
305
	// This allows us to work with everything cleanly.
306
307
	static $mtime;
308
309
	// Should we try to unescape the strings?
310
	if (empty($keep_quotes))
311
	{
312
		foreach ($config_vars as $var => $val)
313
		{
314
			if (is_string($val) && ($keep_quotes === false || strpos($val, '\'') === 0 && strrpos($val, '\'') === strlen($val) - 1))
315
				$config_vars[$var] = trim(stripcslashes($val), '\'');
316
		}
317
	}
318
319
	// Updating the db_last_error, then don't mess around with Settings.php
320
	if (isset($config_vars['db_last_error']))
321
	{
322
		updateDbLastError($config_vars['db_last_error']);
323
324
		if (count($config_vars) === 1 && empty($rebuild))
325
			return true;
326
327
		// Make sure we delete this from Settings.php, if present.
328
		$config_vars['db_last_error'] = 0;
329
	}
330
331
	// Rebuilding should not be undertaken lightly, so we're picky about the parameter.
332
	if (!is_bool($rebuild))
0 ignored issues
show
introduced by
The condition is_bool($rebuild) is always true.
Loading history...
333
		$rebuild = false;
334
335
	$mtime = isset($mtime) ? (int) $mtime : (defined('TIME_START') ? TIME_START : $_SERVER['REQUEST_TIME']);
336
337
	/*****************
338
	 * PART 1: Setup *
339
	 *****************/
340
341
	// Typically Settings.php is in $boarddir, but maybe this is a custom setup...
342
	foreach (get_included_files() as $settingsFile)
343
		if (basename($settingsFile) === 'Settings.php')
344
			break;
345
346
	// Fallback in case Settings.php isn't loaded (e.g. while installing)
347
	if (basename($settingsFile) !== 'Settings.php')
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $settingsFile seems to be defined by a foreach iteration on line 342. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
348
		$settingsFile = (!empty($GLOBALS['boarddir']) && @realpath($GLOBALS['boarddir']) ? $GLOBALS['boarddir'] : (!empty($_SERVER['SCRIPT_FILENAME']) ? dirname($_SERVER['SCRIPT_FILENAME']) : dirname(__DIR__))) . '/Settings.php';
349
350
	// File not found? Attempt an emergency on-the-fly fix!
351
	if (!file_exists($settingsFile))
352
		@touch($settingsFile);
353
354
	// When was Settings.php last changed?
355
	$last_settings_change = filemtime($settingsFile);
356
357
	// Get the current values of everything in Settings.php.
358
	$settings_vars = get_current_settings($mtime, $settingsFile);
359
360
	// If Settings.php is empty for some reason, see if we can use the backup.
361
	if (empty($settings_vars) && file_exists(dirname($settingsFile) . '/Settings_bak.php'))
362
		$settings_vars = get_current_settings($mtime, dirname($settingsFile) . '/Settings_bak.php');
363
364
	// False means there was a problem with the file and we can't safely continue.
365
	if ($settings_vars === false)
366
		return false;
367
368
	// It works best to set everything afresh.
369
	$new_settings_vars = array_merge($settings_vars, $config_vars);
370
371
	// Are we using UTF-8?
372
	$utf8 = isset($GLOBALS['context']['utf8']) ? $GLOBALS['context']['utf8'] : (isset($GLOBALS['utf8']) ? $GLOBALS['utf8'] : (isset($settings_vars['db_character_set']) ? $settings_vars['db_character_set'] === 'utf8' : false));
0 ignored issues
show
Unused Code introduced by
The assignment to $utf8 is dead and can be removed.
Loading history...
373
374
	/*
375
	 * A big, fat array to define properties of all the Settings.php variables.
376
	 *
377
	 * - String keys are used to identify actual variables.
378
	 *
379
	 * - Integer keys are used for content not connected to any particular
380
	 *   variable, such as code blocks or the license block.
381
	 *
382
	 * - The content of the 'text' element is simply printed out, if it is used
383
	 *   at all. Use it for comments or to insert code blocks, etc.
384
	 *
385
	 * - The 'default' element, not surprisingly, gives a default value for
386
	 *   the variable.
387
	 *
388
	 * - The 'type' element defines the expected variable type or types. If
389
	 *   more than one type is allowed, this should be an array listing them.
390
	 *   Types should match the possible types returned by gettype().
391
	 *
392
	 * - If 'raw_default' is true, the default should be printed directly,
393
	 *   rather than being handled as a string. Use it if the default contains
394
	 *   code, e.g. 'dirname(__FILE__)'
395
	 *
396
	 * - If 'required' is true and a value for the variable is undefined,
397
	 *   the update will be aborted. (The only exception is during the SMF
398
	 *   installation process.)
399
	 *
400
	 * - If 'auto_delete' is 1 or true and the variable is empty, the variable
401
	 *   will be deleted from Settings.php. If 'auto_delete' is 0/false/null,
402
	 *   the variable will never be deleted. If 'auto_delete' is 2, behaviour
403
	 *   depends on $rebuild: if $rebuild is true, 'auto_delete' == 2 behaves
404
	 *   like 'auto_delete' == 1; if $rebuild is false, 'auto_delete' == 2
405
	 *   behaves like 'auto_delete' == 0.
406
	 *
407
	 * - The optional 'search_pattern' element defines a custom regular
408
	 *   expression to search for the existing entry in the file. This is
409
	 *   primarily useful for code blocks rather than variables.
410
	 *
411
	 * - The optional 'replace_pattern' element defines a custom regular
412
	 *   expression to decide where the replacement entry should be inserted.
413
	 *   Note: 'replace_pattern' should be avoided unless ABSOLUTELY necessary.
414
	 */
415
	$settings_defs = array(
416
		array(
417
			'text' => implode("\n", array(
418
				'',
419
				'/**',
420
				' * The settings file contains all of the basic settings that need to be present when a database/cache is not available.',
421
				' *',
422
				' * Simple Machines Forum (SMF)',
423
				' *',
424
				' * @package SMF',
425
				' * @author Simple Machines https://www.simplemachines.org',
426
				' * @copyright ' . SMF_SOFTWARE_YEAR . ' Simple Machines and individual contributors',
427
				' * @license https://www.simplemachines.org/about/smf/license.php BSD',
428
				' *',
429
				' * @version ' . SMF_VERSION,
430
				' */',
431
				'',
432
			)),
433
			'search_pattern' => '~/\*\*.*?@package\h+SMF\b.*?\*/\n{0,2}~s',
434
		),
435
		'maintenance' => array(
436
			'text' => implode("\n", array(
437
				'',
438
				'########## Maintenance ##########',
439
				'/**',
440
				' * The maintenance "mode"',
441
				' * Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you\'ll have to make it 0 again manually!)',
442
				' * 0 is default and disables maintenance mode.',
443
				' *',
444
				' * @var int 0, 1, 2',
445
				' * @global int $maintenance',
446
				' */',
447
			)),
448
			'default' => 0,
449
			'type' => 'integer',
450
		),
451
		'mtitle' => array(
452
			'text' => implode("\n", array(
453
				'/**',
454
				' * Title for the Maintenance Mode message.',
455
				' *',
456
				' * @var string',
457
				' * @global int $mtitle',
458
				' */',
459
			)),
460
			'default' => 'Maintenance Mode',
461
			'type' => 'string',
462
		),
463
		'mmessage' => array(
464
			'text' => implode("\n", array(
465
				'/**',
466
				' * Description of why the forum is in maintenance mode.',
467
				' *',
468
				' * @var string',
469
				' * @global string $mmessage',
470
				' */',
471
			)),
472
			'default' => 'Okay faithful users...we\'re attempting to restore an older backup of the database...news will be posted once we\'re back!',
473
			'type' => 'string',
474
		),
475
		'mbname' => array(
476
			'text' => implode("\n", array(
477
				'',
478
				'########## Forum Info ##########',
479
				'/**',
480
				' * The name of your forum.',
481
				' *',
482
				' * @var string',
483
				' */',
484
			)),
485
			'default' => 'My Community',
486
			'type' => 'string',
487
		),
488
		'language' => array(
489
			'text' => implode("\n", array(
490
				'/**',
491
				' * The default language file set for the forum.',
492
				' *',
493
				' * @var string',
494
				' */',
495
			)),
496
			'default' => 'english',
497
			'type' => 'string',
498
		),
499
		'boardurl' => array(
500
			'text' => implode("\n", array(
501
				'/**',
502
				' * URL to your forum\'s folder. (without the trailing /!)',
503
				' *',
504
				' * @var string',
505
				' */',
506
			)),
507
			'default' => 'http://127.0.0.1/smf',
508
			'type' => 'string',
509
		),
510
		'webmaster_email' => array(
511
			'text' => implode("\n", array(
512
				'/**',
513
				' * Email address to send emails from. (like [email protected].)',
514
				' *',
515
				' * @var string',
516
				' */',
517
			)),
518
			'default' => '[email protected]',
519
			'type' => 'string',
520
		),
521
		'cookiename' => array(
522
			'text' => implode("\n", array(
523
				'/**',
524
				' * Name of the cookie to set for authentication.',
525
				' *',
526
				' * @var string',
527
				' */',
528
			)),
529
			'default' => 'SMFCookie11',
530
			'type' => 'string',
531
		),
532
		'auth_secret' => array(
533
			'text' => implode("\n", array(
534
				'/**',
535
				' * Secret key used to create and verify cookies, tokens, etc.',
536
				' * Do not change this unless absolutely necessary, and NEVER share it.',
537
				' *',
538
				' * Note: Changing this will immediately log out all members of your forum',
539
				' * and break the token-based links in all previous email notifications,',
540
				' * among other possible effects.',
541
				' *',
542
				' * @var string',
543
				' */',
544
			)),
545
			'default' => null,
546
			'auto_delete' => 1,
547
		),
548
		'db_type' => array(
549
			'text' => implode("\n", array(
550
				'',
551
				'########## Database Info ##########',
552
				'/**',
553
				' * The database type',
554
				' * Default options: mysql, postgresql',
555
				' *',
556
				' * @var string',
557
				' */',
558
			)),
559
			'default' => 'mysql',
560
			'type' => 'string',
561
		),
562
		'db_port' => array(
563
			'text' => implode("\n", array(
564
				'/**',
565
				' * The database port',
566
				' * 0 to use default port for the database type',
567
				' *',
568
				' * @var int',
569
				' */',
570
			)),
571
			'default' => 0,
572
			'type' => 'integer',
573
		),
574
		'db_server' => array(
575
			'text' => implode("\n", array(
576
				'/**',
577
				' * The server to connect to (or a Unix socket)',
578
				' *',
579
				' * @var string',
580
				' */',
581
			)),
582
			'default' => 'localhost',
583
			'required' => true,
584
			'type' => 'string',
585
		),
586
		'db_name' => array(
587
			'text' => implode("\n", array(
588
				'/**',
589
				' * The database name',
590
				' *',
591
				' * @var string',
592
				' */',
593
			)),
594
			'default' => 'smf',
595
			'required' => true,
596
			'type' => 'string',
597
		),
598
		'db_user' => array(
599
			'text' => implode("\n", array(
600
				'/**',
601
				' * Database username',
602
				' *',
603
				' * @var string',
604
				' */',
605
			)),
606
			'default' => 'root',
607
			'required' => true,
608
			'type' => 'string',
609
		),
610
		'db_passwd' => array(
611
			'text' => implode("\n", array(
612
				'/**',
613
				' * Database password',
614
				' *',
615
				' * @var string',
616
				' */',
617
			)),
618
			'default' => '',
619
			'required' => true,
620
			'type' => 'string',
621
		),
622
		'ssi_db_user' => array(
623
			'text' => implode("\n", array(
624
				'/**',
625
				' * Database user for when connecting with SSI',
626
				' *',
627
				' * @var string',
628
				' */',
629
			)),
630
			'default' => '',
631
			'type' => 'string',
632
		),
633
		'ssi_db_passwd' => array(
634
			'text' => implode("\n", array(
635
				'/**',
636
				' * Database password for when connecting with SSI',
637
				' *',
638
				' * @var string',
639
				' */',
640
			)),
641
			'default' => '',
642
			'type' => 'string',
643
		),
644
		'db_prefix' => array(
645
			'text' => implode("\n", array(
646
				'/**',
647
				' * A prefix to put in front of your table names.',
648
				' * This helps to prevent conflicts',
649
				' *',
650
				' * @var string',
651
				' */',
652
			)),
653
			'default' => 'smf_',
654
			'required' => true,
655
			'type' => 'string',
656
		),
657
		'db_persist' => array(
658
			'text' => implode("\n", array(
659
				'/**',
660
				' * Use a persistent database connection',
661
				' *',
662
				' * @var bool',
663
				' */',
664
			)),
665
			'default' => false,
666
			'type' => 'boolean',
667
		),
668
		'db_error_send' => array(
669
			'text' => implode("\n", array(
670
				'/**',
671
				' * Send emails on database connection error',
672
				' *',
673
				' * @var bool',
674
				' */',
675
			)),
676
			'default' => false,
677
			'type' => 'boolean',
678
		),
679
		'db_mb4' => array(
680
			'text' => implode("\n", array(
681
				'/**',
682
				' * Override the default behavior of the database layer for mb4 handling',
683
				' * null keep the default behavior untouched',
684
				' *',
685
				' * @var null|bool',
686
				' */',
687
			)),
688
			'default' => null,
689
			'type' => array('NULL', 'boolean'),
690
		),
691
		'cache_accelerator' => array(
692
			'text' => implode("\n", array(
693
				'',
694
				'########## Cache Info ##########',
695
				'/**',
696
				' * Select a cache system. You want to leave this up to the cache area of the admin panel for',
697
				' * proper detection of apc, memcached, output_cache, smf, or xcache',
698
				' * (you can add more with a mod).',
699
				' *',
700
				' * @var string',
701
				' */',
702
			)),
703
			'default' => '',
704
			'type' => 'string',
705
		),
706
		'cache_enable' => array(
707
			'text' => implode("\n", array(
708
				'/**',
709
				' * The level at which you would like to cache. Between 0 (off) through 3 (cache a lot).',
710
				' *',
711
				' * @var int',
712
				' */',
713
			)),
714
			'default' => 0,
715
			'type' => 'integer',
716
		),
717
		'cache_memcached' => array(
718
			'text' => implode("\n", array(
719
				'/**',
720
				' * This is only used for memcache / memcached. Should be a string of \'server:port,server:port\'',
721
				' *',
722
				' * @var array',
723
				' */',
724
			)),
725
			'default' => '',
726
			'type' => 'string',
727
		),
728
		'cachedir' => array(
729
			'text' => implode("\n", array(
730
				'/**',
731
				' * This is only for the \'smf\' file cache system. It is the path to the cache directory.',
732
				' * It is also recommended that you place this in /tmp/ if you are going to use this.',
733
				' *',
734
				' * @var string',
735
				' */',
736
			)),
737
			'default' => 'dirname(__FILE__) . \'/cache\'',
738
			'raw_default' => true,
739
			'type' => 'string',
740
		),
741
		'image_proxy_enabled' => array(
742
			'text' => implode("\n", array(
743
				'',
744
				'########## Image Proxy ##########',
745
				'# This is done entirely in Settings.php to avoid loading the DB while serving the images',
746
				'/**',
747
				' * Whether the proxy is enabled or not',
748
				' *',
749
				' * @var bool',
750
				' */',
751
			)),
752
			'default' => true,
753
			'type' => 'boolean',
754
		),
755
		'image_proxy_secret' => array(
756
			'text' => implode("\n", array(
757
				'/**',
758
				' * Secret key to be used by the proxy',
759
				' *',
760
				' * @var string',
761
				' */',
762
			)),
763
			'default' => 'smfisawesome',
764
			'type' => 'string',
765
		),
766
		'image_proxy_maxsize' => array(
767
			'text' => implode("\n", array(
768
				'/**',
769
				' * Maximum file size (in KB) for individual files',
770
				' *',
771
				' * @var int',
772
				' */',
773
			)),
774
			'default' => 5192,
775
			'type' => 'integer',
776
		),
777
		'boarddir' => array(
778
			'text' => implode("\n", array(
779
				'',
780
				'########## Directories/Files ##########',
781
				'# Note: These directories do not have to be changed unless you move things.',
782
				'/**',
783
				' * The absolute path to the forum\'s folder. (not just \'.\'!)',
784
				' *',
785
				' * @var string',
786
				' */',
787
			)),
788
			'default' => 'dirname(__FILE__)',
789
			'raw_default' => true,
790
			'type' => 'string',
791
		),
792
		'sourcedir' => array(
793
			'text' => implode("\n", array(
794
				'/**',
795
				' * Path to the Sources directory.',
796
				' *',
797
				' * @var string',
798
				' */',
799
			)),
800
			'default' => 'dirname(__FILE__) . \'/Sources\'',
801
			'raw_default' => true,
802
			'type' => 'string',
803
		),
804
		'packagesdir' => array(
805
			'text' => implode("\n", array(
806
				'/**',
807
				' * Path to the Packages directory.',
808
				' *',
809
				' * @var string',
810
				' */',
811
			)),
812
			'default' => 'dirname(__FILE__) . \'/Packages\'',
813
			'raw_default' => true,
814
			'type' => 'string',
815
		),
816
		'tasksdir' => array(
817
			'text' => implode("\n", array(
818
				'/**',
819
				' * Path to the tasks directory.',
820
				' *',
821
				' * @var string',
822
				' */',
823
			)),
824
			'default' => '$sourcedir . \'/tasks\'',
825
			'raw_default' => true,
826
			'type' => 'string',
827
		),
828
		array(
829
			'text' => implode("\n", array(
830
				'',
831
				'# Make sure the paths are correct... at least try to fix them.',
832
				'if (!is_dir(realpath($boarddir)) && file_exists(dirname(__FILE__) . \'/agreement.txt\'))',
833
				'	$boarddir = dirname(__FILE__);',
834
				'if (!is_dir(realpath($sourcedir)) && is_dir($boarddir . \'/Sources\'))',
835
				'	$sourcedir = $boarddir . \'/Sources\';',
836
				'if (!is_dir(realpath($tasksdir)) && is_dir($sourcedir . \'/tasks\'))',
837
				'	$tasksdir = $sourcedir . \'/tasks\';',
838
				'if (!is_dir(realpath($packagesdir)) && is_dir($boarddir . \'/Packages\'))',
839
				'	$packagesdir = $boarddir . \'/Packages\';',
840
				'if (!is_dir(realpath($cachedir)) && is_dir($boarddir . \'/cache\'))',
841
				'	$cachedir = $boarddir . \'/cache\';',
842
			)),
843
			'search_pattern' => '~\n?(#[^\n]+)?(?:\n\h*if\s*\((?:\!file_exists\(\$(?>boarddir|sourcedir|tasksdir|packagesdir|cachedir)\)|\!is_dir\(realpath\(\$(?>boarddir|sourcedir|tasksdir|packagesdir|cachedir)\)\))[^;]+\n\h*\$(?>boarddir|sourcedir|tasksdir|packagesdir|cachedir)[^\n]+;)+~sm',
844
		),
845
		'db_character_set' => array(
846
			'text' => implode("\n", array(
847
				'',
848
				'######### Legacy Settings #########',
849
				'# UTF-8 is now the only character set supported in 2.1.',
850
			)),
851
			'default' => 'utf8',
852
			'type' => 'string',
853
		),
854
		'db_show_debug' => array(
855
			'text' => implode("\n", array(
856
				'',
857
				'######### Developer Settings #########',
858
				'# Show debug info.',
859
			)),
860
			'default' => false,
861
			'auto_delete' => 2,
862
			'type' => 'boolean',
863
		),
864
		array(
865
			'text' => implode("\n", array(
866
				'',
867
				'########## Error-Catching ##########',
868
				'# Note: You shouldn\'t touch these settings.',
869
				'if (file_exists((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\'))',
870
				'	include((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\');',
871
				'',
872
				'if (!isset($db_last_error))',
873
				'{',
874
				'	// File does not exist so lets try to create it',
875
				'	file_put_contents((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\', \'<\' . \'?\' . "php\n" . \'$db_last_error = 0;\' . "\n" . \'?\' . \'>\');',
876
				'	$db_last_error = 0;',
877
				'}',
878
			)),
879
			// Designed to match both 2.0 and 2.1 versions of this code.
880
			'search_pattern' => '~\n?#+ Error.Catching #+\n[^\n]*?settings\.\n(?:\$db_last_error = \d{1,11};|if \(file_exists.*?\$db_last_error = 0;(?' . '>\s*}))(?=\n|\?' . '>|$)~s',
881
		),
882
		// Temporary variable used during the upgrade process.
883
		'upgradeData' => array(
884
			'default' => '',
885
			'auto_delete' => 1,
886
			'type' => 'string',
887
		),
888
		// This should be removed if found.
889
		'db_last_error' => array(
890
			'default' => 0,
891
			'auto_delete' => 1,
892
			'type' => 'integer',
893
		),
894
	);
895
896
	// Allow mods the option to define comments, defaults, etc., for their settings.
897
	// Check if function exists, in case we are calling from installer or upgrader.
898
	if (function_exists('call_integration_hook'))
899
		call_integration_hook('integrate_update_settings_file', array(&$settings_defs));
900
901
	// If Settings.php is empty or invalid, try to recover using whatever is in $GLOBALS.
902
	if ($settings_vars === array())
903
	{
904
		foreach ($settings_defs as $var => $setting_def)
905
			if (isset($GLOBALS[$var]))
906
				$settings_vars[$var] = $GLOBALS[$var];
907
908
		$new_settings_vars = array_merge($settings_vars, $config_vars);
909
	}
910
911
	// During install/upgrade, don't set anything until we're ready for it.
912
	if (defined('SMF_INSTALLING') && empty($rebuild))
913
	{
914
		foreach ($settings_defs as $var => $setting_def)
915
			if (!in_array($var, array_keys($new_settings_vars)) && !is_int($var))
916
				unset($settings_defs[$var]);
917
	}
918
919
	/*******************************
920
	 * PART 2: Build substitutions *
921
	 *******************************/
922
923
	$type_regex = array(
924
		'string' =>
925
			'(?:' .
926
				// match the opening quotation mark...
927
				'(["\'])' .
928
				// then any number of other characters or escaped quotation marks...
929
				'(?:.(?!\\1)|\\\(?=\\1))*.?' .
930
				// then the closing quotation mark.
931
				'\\1' .
932
				// Maybe there's a second string concatenated to this one.
933
				'(?:\s*\.\s*)*' .
934
			')+',
935
		// Some numeric values might have been stored as strings.
936
		'integer' =>  '["\']?[+-]?\d+["\']?',
937
		'double' =>  '["\']?[+-]?\d+\.\d+([Ee][+-]\d+)?["\']?',
938
		// Some boolean values might have been stored as integers.
939
		'boolean' =>  '(?i:TRUE|FALSE|(["\']?)[01]\b\\1)',
940
		'NULL' =>  '(?i:NULL)',
941
		// These use a PCRE subroutine to match nested arrays.
942
		'array' =>  'array\s*(\((?>[^()]|(?1))*\))',
943
		'object' =>  '\w+::__set_state\(array\s*(\((?>[^()]|(?1))*\))\)',
944
	);
945
946
	/*
947
	 * The substitutions take place in one of two ways:
948
	 *
949
	 *  1: The search_pattern regex finds a string in Settings.php, which is
950
	 *     temporarily replaced by a placeholder. Once all the placeholders
951
	 *     have been inserted, each is replaced by the final replacement string
952
	 *     that we want to use. This is the standard method.
953
	 *
954
	 *  2: The search_pattern regex finds a string in Settings.php, which is
955
	 *     then deleted by replacing it with an empty placeholder. Then after
956
	 *     all the real placeholders have been dealt with, the replace_pattern
957
	 *     regex finds where to insert the final replacement string that we
958
	 *     want to use. This method is for special cases.
959
	 */
960
	$prefix = mt_rand() . '-';
961
	$neg_index = -1;
962
	$substitutions = array(
963
		$neg_index-- => array(
964
			'search_pattern' => '~^\s*<\?(php\b)?\n?~',
965
			'placeholder' => '',
966
			'replace_pattern' => '~^~',
967
			'replacement' => '<' . "?php\n",
968
		),
969
		$neg_index-- => array(
970
			'search_pattern' => '~\S\K\s*(\?' . '>)?\s*$~',
971
			'placeholder' => "\n" . md5($prefix . '?' . '>'),
972
			'replacement' => "\n\n?" . '>',
973
		),
974
		// Remove the code that redirects to the installer.
975
		$neg_index-- => array(
976
			'search_pattern' => '~^if\s*\(file_exists\(dirname\(__FILE__\)\s*\.\s*\'/install\.php\'\)\)\s*(?:({(?>[^{}]|(?1))*})\h*|header(\((?' . '>[^()]|(?2))*\));\n)~m',
977
			'placeholder' => '',
978
		),
979
	);
980
981
	if (defined('SMF_INSTALLING'))
982
		$substitutions[$neg_index--] = array(
983
			'search_pattern' => '~/\*.*?SMF\s+1\.\d.*?\*/~s',
984
			'placeholder' => '',
985
		);
986
987
	foreach ($settings_defs as $var => $setting_def)
988
	{
989
		$placeholder = md5($prefix . $var);
990
		$replacement = '';
991
992
		if (!empty($setting_def['text']))
993
		{
994
			// Special handling for the license block: always at the beginning.
995
			if (strpos($setting_def['text'], "* @package SMF\n") !== false)
996
			{
997
				$substitutions[$var]['search_pattern'] = $setting_def['search_pattern'];
998
				$substitutions[$var]['placeholder'] = '';
999
				$substitutions[-1]['replacement'] .= $setting_def['text'] . "\n";
1000
			}
1001
			// Special handling for the Error-Catching block: always at the end.
1002
			elseif (strpos($setting_def['text'], 'Error-Catching') !== false)
1003
			{
1004
				$errcatch_var = $var;
0 ignored issues
show
Unused Code introduced by
The assignment to $errcatch_var is dead and can be removed.
Loading history...
1005
				$substitutions[$var]['search_pattern'] = $setting_def['search_pattern'];
1006
				$substitutions[$var]['placeholder'] = '';
1007
				$substitutions[-2]['replacement'] = "\n" . $setting_def['text'] . $substitutions[-2]['replacement'];
1008
			}
1009
			// The text is the whole thing (code blocks, etc.)
1010
			elseif (is_int($var))
1011
			{
1012
				// Remember the path correcting code for later.
1013
				if (strpos($setting_def['text'], '# Make sure the paths are correct') !== false)
1014
					$pathcode_var = $var;
1015
1016
				if (!empty($setting_def['search_pattern']))
1017
					$substitutions[$var]['search_pattern'] = $setting_def['search_pattern'];
1018
				else
1019
					$substitutions[$var]['search_pattern'] = '~' . preg_quote($setting_def['text'], '~') . '~';
1020
1021
				$substitutions[$var]['placeholder'] = $placeholder;
1022
1023
				$replacement .= $setting_def['text'] . "\n";
1024
			}
1025
			// We only include comments when rebuilding.
1026
			elseif (!empty($rebuild))
1027
				$replacement .= $setting_def['text'] . "\n";
1028
		}
1029
1030
		if (is_string($var))
1031
		{
1032
			// Ensure the value is good.
1033
			if (in_array($var, array_keys($new_settings_vars)))
1034
			{
1035
				// Objects without a __set_state method need a fallback.
1036
				if (is_object($new_settings_vars[$var]) && !method_exists($new_settings_vars[$var], '__set_state'))
1037
				{
1038
					if (method_exists($new_settings_vars[$var], '__toString'))
1039
						$new_settings_vars[$var] = (string) $new_settings_vars[$var];
1040
					else
1041
						$new_settings_vars[$var] = (array) $new_settings_vars[$var];
1042
				}
1043
1044
				// Normalize the type if necessary.
1045
				if (isset($setting_def['type']))
1046
				{
1047
					$expected_types = (array) $setting_def['type'];
1048
					$var_type = gettype($new_settings_vars[$var]);
1049
1050
					// Variable is not of an expected type.
1051
					if (!in_array($var_type, $expected_types))
1052
					{
1053
						// Passed in an unexpected array.
1054
						if ($var_type == 'array')
1055
						{
1056
							$temp = reset($new_settings_vars[$var]);
1057
1058
							// Use the first element if there's only one and it is a scalar.
1059
							if (count($new_settings_vars[$var]) === 1 && is_scalar($temp))
1060
								$new_settings_vars[$var] = $temp;
1061
1062
							// Or keep the old value, if that is good.
1063
							elseif (isset($settings_vars[$var]) && in_array(gettype($settings_vars[$var]), $expected_types))
1064
								$new_settings_vars[$var] = $settings_vars[$var];
1065
1066
							// Fall back to the default
1067
							else
1068
								$new_settings_vars[$var] = $setting_def['default'];
1069
						}
1070
1071
						// Cast it to whatever type was expected.
1072
						// Note: the order of the types in this loop matters.
1073
						foreach (array('boolean', 'integer', 'double', 'string', 'array') as $to_type)
1074
						{
1075
							if (in_array($to_type, $expected_types))
1076
							{
1077
								settype($new_settings_vars[$var], $to_type);
1078
								break;
1079
							}
1080
						}
1081
					}
1082
				}
1083
			}
1084
			// Abort if a required one is undefined (unless we're installing).
1085
			elseif (!empty($setting_def['required']) && !defined('SMF_INSTALLING'))
1086
				return false;
1087
1088
			// Create the search pattern.
1089
			if (!empty($setting_def['search_pattern']))
1090
				$substitutions[$var]['search_pattern'] = $setting_def['search_pattern'];
1091
			else
1092
			{
1093
				$var_pattern = array();
1094
1095
				if (isset($setting_def['type']))
1096
				{
1097
					foreach ((array) $setting_def['type'] as $type)
1098
						$var_pattern[] = $type_regex[$type];
1099
				}
1100
1101
				if (in_array($var, array_keys($config_vars)))
1102
				{
1103
					$var_pattern[] = @$type_regex[gettype($config_vars[$var])];
1104
1105
					if (is_string($config_vars[$var]) && strpos($config_vars[$var], dirname($settingsFile)) === 0)
1106
						$var_pattern[] = '(?:__DIR__|dirname\(__FILE__\)) . \'' . (preg_quote(str_replace(dirname($settingsFile), '', $config_vars[$var]), '~')) . '\'';
1107
				}
1108
1109
				if (in_array($var, array_keys($settings_vars)))
1110
				{
1111
					$var_pattern[] = @$type_regex[gettype($settings_vars[$var])];
1112
1113
					if (is_string($settings_vars[$var]) && strpos($settings_vars[$var], dirname($settingsFile)) === 0)
1114
						$var_pattern[] = '(?:__DIR__|dirname\(__FILE__\)) . \'' . (preg_quote(str_replace(dirname($settingsFile), '', $settings_vars[$var]), '~')) . '\'';
1115
				}
1116
1117
				if (!empty($setting_def['raw_default']) && $setting_def['default'] !== '')
1118
				{
1119
					$var_pattern[] = preg_replace('/\s+/', '\s+', preg_quote($setting_def['default'], '~'));
1120
1121
					if (strpos($setting_def['default'], 'dirname(__FILE__)') !== false)
1122
						$var_pattern[] = preg_replace('/\s+/', '\s+', preg_quote(str_replace('dirname(__FILE__)', '__DIR__', $setting_def['default']), '~'));
1123
1124
					if (strpos($setting_def['default'], '__DIR__') !== false)
1125
						$var_pattern[] = preg_replace('/\s+/', '\s+', preg_quote(str_replace('__DIR__', 'dirname(__FILE__)', $setting_def['default']), '~'));
1126
				}
1127
1128
				$var_pattern = array_unique($var_pattern);
1129
1130
				$var_pattern = count($var_pattern) > 1 ? '(?:' . (implode('|', $var_pattern)) . ')' : $var_pattern[0];
1131
1132
				$substitutions[$var]['search_pattern'] = '~(?<=^|\s)\h*\$' . preg_quote($var, '~') . '\s*=\s*' . $var_pattern . ';~' . (!empty($context['utf8']) ? 'u' : '');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $context seems to never exist and therefore empty should always be true.
Loading history...
1133
			}
1134
1135
			// Next create the placeholder or replace_pattern.
1136
			if (!empty($setting_def['replace_pattern']))
1137
				$substitutions[$var]['replace_pattern'] = $setting_def['replace_pattern'];
1138
			else
1139
				$substitutions[$var]['placeholder'] = $placeholder;
1140
1141
			// Now create the replacement.
1142
			// A setting to delete.
1143
			if (!empty($setting_def['auto_delete']) && empty($new_settings_vars[$var]))
1144
			{
1145
				if ($setting_def['auto_delete'] === 2 && empty($rebuild) && in_array($var, array_keys($new_settings_vars)))
1146
				{
1147
					$replacement .= '$' . $var . ' = ' . ($new_settings_vars[$var] === $setting_def['default'] && !empty($setting_def['raw_default']) ? sprintf($new_settings_vars[$var]) : smf_var_export($new_settings_vars[$var], true)) . ";";
0 ignored issues
show
Unused Code introduced by
The call to smf_var_export() has too many arguments starting with true. ( Ignorable by Annotation )

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

1147
					$replacement .= '$' . $var . ' = ' . ($new_settings_vars[$var] === $setting_def['default'] && !empty($setting_def['raw_default']) ? sprintf($new_settings_vars[$var]) : /** @scrutinizer ignore-call */ smf_var_export($new_settings_vars[$var], true)) . ";";

This check compares calls to functions or methods with their respective definitions. If the call has more arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
1148
				}
1149
				else
1150
				{
1151
					$replacement = '';
1152
1153
					// This is just for cosmetic purposes. Removes the blank line.
1154
					$substitutions[$var]['search_pattern'] = str_replace('(?<=^|\s)', '\n?', $substitutions[$var]['search_pattern']);
1155
				}
1156
			}
1157
			// Add this setting's value.
1158
			elseif (in_array($var, array_keys($new_settings_vars)))
1159
			{
1160
				$replacement .= '$' . $var . ' = ' . ($new_settings_vars[$var] === $setting_def['default'] && !empty($setting_def['raw_default']) ? sprintf($new_settings_vars[$var]) : smf_var_export($new_settings_vars[$var], true)) . ";";
1161
			}
1162
			// Fall back to the default value.
1163
			elseif (isset($setting_def['default']))
1164
			{
1165
				$replacement .= '$' . $var . ' = ' . (!empty($setting_def['raw_default']) ? sprintf($setting_def['default']) : smf_var_export($setting_def['default'], true)) . ';';
1166
			}
1167
			// This shouldn't happen, but we've got nothing.
1168
			else
1169
				$replacement .= '$' . $var . ' = null;';
1170
		}
1171
1172
		$substitutions[$var]['replacement'] = $replacement;
1173
1174
		// We're done with this one.
1175
		unset($new_settings_vars[$var]);
1176
	}
1177
1178
	// Any leftovers to deal with?
1179
	foreach ($new_settings_vars as $var => $val)
1180
	{
1181
		$var_pattern = array();
1182
1183
		if (in_array($var, array_keys($config_vars)))
1184
			$var_pattern[] = $type_regex[gettype($config_vars[$var])];
1185
1186
		if (in_array($var, array_keys($settings_vars)))
1187
			$var_pattern[] = $type_regex[gettype($settings_vars[$var])];
1188
1189
		$var_pattern = array_unique($var_pattern);
1190
1191
		$var_pattern = count($var_pattern) > 1 ? '(?:' . (implode('|', $var_pattern)) . ')' : $var_pattern[0];
1192
1193
		$placeholder = md5($prefix . $var);
1194
1195
		$substitutions[$var]['search_pattern'] = '~(?<=^|\s)\h*\$' . preg_quote($var, '~') . '\s*=\s*' . $var_pattern . ';~' . (!empty($context['utf8']) ? 'u' : '');
1196
		$substitutions[$var]['placeholder'] = $placeholder;
1197
		$substitutions[$var]['replacement'] = '$' . $var . ' = ' . smf_var_export($val, true) . ";";
1198
	}
1199
1200
	// During an upgrade, some of the path variables may not have been declared yet.
1201
	if (defined('SMF_INSTALLING') && empty($rebuild))
1202
	{
1203
		preg_match_all('~^\h*\$(\w+)\s*=\s*~m', $substitutions[$pathcode_var]['replacement'], $matches);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $pathcode_var does not seem to be defined for all execution paths leading up to this point.
Loading history...
1204
		$missing_pathvars = array_diff($matches[1], array_keys($substitutions));
1205
1206
		if (!empty($missing_pathvars))
1207
		{
1208
			foreach ($missing_pathvars as $var)
1209
			{
1210
				$substitutions[$pathcode_var]['replacement'] = preg_replace('~\nif[^\n]+\$' . $var . '[^\n]+\n\h*\$' . $var . ' = [^\n]+~', '', $substitutions[$pathcode_var]['replacement']);
1211
			}
1212
		}
1213
	}
1214
1215
	// It's important to do the numbered ones before the named ones, or messes happen.
1216
	uksort($substitutions, function($a, $b) {
1217
		if (is_int($a) && is_int($b))
1218
			return $a > $b;
1219
		elseif (is_int($a))
1220
			return -1;
1221
		elseif (is_int($b))
1222
			return 1;
1223
		else
1224
			return strcasecmp($b, $a);
1225
	});
1226
1227
	/******************************
1228
	 * PART 3: Content processing *
1229
	 ******************************/
1230
1231
	/* 3.a: Get the content of Settings.php and make sure it is good. */
1232
1233
	// Retrieve the contents of Settings.php and normalize the line endings.
1234
	$settingsText = trim(strtr(file_get_contents($settingsFile), array("\r\n" => "\n", "\r" => "\n")));
1235
1236
	// If Settings.php is empty or corrupt for some reason, see if we can recover.
1237
	if ($settingsText == '' || substr($settingsText, 0, 5) !== '<' . '?php')
1238
	{
1239
		// Try restoring from the backup.
1240
		if (file_exists(dirname($settingsFile) . '/Settings_bak.php'))
1241
			$settingsText = strtr(file_get_contents(dirname($settingsFile) . '/Settings_bak.php'), array("\r\n" => "\n", "\r" => "\n"));
1242
1243
		// Backup is bad too? Our only option is to create one from scratch.
1244
		if ($settingsText == '' || substr($settingsText, 0, 5) !== '<' . '?php' || substr($settingsText, -2) !== '?' . '>')
1245
		{
1246
			$settingsText = '<' . "?php\n";
1247
			foreach ($settings_defs as $var => $setting_def)
1248
			{
1249
				if (!empty($setting_def['text']) && strpos($substitutions[$var]['replacement'], $setting_def['text']) === false)
1250
					$substitutions[$var]['replacement'] = $setting_def['text'] . "\n" . $substitutions[$var]['replacement'];
1251
1252
				$settingsText .= $substitutions[$var]['replacement'] . "\n";
1253
			}
1254
			$settingsText .= "\n\n?" . '>';
1255
			$rebuild = true;
1256
		}
1257
	}
1258
1259
	// Settings.php is unlikely to contain any heredocs, but just in case...
1260
	if (preg_match_all('/<<<(\'?)(\w+)\'?\n(.*?)\n\2;$/m', $settingsText, $matches))
1261
	{
1262
		foreach ($matches[0] as $mkey => $heredoc)
1263
		{
1264
			if (!empty($matches[1][$mkey]))
1265
				$heredoc_replacements[$heredoc] = var_export($matches[3][$mkey], true) . ';';
1266
			else
1267
				$heredoc_replacements[$heredoc] = '"' . strtr(substr(var_export($matches[3][$mkey], true), 1, -1), array("\\'" => "'", '"' => '\"')) . '";';
1268
		}
1269
1270
		$settingsText = strtr($settingsText, $heredoc_replacements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $heredoc_replacements seems to be defined by a foreach iteration on line 1262. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1271
	}
1272
1273
	/* 3.b: Loop through all our substitutions to insert placeholders, etc. */
1274
1275
	$last_var = null;
1276
	$bare_settingsText = $settingsText;
1277
	$force_before_pathcode = array();
1278
	foreach ($substitutions as $var => $substitution)
1279
	{
1280
		$placeholders[$var] = $substitution['placeholder'];
1281
1282
		if (!empty($substitution['placeholder']))
1283
		{
1284
			$simple_replacements[$substitution['placeholder']] = $substitution['replacement'];
1285
		}
1286
		elseif (!empty($substitution['replace_pattern']))
1287
		{
1288
			$replace_patterns[$var] = $substitution['replace_pattern'];
1289
			$replace_strings[$var] = $substitution['replacement'];
1290
		}
1291
1292
		if (strpos($substitutions[$pathcode_var]['replacement'], '$' . $var . ' = ') !== false)
1293
			$force_before_pathcode[] = $var;
1294
1295
		// Look before you leap.
1296
		preg_match_all($substitution['search_pattern'], $bare_settingsText, $matches);
1297
1298
		if ((is_string($var) || $var === $pathcode_var) && count($matches[0]) !== 1 && $substitution['replacement'] !== '')
1299
		{
1300
			// More than one instance of the variable = not good.
1301
			if (count($matches[0]) > 1)
1302
			{
1303
				if (is_string($var))
1304
				{
1305
					// Maybe we can try something more interesting?
1306
					$sp = substr($substitution['search_pattern'], 1);
1307
1308
					if (strpos($sp, '(?<=^|\s)') === 0)
1309
						$sp = substr($sp, 9);
1310
1311
					if (strpos($sp, '^') === 0 || strpos($sp, '(?<') === 0)
1312
						return false;
1313
1314
					// See if we can exclude `if` blocks, etc., to narrow down the matches.
1315
					// @todo Multiple layers of nested brackets might confuse this.
1316
					$sp = '~(?:^|//[^\n]+c\n|\*/|[;}]|' . implode('|', array_filter($placeholders)) . ')\s*' . (strpos($sp, '\K') === false ? '\K' : '') . $sp;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $placeholders seems to be defined later in this foreach loop on line 1280. Are you sure it is defined here?
Loading history...
1317
1318
					preg_match_all($sp, $settingsText, $matches);
1319
				}
1320
				else
1321
					$sp = $substitution['search_pattern'];
1322
1323
				// Found at least some that are simple assignment statements.
1324
				if (count($matches[0]) > 0)
1325
				{
1326
					// Remove any duplicates.
1327
					if (count($matches[0]) > 1)
1328
						$settingsText = preg_replace($sp, '', $settingsText, count($matches[0]) - 1);
1329
1330
					// Insert placeholder for the last one.
1331
					$settingsText = preg_replace($sp, $substitution['placeholder'], $settingsText, 1);
1332
				}
1333
1334
				// All instances are inside more complex code structures.
1335
				else
1336
				{
1337
					// Only safe option at this point is to skip it.
1338
					unset($substitutions[$var], $new_settings_vars[$var], $settings_defs[$var], $simple_replacements[$substitution['placeholder']], $replace_patterns[$var], $replace_strings[$var]);
1339
1340
					continue;
1341
				}
1342
			}
1343
			// No matches found.
1344
			elseif (count($matches[0]) === 0)
1345
			{
1346
				$found = false;
1347
				$in_c = in_array($var, array_keys($config_vars));
1348
				$in_s = in_array($var, array_keys($settings_vars));
1349
1350
				// Is it in there at all?
1351
				if (!preg_match('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*~', $bare_settingsText))
1352
				{
1353
					// It's defined by Settings.php, but not by code in the file.
1354
					// Probably done via an include or something. Skip it.
1355
					if ($in_s)
1356
						unset($substitutions[$var], $settings_defs[$var]);
1357
1358
					// Admin is explicitly trying to set this one, so we'll handle
1359
					// it as if it were a new custom setting being added.
1360
					elseif ($in_c)
1361
						$new_settings_vars[$var] = $config_vars[$var];
1362
1363
					continue;
1364
				}
1365
1366
				// It's in there somewhere, so check if the value changed type.
1367
				foreach (array('scalar', 'object', 'array') as $type)
1368
				{
1369
					// Try all the other scalar types first.
1370
					if ($type == 'scalar')
1371
						$sp = '(?:' . (implode('|', array_diff_key($type_regex, array($in_c ? gettype($config_vars[$var]) : ($in_s ? gettype($settings_vars[$var]) : PHP_INT_MAX) => '', 'array' => '', 'object' => '')))) . ')';
1372
1373
					// Maybe it's an object? (Probably not, but we should check.)
1374
					elseif ($type == 'object')
1375
					{
1376
						if (strpos($settingsText, '__set_state') === false)
1377
							continue;
1378
1379
						$sp = $type_regex['object'];
1380
					}
1381
1382
					// Maybe it's an array?
1383
					else
1384
						$sp = $type_regex['array'];
1385
1386
					if (preg_match('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*' . $sp . '~', $bare_settingsText, $derp))
1387
					{
1388
						$settingsText = preg_replace('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*' . $sp . '~', $substitution['placeholder'], $settingsText);
1389
						$found = true;
1390
						break;
1391
					}
1392
				}
1393
1394
				// Something weird is going on. Better just leave it alone.
1395
				if (!$found)
1396
				{
1397
					// $var? What $var? Never heard of it.
1398
					unset($substitutions[$var], $new_settings_vars[$var], $settings_defs[$var], $simple_replacements[$substitution['placeholder']], $replace_patterns[$var], $replace_strings[$var]);
1399
					continue;
1400
				}
1401
			}
1402
		}
1403
		// Good to go, so insert our placeholder.
1404
		else
1405
			$settingsText = preg_replace($substitution['search_pattern'], $substitution['placeholder'], $settingsText);
1406
1407
		// Once the code blocks are done, we want to compare to a version without comments.
1408
		if (is_int($last_var) && is_string($var))
1409
			$bare_settingsText = strip_php_comments($settingsText);
1410
1411
		$last_var = $var;
1412
	}
1413
1414
	// Rebuilding requires more work.
1415
	if (!empty($rebuild))
1416
	{
1417
		// Strip out the leading and trailing placeholders to prevent duplication.
1418
		$settingsText = str_replace(array($substitutions[-1]['placeholder'], $substitutions[-2]['placeholder']), '', $settingsText);
1419
1420
		// Strip out all our standard comments.
1421
		foreach ($settings_defs as $var => $setting_def)
1422
		{
1423
			if (isset($setting_def['text']))
1424
				$settingsText = strtr($settingsText, array($setting_def['text'] . "\n" => '', $setting_def['text'] => '',));
1425
		}
1426
1427
		// We need to refresh $bare_settingsText at this point.
1428
		$bare_settingsText = strip_php_comments($settingsText);
1429
1430
		// Fix up whitespace to make comparison easier.
1431
		foreach ($placeholders as $placeholder)
1432
		{
1433
			$bare_settingsText = str_replace(array($placeholder . "\n\n", $placeholder), $placeholder . "\n", $bare_settingsText);
1434
		}
1435
		$bare_settingsText = preg_replace('/\h+$/m', '', rtrim($bare_settingsText));
1436
1437
		/*
1438
		 * Divide the existing content into sections.
1439
		 * The idea here is to make sure we don't mess with the relative position
1440
		 * of any code blocks in the file, since that could break things. Within
1441
		 * each section, however, we'll reorganize the content to match the
1442
		 * default layout as closely as we can.
1443
		 */
1444
		$sections = array(array());
1445
		$section_num = 0;
1446
		$trimmed_placeholders = array_filter(array_map('trim', $placeholders));
1447
		$newsection_placeholders = array();
1448
		$all_custom_content = '';
1449
		foreach ($substitutions as $var => $substitution)
1450
		{
1451
			if (is_int($var) && ($var === -2 || $var > 0) && isset($trimmed_placeholders[$var]) && strpos($bare_settingsText, $trimmed_placeholders[$var]) !== false)
1452
				$newsection_placeholders[$var] = $trimmed_placeholders[$var];
1453
		}
1454
		foreach (preg_split('~(?<=' . implode('|', $trimmed_placeholders) . ')|(?=' . implode('|', $trimmed_placeholders) . ')~', $bare_settingsText) as $part)
1455
		{
1456
			$part = trim($part);
1457
1458
			if (empty($part))
1459
				continue;
1460
1461
			// Build a list of placeholders for this section.
1462
			if (in_array($part, $trimmed_placeholders) && !in_array($part, $newsection_placeholders))
1463
			{
1464
				$sections[$section_num][] = $part;
1465
			}
1466
			// Custom content and newsection_placeholders get their own sections.
1467
			else
1468
			{
1469
				if (!empty($sections[$section_num]))
1470
					++$section_num;
1471
1472
				$sections[$section_num][] = $part;
1473
1474
				++$section_num;
1475
1476
				if (!in_array($part, $trimmed_placeholders))
1477
					$all_custom_content .= "\n" . $part;
1478
			}
1479
		}
1480
1481
		// And now, rebuild the content!
1482
		$new_settingsText = '';
1483
		$done_defs = array();
1484
		$sectionkeys = array_keys($sections);
1485
		foreach ($sections as $sectionkey => $section)
1486
		{
1487
			// Custom content needs to be preserved.
1488
			if (count($section) === 1 && !in_array($section[0], $trimmed_placeholders))
1489
			{
1490
				$prev_section_end = $sectionkey < 1 ? 0 : strpos($settingsText, end($sections[$sectionkey - 1])) + strlen(end($sections[$sectionkey - 1]));
1491
				$next_section_start = $sectionkey == end($sectionkeys) ? strlen($settingsText) : strpos($settingsText, $sections[$sectionkey + 1][0]);
1492
1493
				$new_settingsText .= "\n" . substr($settingsText, $prev_section_end, $next_section_start - $prev_section_end) . "\n";
1494
			}
1495
			// Put the placeholders in this section into canonical order.
1496
			else
1497
			{
1498
				$section_parts = array_flip($section);
1499
				$pathcode_reached = false;
1500
				foreach ($settings_defs as $var => $setting_def)
1501
				{
1502
					if ($var === $pathcode_var)
1503
						$pathcode_reached = true;
1504
1505
					// Already did this setting, so move on to the next.
1506
					if (in_array($var, $done_defs))
1507
						continue;
1508
1509
					// Stop when we hit a setting definition that will start a later section.
1510
					if (isset($newsection_placeholders[$var]) && count($section) !== 1)
1511
						break;
1512
1513
					// Stop when everything in this section is done, unless it's the last.
1514
					// This helps maintain the relative position of any custom content.
1515
					if (empty($section_parts) && $sectionkey < (count($sections) - 1))
1516
						break;
1517
1518
					$p = trim($substitutions[$var]['placeholder']);
1519
1520
					// Can't do anything with an empty placeholder.
1521
					if ($p === '')
1522
						continue;
1523
1524
					// Does this need to be inserted before the path correction code?
1525
					if (strpos($new_settingsText, trim($substitutions[$pathcode_var]['placeholder'])) !== false && in_array($var, $force_before_pathcode))
1526
					{
1527
						$new_settingsText = strtr($new_settingsText, array($substitutions[$pathcode_var]['placeholder'] => $p . "\n" . $substitutions[$pathcode_var]['placeholder']));
1528
1529
						$bare_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1530
						$done_defs[] = $var;
1531
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1532
					}
1533
1534
					// If it's in this section, add it to the new text now.
1535
					elseif (in_array($p, $section))
1536
					{
1537
						$new_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1538
						$done_defs[] = $var;
1539
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1540
					}
1541
1542
					// Perhaps it is safe to reposition it anyway.
1543
					elseif (is_string($var) && strpos($new_settingsText, $p) === false && strpos($all_custom_content, '$' . $var) === false)
1544
					{
1545
						$new_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1546
						$done_defs[] = $var;
1547
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1548
					}
1549
1550
					// If this setting is missing entirely, fix it.
1551
					elseif (strpos($bare_settingsText, $p) === false)
1552
					{
1553
						// Special case if the path code is missing. Put it near the end,
1554
						// and also anything else that is missing that normally follows it.
1555
						if (!isset($newsection_placeholders[$pathcode_var]) && $pathcode_reached === true && $sectionkey < (count($sections) - 1))
1556
							break;
1557
1558
						$new_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1559
						$bare_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1560
						$done_defs[] = $var;
1561
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1562
					}
1563
				}
1564
			}
1565
		}
1566
		$settingsText = $new_settingsText;
1567
1568
		// Restore the leading and trailing placeholders as necessary.
1569
		foreach (array(-1, -2) as $var)
1570
		{
1571
			if (!empty($substitutions[$var]['placeholder']) && strpos($settingsText, $substitutions[$var]['placeholder']) === false);
1572
			{
1573
				$settingsText = ($var == -1 ? $substitutions[$var]['placeholder'] : '') . $settingsText . ($var == -2 ? $substitutions[$var]['placeholder'] : '');
1574
			}
1575
		}
1576
	}
1577
	// Even if not rebuilding, there are a few variables that may need to be moved around.
1578
	else
1579
	{
1580
		$pathcode_pos = strpos($settingsText, $substitutions[$pathcode_var]['placeholder']);
1581
1582
		if ($pathcode_pos !== false)
1583
		{
1584
			foreach ($force_before_pathcode as $var)
1585
			{
1586
				if (!empty($substitutions[$var]['placeholder']) && strpos($settingsText, $substitutions[$var]['placeholder']) > $pathcode_pos)
1587
				{
1588
					$settingsText = strtr($settingsText, array(
1589
						$substitutions[$var]['placeholder'] => '',
1590
						$substitutions[$pathcode_var]['placeholder'] => $substitutions[$var]['placeholder'] . "\n" . $substitutions[$pathcode_var]['placeholder'],
1591
					));
1592
				}
1593
			}
1594
		}
1595
	}
1596
1597
	/* 3.c: Replace the placeholders with the final values */
1598
1599
	// Where possible, perform simple substitutions.
1600
	$settingsText = strtr($settingsText, $simple_replacements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $simple_replacements does not seem to be defined for all execution paths leading up to this point.
Loading history...
1601
1602
	// Deal with any complicated ones.
1603
	if (!empty($replace_patterns))
1604
		$settingsText = preg_replace($replace_patterns, $replace_strings, $settingsText);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $replace_strings does not seem to be defined for all execution paths leading up to this point.
Loading history...
1605
1606
	// Make absolutely sure that the path correction code is included.
1607
	if (strpos($settingsText, $substitutions[$pathcode_var]['replacement']) === false)
1608
		$settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', "\n" . $substitutions[$pathcode_var]['replacement'] . "\n", $settingsText);
1609
1610
	// If we did not rebuild, do just enough to make sure the thing is viable.
1611
	if (empty($rebuild))
1612
	{
1613
		// We need to refresh $bare_settingsText again, and remove the code blocks from it.
1614
		$bare_settingsText = $settingsText;
1615
		foreach ($substitutions as $var => $substitution)
1616
		{
1617
			if (!is_int($var))
1618
				break;
1619
1620
			if (isset($substitution['replacement']))
1621
				$bare_settingsText = str_replace($substitution['replacement'], '', $bare_settingsText);
1622
		}
1623
		$bare_settingsText = strip_php_comments($bare_settingsText);
1624
1625
		// Now insert any defined settings that are missing.
1626
		$pathcode_reached = false;
1627
		foreach ($settings_defs as $var => $setting_def)
1628
		{
1629
			if ($var === $pathcode_var)
1630
				$pathcode_reached = true;
1631
1632
			if (is_int($var))
1633
				continue;
1634
1635
			// Do nothing if it is already in there.
1636
			if (preg_match($substitutions[$var]['search_pattern'], $bare_settingsText))
1637
				continue;
1638
1639
			// Insert it either before or after the path correction code, whichever is appropriate.
1640
			if (!$pathcode_reached || in_array($var, $force_before_pathcode))
1641
			{
1642
				$settingsText = preg_replace($substitutions[$pathcode_var]['search_pattern'], $substitutions[$var]['replacement'] . "\n$0", $settingsText);
1643
			}
1644
			else
1645
			{
1646
				$settingsText = preg_replace($substitutions[$pathcode_var]['search_pattern'], "$0\n" . $substitutions[$var]['replacement'], $settingsText);
1647
			}
1648
		}
1649
	}
1650
1651
	// If we have any brand new settings to add, do so.
1652
	foreach ($new_settings_vars as $var => $val)
1653
	{
1654
		if (isset($substitutions[$var]) && !preg_match($substitutions[$var]['search_pattern'], $settingsText))
1655
		{
1656
			if (!isset($settings_defs[$var]) && strpos($settingsText, '# Custom Settings #') === false)
1657
				$settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', "\n\n######### Custom Settings #########\n", $settingsText);
1658
1659
			$settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', $substitutions[$var]['replacement'] . "\n", $settingsText);
1660
		}
1661
	}
1662
1663
	// This is just cosmetic. Get rid of extra lines of whitespace.
1664
	$settingsText = preg_replace('~\n\s*\n~', "\n\n", $settingsText);
1665
1666
	/**************************************
1667
	 * PART 4: Check syntax before saving *
1668
	 **************************************/
1669
1670
	$temp_sfile = tempnam(sm_temp_dir(), md5($prefix . 'Settings.php'));
1671
	file_put_contents($temp_sfile, $settingsText);
1672
1673
	$result = get_current_settings(filemtime($temp_sfile), $temp_sfile);
1674
1675
	unlink($temp_sfile);
1676
1677
	// If the syntax is borked, try rebuilding to see if that fixes it.
1678
	if ($result === false)
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
1679
		return empty($rebuild) ? updateSettingsFile($config_vars, $keep_quotes, true) : false;
1680
1681
	/******************************************
1682
	 * PART 5: Write updated settings to file *
1683
	 ******************************************/
1684
1685
	$success = safe_file_write($settingsFile, $settingsText, dirname($settingsFile) . '/Settings_bak.php', $last_settings_change);
1686
1687
	// Remember this in case updateSettingsFile is called twice.
1688
	$mtime = filemtime($settingsFile);
0 ignored issues
show
Unused Code introduced by
The assignment to $mtime is dead and can be removed.
Loading history...
1689
1690
	return $success;
1691
}
1692
1693
/**
1694
 * Retrieves a copy of the current values of all settings defined in Settings.php.
1695
 *
1696
 * Importantly, it does this without affecting our actual global variables at all,
1697
 * and it performs safety checks before acting. The result is an array of the
1698
 * values as recorded in the settings file.
1699
 *
1700
 * @param int $mtime Timestamp of last known good configuration. Defaults to time SMF started.
1701
 * @param string $settingsFile The settings file. Defaults to SMF's standard Settings.php.
1702
 * @return array An array of name/value pairs for all the settings in the file.
1703
 */
1704
function get_current_settings($mtime = null, $settingsFile = null)
1705
{
1706
	$mtime = is_null($mtime) ? (defined('TIME_START') ? TIME_START : $_SERVER['REQUEST_TIME']) : (int) $mtime;
1707
1708
	if (!is_file($settingsFile))
1709
	{
1710
		foreach (get_included_files() as $settingsFile)
1711
			if (basename($settingsFile) === 'Settings.php')
1712
				break;
1713
1714
		if (basename($settingsFile) !== 'Settings.php')
1715
			return false;
1716
	}
1717
1718
	// If the file has been changed since the last known good configuration, bail out.
1719
	clearstatcache();
1720
	if (filemtime($settingsFile) > $mtime)
1721
		return false;
1722
1723
	// Strip out opening and closing PHP tags.
1724
	$settingsText = trim(file_get_contents($settingsFile));
1725
	if (substr($settingsText, 0, 5) == '<' . '?php')
1726
		$settingsText = substr($settingsText, 5);
1727
	if (substr($settingsText, -2) == '?' . '>')
1728
		$settingsText = substr($settingsText, 0, -2);
1729
1730
	// Since we're using eval, we need to manually replace these with strings.
1731
	$settingsText = strtr($settingsText, array(
1732
		'__FILE__' => var_export($settingsFile, true),
1733
		'__DIR__' => var_export(dirname($settingsFile), true),
1734
	));
1735
1736
	// Prevents warnings about constants that are already defined.
1737
	$settingsText = preg_replace_callback(
1738
		'~\bdefine\s*\(\s*(["\'])(\w+)\1~',
1739
		function ($matches)
1740
		{
1741
			return 'define(\'' . md5(mt_rand()) . '\'';
1742
		},
1743
		$settingsText
1744
	);
1745
1746
	// Handle eval errors gracefully in both PHP 5 and PHP 7
1747
	try
1748
	{
1749
		if($settingsText !== '' && @eval($settingsText) === false)
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1750
			throw new ErrorException('eval error');
1751
1752
		unset($mtime, $settingsFile, $settingsText);
1753
		$defined_vars = get_defined_vars();
1754
	}
1755
	catch (Throwable $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1756
	catch (ErrorException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1757
	if (isset($e))
1758
		return false;
1759
1760
	return $defined_vars;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $defined_vars does not seem to be defined for all execution paths leading up to this point.
Loading history...
1761
}
1762
1763
/**
1764
 * Writes data to a file, optionally making a backup, while avoiding race conditions.
1765
 *
1766
 * @param string $file The filepath of the file where the data should be written.
1767
 * @param string $data The data to be written to $file.
1768
 * @param string $backup_file The filepath where the backup should be saved. Default null.
1769
 * @param int $mtime If modification time of $file is more recent than this Unix timestamp, the write operation will abort. Defaults to time that the script started execution.
1770
 * @param bool $append If true, the data will be appended instead of overwriting the existing content of the file. Default false.
1771
 * @return bool Whether the write operation succeeded or not.
1772
 */
1773
function safe_file_write($file, $data, $backup_file = null, $mtime = null, $append = false)
1774
{
1775
	global $cachedir;
1776
1777
	// Sanity checks.
1778
	if (!file_exists($file) && !is_dir(dirname($file)))
1779
		return false;
1780
1781
	if (!is_int($mtime))
1782
		$mtime = $_SERVER['REQUEST_TIME'];
1783
1784
	$temp_dir = sm_temp_dir();
1785
1786
	// Our temp files.
1787
	$temp_sfile = tempnam($temp_dir, pathinfo($file, PATHINFO_FILENAME) . '.');
1788
1789
	if (!empty($backup_file))
1790
		$temp_bfile = tempnam($temp_dir, pathinfo($backup_file, PATHINFO_FILENAME) . '.');
1791
1792
	// We need write permissions.
1793
	$failed = false;
1794
	foreach (array($file, $backup_file) as $sf)
1795
	{
1796
		if (empty($sf))
1797
			continue;
1798
1799
		if (!file_exists($sf))
1800
			touch($sf);
1801
		elseif (!is_file($sf))
1802
			$failed = true;
1803
1804
		if (!$failed)
1805
			$failed = !smf_chmod($sf);
1806
	}
1807
1808
	// Is there enough free space on the disk?
1809
	if (!$failed && disk_free_space(dirname($file)) < (strlen($data) + filesize($file) + (!empty($backup_file) ? filesize($backup_file) : 0)))
1810
		$failed = true;
1811
1812
	// Now let's see if writing to a temp file succeeds.
1813
	if (!$failed && file_put_contents($temp_sfile, $data, LOCK_EX) !== strlen($data))
1814
		$failed = true;
1815
1816
	// Tests passed, so it's time to do the job.
1817
	if (!$failed)
1818
	{
1819
		// Back up the backup, just in case.
1820
		if (file_exists($backup_file))
1821
			$temp_bfile_saved = @copy($backup_file, $temp_bfile);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $temp_bfile does not seem to be defined for all execution paths leading up to this point.
Loading history...
1822
1823
		// Make sure no one changed the file while we weren't looking.
1824
		clearstatcache();
1825
		if (filemtime($file) <= $mtime)
1826
		{
1827
			// Attempt to open the file.
1828
			$sfhandle = @fopen($file, 'c');
1829
1830
			// Let's do this thing!
1831
			if ($sfhandle !== false)
1832
			{
1833
				// Immediately get a lock.
1834
				flock($sfhandle, LOCK_EX);
1835
1836
				// Make sure the backup works before we do anything more.
1837
				$temp_sfile_saved = @copy($file, $temp_sfile);
1838
1839
				// Now write our data to the file.
1840
				if ($temp_sfile_saved)
1841
				{
1842
					if (empty($append))
1843
					{
1844
						ftruncate($sfhandle, 0);
1845
						rewind($sfhandle);
1846
					}
1847
1848
					$failed = fwrite($sfhandle, $data) !== strlen($data);
1849
				}
1850
				else
1851
					$failed = true;
1852
1853
				// If writing failed, put everything back the way it was.
1854
				if ($failed)
1855
				{
1856
					if (!empty($temp_sfile_saved))
1857
						@rename($temp_sfile, $file);
1858
1859
					if (!empty($temp_bfile_saved))
1860
						@rename($temp_bfile, $backup_file);
1861
				}
1862
				// It worked, so make our temp backup the new permanent backup.
1863
				elseif (!empty($backup_file))
1864
					@rename($temp_sfile, $backup_file);
1865
1866
				// And we're done.
1867
				flock($sfhandle, LOCK_UN);
1868
				fclose($sfhandle);
1869
			}
1870
		}
1871
	}
1872
1873
	// We're done with these.
1874
	@unlink($temp_sfile);
1875
	@unlink($temp_bfile);
1876
1877
	if ($failed)
1878
		return false;
1879
1880
	// Even though on normal installations the filemtime should invalidate any cached version
1881
	// it seems that there are times it might not. So let's MAKE it dump the cache.
1882
	if (function_exists('opcache_invalidate'))
1883
		opcache_invalidate($file, true);
1884
1885
	return true;
1886
}
1887
1888
/**
1889
 * A wrapper around var_export whose output matches SMF coding conventions.
1890
 *
1891
 * @todo Add special handling for objects?
1892
 *
1893
 * @param mixed $var The variable to export
1894
 * @return mixed A PHP-parseable representation of the variable's value
1895
 */
1896
function smf_var_export($var)
1897
{
1898
	/*
1899
	 * Old versions of updateSettingsFile couldn't handle multi-line values.
1900
	 * Even though technically we can now, we'll keep arrays on one line for
1901
	 * the sake of backwards compatibility.
1902
	 */
1903
	if (is_array($var))
1904
	{
1905
		$return = array();
1906
1907
		foreach ($var as $key => $value)
1908
			$return[] = var_export($key, true) . ' => ' . smf_var_export($value);
1909
1910
		return 'array(' . implode(', ', $return) . ')';
1911
	}
1912
1913
	// For the same reason, replace literal returns and newlines with "\r" and "\n"
1914
	elseif (is_string($var) && (strpos($var, "\n") !== false || strpos($var, "\r") !== false))
1915
	{
1916
		return strtr(preg_replace_callback('/[\r\n]+/', function($m) {
1917
			return '\' . "' . strtr($m[0], array("\r" => '\r', "\n" => '\n')) . '" . \'';
1918
		}, $var), array("'' . " => '', " . ''" => ''));
1919
	}
1920
1921
	// We typically use lowercase true/false/null.
1922
	elseif (in_array(gettype($var), array('boolean', 'NULL')))
1923
		return strtolower(var_export($var, true));
1924
1925
	// Nothing special.
1926
	else
1927
		return var_export($var, true);
1928
};
1929
1930
/**
1931
 * Deletes all PHP comments from a string.
1932
 * Useful when analyzing input from file_get_contents, etc.
1933
 *
1934
 * If the code contains any strings in nowdoc or heredoc syntax, they will be
1935
 * converted to single- or double-quote strings.
1936
 *
1937
 * @param string $code_str A string containing PHP code.
1938
 * @param string|null $line_ending One of "\r", "\n", or "\r\n". Leave unset for auto-detect.
1939
 * @return string A string of PHP code with no comments in it.
1940
 */
1941
function strip_php_comments($code_str, $line_ending = null)
1942
{
1943
	// What line ending should we use?
1944
	// Note: this depends on the string, not the host OS, so PHP_EOL isn't what we want.
1945
	if (!in_array($line_ending, array("\r", "\n", "\r\n")))
1946
	{
1947
		if (strpos($code_str, "\r\n") !== false)
1948
			$line_ending = "\r\n";
1949
		elseif (strpos($code_str, "\n") !== false)
1950
			$line_ending = "\n";
1951
		elseif (strpos($code_str, "\r") !== false)
1952
			$line_ending = "\r";
1953
	}
1954
1955
	// Everything is simpler if we convert heredocs to normal strings first.
1956
	if (preg_match_all('/<<<(\'?)(\w+)\'?'. $line_ending . '(.*?)'. $line_ending . '\2;$/m', $code_str, $matches))
1957
	{
1958
		foreach ($matches[0] as $mkey => $heredoc)
1959
		{
1960
			if (!empty($matches[1][$mkey]))
1961
				$heredoc_replacements[$heredoc] = var_export($matches[3][$mkey], true) . ';';
1962
			else
1963
				$heredoc_replacements[$heredoc] = '"' . strtr(substr(var_export($matches[3][$mkey], true), 1, -1), array("\\'" => "'", '"' => '\"')) . '";';
1964
		}
1965
1966
		$code_str = strtr($code_str, $heredoc_replacements);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $heredoc_replacements seems to be defined by a foreach iteration on line 1958. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1967
	}
1968
1969
	// Split before everything that could possibly delimit a comment or a string.
1970
	$parts = preg_split('~(?=#+|/(?=/|\*)|\*/|'. $line_ending . '|(?<!\\\)[\'"])~m', $code_str);
1971
1972
	$in_string = 0;
1973
	$in_comment = 0;
1974
	foreach ($parts as $partkey => $part)
1975
	{
1976
		$one_char = substr($part, 0, 1);
1977
		$two_char = substr($part, 0, 2);
1978
		$to_remove = 0;
1979
1980
		/*
1981
		 * Meaning of $in_string values:
1982
		 *	0: not in a string
1983
		 *	1: in a single quote string
1984
		 *	2: in a double quote string
1985
		 */
1986
		if ($one_char == "'")
1987
		{
1988
			if (!empty($in_comment))
1989
				$in_string = 0;
1990
			elseif (in_array($in_string, array(0, 1)))
1991
				$in_string = ($in_string ^ 1);
1992
		}
1993
		elseif ($one_char == '"')
1994
		{
1995
			if (!empty($in_comment))
1996
				$in_string = 0;
1997
			elseif (in_array($in_string, array(0, 2)))
1998
				$in_string = ($in_string ^ 2);
1999
		}
2000
2001
		/*
2002
		 * Meaning of $in_comment values:
2003
		 * 	0: not in a comment
2004
		 *	1: in a single line comment
2005
		 *	2: in a multi-line comment
2006
		 */
2007
		elseif ($one_char == '#' || $two_char == '//')
2008
		{
2009
			$in_comment = !empty($in_string) ? 0 : (empty($in_comment) ? 1 : $in_comment);
2010
		}
2011
		elseif (($line_ending === "\r\n" && $two_char === $line_ending) || $one_char == $line_ending)
2012
		{
2013
			if ($in_comment == 1)
2014
			{
2015
				$in_comment = 0;
2016
2017
				// If we've removed everything on this line, take out the line ending, too.
2018
				if ($parts[$partkey - 1] === $line_ending)
2019
					$to_remove = strlen($line_ending);
2020
			}
2021
		}
2022
		elseif ($two_char == '/*')
2023
		{
2024
			$in_comment = !empty($in_string) ? 0 : (empty($in_comment) ? 2 : $in_comment);
2025
		}
2026
		elseif ($two_char == '*/')
2027
		{
2028
			if ($in_comment == 2)
2029
			{
2030
				$in_comment = 0;
2031
2032
				// Delete the comment closing.
2033
				$to_remove = 2;
2034
			}
2035
		}
2036
2037
		if (empty($in_comment))
2038
			$parts[$partkey] = strlen($part) > $to_remove ? substr($part, $to_remove) : '';
2039
		else
2040
			$parts[$partkey] = '';
2041
	}
2042
2043
	return implode('', $parts);
0 ignored issues
show
Bug introduced by
It seems like $parts can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

2043
	return implode('', /** @scrutinizer ignore-type */ $parts);
Loading history...
2044
}
2045
2046
/**
2047
 * Saves the time of the last db error for the error log
2048
 * - Done separately from updateSettingsFile to avoid race conditions
2049
 *   which can occur during a db error
2050
 * - If it fails Settings.php will assume 0
2051
 *
2052
 * @param int $time The timestamp of the last DB error
2053
 */
2054
function updateDbLastError($time)
2055
{
2056
	global $boarddir, $cachedir;
2057
2058
	// Write out the db_last_error file with the error timestamp
2059
	if (!empty($cachedir) && is_writable($cachedir))
2060
		$errorfile = $cachedir . '/db_last_error.php';
2061
2062
	elseif (file_exists(dirname(__DIR__) . '/cache'))
2063
		$errorfile = dirname(__DIR__) . '/cache/db_last_error.php';
2064
2065
	else
2066
		$errorfile = dirname(__DIR__) . '/db_last_error.php';
2067
2068
	file_put_contents($errorfile, '<' . '?' . "php\n" . '$db_last_error = ' . $time . ';' . "\n" . '?' . '>', LOCK_EX);
2069
2070
	@touch($boarddir . '/' . 'Settings.php');
2071
}
2072
2073
/**
2074
 * Saves the admin's current preferences to the database.
2075
 */
2076
function updateAdminPreferences()
2077
{
2078
	global $options, $context, $smcFunc, $settings, $user_info;
2079
2080
	// This must exist!
2081
	if (!isset($context['admin_preferences']))
2082
		return false;
2083
2084
	// This is what we'll be saving.
2085
	$options['admin_preferences'] = $smcFunc['json_encode']($context['admin_preferences']);
2086
2087
	// Just check we haven't ended up with something theme exclusive somehow.
2088
	$smcFunc['db_query']('', '
2089
		DELETE FROM {db_prefix}themes
2090
		WHERE id_theme != {int:default_theme}
2091
			AND variable = {string:admin_preferences}',
2092
		array(
2093
			'default_theme' => 1,
2094
			'admin_preferences' => 'admin_preferences',
2095
		)
2096
	);
2097
2098
	// Update the themes table.
2099
	$smcFunc['db_insert']('replace',
2100
		'{db_prefix}themes',
2101
		array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
2102
		array($user_info['id'], 1, 'admin_preferences', $options['admin_preferences']),
2103
		array('id_member', 'id_theme', 'variable')
2104
	);
2105
2106
	// Make sure we invalidate any cache.
2107
	cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 0);
2108
}
2109
2110
/**
2111
 * Send all the administrators a lovely email.
2112
 * - loads all users who are admins or have the admin forum permission.
2113
 * - uses the email template and replacements passed in the parameters.
2114
 * - sends them an email.
2115
 *
2116
 * @param string $template Which email template to use
2117
 * @param array $replacements An array of items to replace the variables in the template
2118
 * @param array $additional_recipients An array of arrays of info for additional recipients. Should have 'id', 'email' and 'name' for each.
2119
 */
2120
function emailAdmins($template, $replacements = array(), $additional_recipients = array())
2121
{
2122
	global $smcFunc, $sourcedir, $language, $modSettings;
2123
2124
	// We certainly want this.
2125
	require_once($sourcedir . '/Subs-Post.php');
2126
2127
	// Load all members which are effectively admins.
2128
	require_once($sourcedir . '/Subs-Members.php');
2129
	$members = membersAllowedTo('admin_forum');
2130
2131
	// Load their alert preferences
2132
	require_once($sourcedir . '/Subs-Notify.php');
2133
	$prefs = getNotifyPrefs($members, 'announcements', true);
2134
2135
	$request = $smcFunc['db_query']('', '
2136
		SELECT id_member, member_name, real_name, lngfile, email_address
2137
		FROM {db_prefix}members
2138
		WHERE id_member IN({array_int:members})',
2139
		array(
2140
			'members' => $members,
2141
		)
2142
	);
2143
	$emails_sent = array();
2144
	while ($row = $smcFunc['db_fetch_assoc']($request))
2145
	{
2146
		if (empty($prefs[$row['id_member']]['announcements']))
2147
			continue;
2148
2149
		// Stick their particulars in the replacement data.
2150
		$replacements['IDMEMBER'] = $row['id_member'];
2151
		$replacements['REALNAME'] = $row['member_name'];
2152
		$replacements['USERNAME'] = $row['real_name'];
2153
2154
		// Load the data from the template.
2155
		$emaildata = loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
2156
2157
		// Then send the actual email.
2158
		sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
2159
2160
		// Track who we emailed so we don't do it twice.
2161
		$emails_sent[] = $row['email_address'];
2162
	}
2163
	$smcFunc['db_free_result']($request);
2164
2165
	// Any additional users we must email this to?
2166
	if (!empty($additional_recipients))
2167
		foreach ($additional_recipients as $recipient)
2168
		{
2169
			if (in_array($recipient['email'], $emails_sent))
2170
				continue;
2171
2172
			$replacements['IDMEMBER'] = $recipient['id'];
2173
			$replacements['REALNAME'] = $recipient['name'];
2174
			$replacements['USERNAME'] = $recipient['name'];
2175
2176
			// Load the template again.
2177
			$emaildata = loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty($modSettings['userLanguage']) ? $language : $recipient['lang']);
2178
2179
			// Send off the email.
2180
			sendmail($recipient['email'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
2181
		}
2182
}
2183
2184
/*
2185
 * Locates the most appropriate temp directory.
2186
 *
2187
 * Systems using `open_basedir` restrictions may receive errors with
2188
 * `sys_get_temp_dir()` due to misconfigurations on servers. Other
2189
 * cases sys_temp_dir may not be set to a safe value. Additionaly
2190
 * `sys_get_temp_dir` may use a readonly directory. This attempts to
2191
 * find a working temp directory that is accessible under the
2192
 * restrictions and is writable to the web service account.
2193
 *
2194
 * Directories cheked against `open_basedir`:
2195
 *
2196
 * - `sys_get_temp_dir()`
2197
 * - `upload_tmp_dir`
2198
 * - `session.save_path`
2199
 * - `$cachedir`
2200
 *
2201
 * @return string
2202
*/
2203
function sm_temp_dir()
2204
{
2205
	global $cachedir;
2206
2207
	static $temp_dir = null;
2208
2209
	// Already did this.
2210
	if (!empty($temp_dir))
2211
		return $temp_dir;
2212
2213
	// Temp Directory options order.
2214
	$temp_dir_options = array(
2215
		0 => 'sys_get_temp_dir',
2216
		1 => 'upload_tmp_dir',
2217
		2 => 'session.save_path',
2218
		3 => 'cachedir'
2219
	);
2220
2221
	// Determine if we should detect a restriction and what restrictions that may be.
2222
	$restriction = !empty(ini_get('open_basedir')) ? explode(':', ini_get('open_basedir')) : false;
2223
2224
	// Prevent any errors as we search.
2225
	$old_error_reporting = error_reporting(0);
2226
2227
	// Search for a working temp directory.
2228
	foreach ($temp_dir_options as $id_temp => $temp_option)
2229
	{
2230
		$possible_temp = sm_temp_dir_option($temp_option);
2231
2232
		// Check if we have a restriction preventing this from working.
2233
		if ($restriction)
2234
		{
2235
			foreach ($restriction as $dir)
2236
			{
2237
				if (strpos($possible_temp, $dir) !== false && is_writable($possible_temp))
2238
				{
2239
					$temp_dir = $possible_temp;
2240
					break;
2241
				}
2242
			}
2243
		}
2244
		// No restrictions, but need to check for writable status.
2245
		elseif (is_writable($possible_temp))
2246
		{
2247
			$temp_dir = $possible_temp;
2248
			break;
2249
		}
2250
	}
2251
2252
	// Fall back to sys_get_temp_dir even though it won't work, so we have something.
2253
	if (empty($temp_dir))
2254
		$temp_dir = sm_temp_dir_option('default');
2255
2256
	// Fix the path.
2257
	$temp_dir = substr($temp_dir, -1) === '/' ? $temp_dir : $temp_dir . '/';
2258
2259
	// Put things back.
2260
	error_reporting($old_error_reporting);
2261
2262
	return $temp_dir;
2263
}
2264
2265
/**
2266
 * Internal function for sm_temp_dir.
2267
 *
2268
 * @param string $option Which temp_dir option to use
2269
 */
2270
function sm_temp_dir_option($option = 'sys_get_temp_dir')
2271
{
2272
	global $cachedir;
2273
2274
	if ($option === 'cachedir')
2275
		return rtrim($cachedir, '/');
2276
	elseif ($option === 'session.save_path')
2277
		return rtrim(ini_get('session.save_path'), '/');
2278
	elseif ($option === 'upload_tmp_dir')
2279
		return rtrim(ini_get('upload_tmp_dir'), '/');
2280
	// This is the default option.
2281
	else
2282
		return sys_get_temp_dir();
2283
}
2284
2285
?>