strip_php_comments()   F
last analyzed

Complexity

Conditions 40
Paths 531

Size

Total Lines 157
Code Lines 78

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 40
eloc 78
nc 531
nop 1
dl 0
loc 157
rs 0.6513
c 0
b 0
f 0

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

1203
					$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...
1204
				}
1205
				else
1206
				{
1207
					$replacement = '';
1208
					$substitutions[$var]['placeholder'] = '';
1209
1210
					// This is just for cosmetic purposes. Removes the blank line.
1211
					$substitutions[$var]['search_pattern'] = str_replace('(?<=^|\s)', '\n?', $substitutions[$var]['search_pattern']);
1212
				}
1213
			}
1214
			// Add this setting's value.
1215
			elseif (in_array($var, array_keys($new_settings_vars)))
1216
			{
1217
				$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)) . ";";
1218
			}
1219
			// Fall back to the default value.
1220
			elseif (isset($setting_def['default']))
1221
			{
1222
				$replacement .= '$' . $var . ' = ' . (!empty($setting_def['raw_default']) ? sprintf($setting_def['default']) : smf_var_export($setting_def['default'], true)) . ';';
1223
			}
1224
			// This shouldn't happen, but we've got nothing.
1225
			else
1226
				$replacement .= '$' . $var . ' = null;';
1227
		}
1228
1229
		$substitutions[$var]['replacement'] = $replacement;
1230
1231
		// We're done with this one.
1232
		unset($new_settings_vars[$var]);
1233
	}
1234
1235
	// Any leftovers to deal with?
1236
	foreach ($new_settings_vars as $var => $val)
1237
	{
1238
		$var_pattern = array();
1239
1240
		if (in_array($var, array_keys($config_vars)))
1241
			$var_pattern[] = $type_regex[gettype($config_vars[$var])];
1242
1243
		if (in_array($var, array_keys($settings_vars)))
1244
			$var_pattern[] = $type_regex[gettype($settings_vars[$var])];
1245
1246
		$var_pattern = array_unique($var_pattern);
1247
1248
		$var_pattern = count($var_pattern) > 1 ? '(?:' . (implode('|', $var_pattern)) . ')' : $var_pattern[0];
1249
1250
		$placeholder = md5($prefix . $var);
1251
1252
		$substitutions[$var]['search_pattern'] = '~(?<=^|\s)\h*\$' . preg_quote($var, '~') . '\s*=\s*' . $var_pattern . ';~' . (!empty($utf8) ? 'u' : '');
1253
		$substitutions[$var]['placeholder'] = $placeholder;
1254
		$substitutions[$var]['replacement'] = '$' . $var . ' = ' . smf_var_export($val, true) . ";";
1255
	}
1256
1257
	// During an upgrade, some of the path variables may not have been declared yet.
1258
	if (defined('SMF_INSTALLING') && empty($rebuild))
1259
	{
1260
		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...
1261
		$missing_pathvars = array_diff($matches[1], array_keys($substitutions));
1262
1263
		if (!empty($missing_pathvars))
1264
		{
1265
			foreach ($missing_pathvars as $var)
1266
			{
1267
				$substitutions[$pathcode_var]['replacement'] = preg_replace('~\nif[^\n]+\$' . $var . '[^\n]+\n\h*\$' . $var . ' = [^\n]+~', '', $substitutions[$pathcode_var]['replacement']);
1268
			}
1269
		}
1270
	}
1271
1272
	// It's important to do the numbered ones before the named ones, or messes happen.
1273
	uksort(
1274
		$substitutions,
1275
		function($a, $b) {
1276
			if (is_int($a) && is_int($b))
1277
				return $a > $b ? 1 : ($a < $b ? -1 : 0);
1278
			elseif (is_int($a))
1279
				return -1;
1280
			elseif (is_int($b))
1281
				return 1;
1282
			else
1283
				return strcasecmp($b, $a);
1284
		}
1285
	);
1286
1287
	/******************************
1288
	 * PART 3: Content processing *
1289
	 ******************************/
1290
1291
	/* 3.a: Get the content of Settings.php and make sure it is good. */
1292
1293
	// Retrieve the contents of Settings.php and normalize the line endings.
1294
	$settingsText = trim(strtr(file_get_contents($settingsFile), array("\r\n" => "\n", "\r" => "\n")));
1295
1296
	// If Settings.php is empty or corrupt for some reason, see if we can recover.
1297
	if ($settingsText == '' || substr($settingsText, 0, 5) !== '<' . '?php')
1298
	{
1299
		// Try restoring from the backup.
1300
		if (file_exists(dirname($settingsFile) . '/Settings_bak.php'))
1301
			$settingsText = strtr(file_get_contents(dirname($settingsFile) . '/Settings_bak.php'), array("\r\n" => "\n", "\r" => "\n"));
1302
1303
		// Backup is bad too? Our only option is to create one from scratch.
1304
		if ($settingsText == '' || substr($settingsText, 0, 5) !== '<' . '?php' || substr($settingsText, -2) !== '?' . '>')
1305
		{
1306
			$settingsText = '<' . "?php\n";
1307
			foreach ($settings_defs as $var => $setting_def)
1308
			{
1309
				if (is_string($var) && !empty($setting_def['text']) && strpos($substitutions[$var]['replacement'], $setting_def['text']) === false)
1310
					$substitutions[$var]['replacement'] = $setting_def['text'] . "\n" . $substitutions[$var]['replacement'];
1311
1312
				$settingsText .= $substitutions[$var]['replacement'] . "\n";
1313
			}
1314
			$settingsText .= "\n\n?" . '>';
1315
			$rebuild = true;
1316
		}
1317
	}
1318
1319
	// Settings.php is unlikely to contain any heredocs, but just in case...
1320
	if (preg_match_all('/<<<([\'"]?)(\w+)\1\R(.*?)\R\h*\2;$/ms', $settingsText, $matches))
1321
	{
1322
		foreach ($matches[0] as $mkey => $heredoc)
1323
		{
1324
			if (!empty($matches[1][$mkey]) && $matches[1][$mkey] === '\'')
1325
				$heredoc_replacements[$heredoc] = var_export($matches[3][$mkey], true) . ';';
1326
			else
1327
				$heredoc_replacements[$heredoc] = '"' . strtr(substr(var_export($matches[3][$mkey], true), 1, -1), array("\\'" => "'", '"' => '\"')) . '";';
1328
		}
1329
1330
		$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 1322. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
1331
	}
1332
1333
	/* 3.b: Loop through all our substitutions to insert placeholders, etc. */
1334
1335
	$last_var = null;
1336
	$bare_settingsText = $settingsText;
1337
	$force_before_pathcode = array();
1338
	foreach ($substitutions as $var => $substitution)
1339
	{
1340
		$placeholders[$var] = $substitution['placeholder'];
1341
1342
		if (!empty($substitution['placeholder']))
1343
		{
1344
			$simple_replacements[$substitution['placeholder']] = $substitution['replacement'];
1345
		}
1346
		elseif (!empty($substitution['replace_pattern']))
1347
		{
1348
			$replace_patterns[$var] = $substitution['replace_pattern'];
1349
			$replace_strings[$var] = $substitution['replacement'];
1350
		}
1351
1352
		if (strpos($substitutions[$pathcode_var]['replacement'], '$' . $var . ' = ') !== false)
1353
			$force_before_pathcode[] = $var;
1354
1355
		// Look before you leap.
1356
		preg_match_all($substitution['search_pattern'], $bare_settingsText, $matches);
1357
1358
		if ((is_string($var) || $var === $pathcode_var) && count($matches[0]) !== 1 && $substitution['replacement'] !== '')
1359
		{
1360
			// More than one instance of the variable = not good.
1361
			if (count($matches[0]) > 1)
1362
			{
1363
				if (is_string($var))
1364
				{
1365
					// Maybe we can try something more interesting?
1366
					$sp = substr($substitution['search_pattern'], 1);
1367
1368
					if (strpos($sp, '(?<=^|\s)') === 0)
1369
						$sp = substr($sp, 9);
1370
1371
					if (strpos($sp, '^') === 0 || strpos($sp, '(?<') === 0)
1372
						return false;
1373
1374
					// See if we can exclude `if` blocks, etc., to narrow down the matches.
1375
					// @todo Multiple layers of nested brackets might confuse this.
1376
					$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 1340. Are you sure it is defined here?
Loading history...
1377
1378
					preg_match_all($sp, $settingsText, $matches);
1379
				}
1380
				else
1381
					$sp = $substitution['search_pattern'];
1382
1383
				// Found at least some that are simple assignment statements.
1384
				if (count($matches[0]) > 0)
1385
				{
1386
					// Remove any duplicates.
1387
					if (count($matches[0]) > 1)
1388
						$settingsText = preg_replace($sp, '', $settingsText, count($matches[0]) - 1);
1389
1390
					// Insert placeholder for the last one.
1391
					$settingsText = preg_replace($sp, $substitution['placeholder'], $settingsText, 1);
1392
				}
1393
1394
				// All instances are inside more complex code structures.
1395
				else
1396
				{
1397
					// Only safe option at this point is to skip it.
1398
					unset($substitutions[$var], $new_settings_vars[$var], $settings_defs[$var], $simple_replacements[$substitution['placeholder']], $replace_patterns[$var], $replace_strings[$var]);
1399
1400
					continue;
1401
				}
1402
			}
1403
			// No matches found.
1404
			elseif (count($matches[0]) === 0)
1405
			{
1406
				$found = false;
1407
				$in_c = in_array($var, array_keys($config_vars));
1408
				$in_s = in_array($var, array_keys($settings_vars));
1409
1410
				// Is it in there at all?
1411
				if (!preg_match('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*~', $bare_settingsText))
1412
				{
1413
					// It's defined by Settings.php, but not by code in the file.
1414
					// Probably done via an include or something. Skip it.
1415
					if ($in_s)
1416
						unset($substitutions[$var], $settings_defs[$var]);
1417
1418
					// Admin is explicitly trying to set this one, so we'll handle
1419
					// it as if it were a new custom setting being added.
1420
					elseif ($in_c)
1421
						$new_settings_vars[$var] = $config_vars[$var];
1422
1423
					continue;
1424
				}
1425
1426
				// It's in there somewhere, so check if the value changed type.
1427
				foreach (array('scalar', 'object', 'array') as $type)
1428
				{
1429
					// Try all the other scalar types first.
1430
					if ($type == 'scalar')
1431
						$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' => '')))) . ')';
1432
1433
					// Maybe it's an object? (Probably not, but we should check.)
1434
					elseif ($type == 'object')
1435
					{
1436
						if (strpos($settingsText, '__set_state') === false)
1437
							continue;
1438
1439
						$sp = $type_regex['object'];
1440
					}
1441
1442
					// Maybe it's an array?
1443
					else
1444
						$sp = $type_regex['array'];
1445
1446
					if (preg_match('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*' . $sp . '~', $bare_settingsText, $derp))
1447
					{
1448
						$settingsText = preg_replace('~(^|\s)\$' . preg_quote($var, '~') . '\s*=\s*' . $sp . '~', $substitution['placeholder'], $settingsText);
1449
						$found = true;
1450
						break;
1451
					}
1452
				}
1453
1454
				// Something weird is going on. Better just leave it alone.
1455
				if (!$found)
1456
				{
1457
					// $var? What $var? Never heard of it.
1458
					unset($substitutions[$var], $new_settings_vars[$var], $settings_defs[$var], $simple_replacements[$substitution['placeholder']], $replace_patterns[$var], $replace_strings[$var]);
1459
					continue;
1460
				}
1461
			}
1462
		}
1463
		// Good to go, so insert our placeholder.
1464
		else
1465
			$settingsText = preg_replace($substitution['search_pattern'], $substitution['placeholder'], $settingsText);
1466
1467
		// Once the code blocks are done, we want to compare to a version without comments.
1468
		if (is_int($last_var) && is_string($var))
1469
			$bare_settingsText = strip_php_comments($settingsText);
1470
1471
		$last_var = $var;
1472
	}
1473
1474
	// Rebuilding requires more work.
1475
	if (!empty($rebuild))
1476
	{
1477
		// Strip out the leading and trailing placeholders to prevent duplication.
1478
		$settingsText = str_replace(array($substitutions[-1]['placeholder'], $substitutions[-2]['placeholder']), '', $settingsText);
1479
1480
		// Strip out all our standard comments.
1481
		foreach ($settings_defs as $var => $setting_def)
1482
		{
1483
			if (isset($setting_def['text']))
1484
				$settingsText = strtr($settingsText, array($setting_def['text'] . "\n" => '', $setting_def['text'] => '',));
1485
		}
1486
1487
		// We need to refresh $bare_settingsText at this point.
1488
		$bare_settingsText = strip_php_comments($settingsText);
1489
1490
		// Fix up whitespace to make comparison easier.
1491
		foreach ($placeholders as $placeholder)
1492
		{
1493
			$bare_settingsText = str_replace(array($placeholder . "\n\n", $placeholder), $placeholder . "\n", $bare_settingsText);
1494
		}
1495
		$bare_settingsText = preg_replace('/\h+$/m', '', rtrim($bare_settingsText));
1496
1497
		/*
1498
		 * Divide the existing content into sections.
1499
		 * The idea here is to make sure we don't mess with the relative position
1500
		 * of any code blocks in the file, since that could break things. Within
1501
		 * each section, however, we'll reorganize the content to match the
1502
		 * default layout as closely as we can.
1503
		 */
1504
		$sections = array(array());
1505
		$section_num = 0;
1506
		$trimmed_placeholders = array_filter(array_map('trim', $placeholders));
1507
		$newsection_placeholders = array();
1508
		$all_custom_content = '';
1509
		foreach ($substitutions as $var => $substitution)
1510
		{
1511
			if (is_int($var) && ($var === -2 || $var > 0) && isset($trimmed_placeholders[$var]) && strpos($bare_settingsText, $trimmed_placeholders[$var]) !== false)
1512
				$newsection_placeholders[$var] = $trimmed_placeholders[$var];
1513
		}
1514
		foreach (preg_split('~(?<=' . implode('|', $trimmed_placeholders) . ')|(?=' . implode('|', $trimmed_placeholders) . ')~', $bare_settingsText) as $part)
1515
		{
1516
			$part = trim($part);
1517
1518
			if (empty($part))
1519
				continue;
1520
1521
			// Build a list of placeholders for this section.
1522
			if (in_array($part, $trimmed_placeholders) && !in_array($part, $newsection_placeholders))
1523
			{
1524
				$sections[$section_num][] = $part;
1525
			}
1526
			// Custom content and newsection_placeholders get their own sections.
1527
			else
1528
			{
1529
				if (!empty($sections[$section_num]))
1530
					++$section_num;
1531
1532
				$sections[$section_num][] = $part;
1533
1534
				++$section_num;
1535
1536
				if (!in_array($part, $trimmed_placeholders))
1537
					$all_custom_content .= "\n" . $part;
1538
			}
1539
		}
1540
1541
		// And now, rebuild the content!
1542
		$new_settingsText = '';
1543
		$done_defs = array();
1544
		$sectionkeys = array_keys($sections);
1545
		foreach ($sections as $sectionkey => $section)
1546
		{
1547
			// Custom content needs to be preserved.
1548
			if (count($section) === 1 && !in_array($section[0], $trimmed_placeholders))
1549
			{
1550
				$prev_section_end = $sectionkey < 1 ? 0 : strpos($settingsText, end($sections[$sectionkey - 1])) + strlen(end($sections[$sectionkey - 1]));
1551
				$next_section_start = $sectionkey == end($sectionkeys) ? strlen($settingsText) : strpos($settingsText, $sections[$sectionkey + 1][0]);
1552
1553
				$new_settingsText .= "\n" . substr($settingsText, $prev_section_end, $next_section_start - $prev_section_end) . "\n";
1554
			}
1555
			// Put the placeholders in this section into canonical order.
1556
			else
1557
			{
1558
				$section_parts = array_flip($section);
1559
				$pathcode_reached = false;
1560
				foreach ($settings_defs as $var => $setting_def)
1561
				{
1562
					if ($var === $pathcode_var)
1563
						$pathcode_reached = true;
1564
1565
					// Already did this setting, so move on to the next.
1566
					if (in_array($var, $done_defs))
1567
						continue;
1568
1569
					// Stop when we hit a setting definition that will start a later section.
1570
					if (isset($newsection_placeholders[$var]) && count($section) !== 1)
1571
						break;
1572
1573
					// Stop when everything in this section is done, unless it's the last.
1574
					// This helps maintain the relative position of any custom content.
1575
					if (empty($section_parts) && $sectionkey < (count($sections) - 1))
1576
						break;
1577
1578
					$p = trim($substitutions[$var]['placeholder']);
1579
1580
					// Can't do anything with an empty placeholder.
1581
					if ($p === '')
1582
						continue;
1583
1584
					// Does this need to be inserted before the path correction code?
1585
					if (strpos($new_settingsText, trim($substitutions[$pathcode_var]['placeholder'])) !== false && in_array($var, $force_before_pathcode))
1586
					{
1587
						$new_settingsText = strtr($new_settingsText, array($substitutions[$pathcode_var]['placeholder'] => $p . "\n" . $substitutions[$pathcode_var]['placeholder']));
1588
1589
						$bare_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1590
						$done_defs[] = $var;
1591
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1592
					}
1593
1594
					// If it's in this section, add it to the new text now.
1595
					elseif (in_array($p, $section))
1596
					{
1597
						$new_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1598
						$done_defs[] = $var;
1599
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1600
					}
1601
1602
					// Perhaps it is safe to reposition it anyway.
1603
					elseif (is_string($var) && strpos($new_settingsText, $p) === false && strpos($all_custom_content, '$' . $var) === false)
1604
					{
1605
						$new_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1606
						$done_defs[] = $var;
1607
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1608
					}
1609
1610
					// If this setting is missing entirely, fix it.
1611
					elseif (strpos($bare_settingsText, $p) === false)
1612
					{
1613
						// Special case if the path code is missing. Put it near the end,
1614
						// and also anything else that is missing that normally follows it.
1615
						if (!isset($newsection_placeholders[$pathcode_var]) && $pathcode_reached === true && $sectionkey < (count($sections) - 1))
1616
							break;
1617
1618
						$new_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1619
						$bare_settingsText .= "\n" . $substitutions[$var]['placeholder'];
1620
						$done_defs[] = $var;
1621
						unset($section_parts[trim($substitutions[$var]['placeholder'])]);
1622
					}
1623
				}
1624
			}
1625
		}
1626
		$settingsText = $new_settingsText;
1627
1628
		// Restore the leading and trailing placeholders as necessary.
1629
		foreach (array(-1, -2) as $var)
1630
		{
1631
			if (!empty($substitutions[$var]['placeholder']) && strpos($settingsText, $substitutions[$var]['placeholder']) === false);
1632
			{
1633
				$settingsText = ($var == -1 ? $substitutions[$var]['placeholder'] : '') . $settingsText . ($var == -2 ? $substitutions[$var]['placeholder'] : '');
1634
			}
1635
		}
1636
	}
1637
	// Even if not rebuilding, there are a few variables that may need to be moved around.
1638
	else
1639
	{
1640
		$pathcode_pos = strpos($settingsText, $substitutions[$pathcode_var]['placeholder']);
1641
1642
		if ($pathcode_pos !== false)
1643
		{
1644
			foreach ($force_before_pathcode as $var)
1645
			{
1646
				if (!empty($substitutions[$var]['placeholder']) && strpos($settingsText, $substitutions[$var]['placeholder']) > $pathcode_pos)
1647
				{
1648
					$settingsText = strtr($settingsText, array(
1649
						$substitutions[$var]['placeholder'] => '',
1650
						$substitutions[$pathcode_var]['placeholder'] => $substitutions[$var]['placeholder'] . "\n" . $substitutions[$pathcode_var]['placeholder'],
1651
					));
1652
				}
1653
			}
1654
		}
1655
	}
1656
1657
	/* 3.c: Replace the placeholders with the final values */
1658
1659
	// Where possible, perform simple substitutions.
1660
	$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...
1661
1662
	// Deal with any complicated ones.
1663
	if (!empty($replace_patterns))
1664
		$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...
1665
1666
	// Make absolutely sure that the path correction code is included.
1667
	if (strpos($settingsText, $substitutions[$pathcode_var]['replacement']) === false)
1668
		$settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', "\n" . $substitutions[$pathcode_var]['replacement'] . "\n", $settingsText);
1669
1670
	// If we did not rebuild, do just enough to make sure the thing is viable.
1671
	if (empty($rebuild))
1672
	{
1673
		// We need to refresh $bare_settingsText again, and remove the code blocks from it.
1674
		$bare_settingsText = $settingsText;
1675
		foreach ($substitutions as $var => $substitution)
1676
		{
1677
			if (!is_int($var))
1678
				break;
1679
1680
			if (isset($substitution['replacement']))
1681
				$bare_settingsText = str_replace($substitution['replacement'], '', $bare_settingsText);
1682
		}
1683
		$bare_settingsText = strip_php_comments($bare_settingsText);
1684
1685
		// Now insert any defined settings that are missing.
1686
		$pathcode_reached = false;
1687
		foreach ($settings_defs as $var => $setting_def)
1688
		{
1689
			if ($var === $pathcode_var)
1690
				$pathcode_reached = true;
1691
1692
			if (is_int($var))
1693
				continue;
1694
1695
			// Do nothing if it is already in there.
1696
			if (preg_match($substitutions[$var]['search_pattern'], $bare_settingsText))
1697
				continue;
1698
1699
			// Insert it either before or after the path correction code, whichever is appropriate.
1700
			if (!$pathcode_reached || in_array($var, $force_before_pathcode))
1701
			{
1702
				$settingsText = preg_replace($substitutions[$pathcode_var]['search_pattern'], $substitutions[$var]['replacement'] . "\n\n$0", $settingsText);
1703
			}
1704
			else
1705
			{
1706
				$settingsText = preg_replace($substitutions[$pathcode_var]['search_pattern'], "$0\n\n" . $substitutions[$var]['replacement'], $settingsText);
1707
			}
1708
		}
1709
	}
1710
1711
	// If we have any brand new settings to add, do so.
1712
	foreach ($new_settings_vars as $var => $val)
1713
	{
1714
		if (isset($substitutions[$var]) && !preg_match($substitutions[$var]['search_pattern'], $settingsText))
1715
		{
1716
			if (!isset($settings_defs[$var]) && strpos($settingsText, '# Custom Settings #') === false)
1717
				$settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', "\n\n######### Custom Settings #########\n", $settingsText);
1718
1719
			$settingsText = preg_replace('~(?=\n#+ Error.Catching #+)~', $substitutions[$var]['replacement'] . "\n", $settingsText);
1720
		}
1721
	}
1722
1723
	// This is just cosmetic. Get rid of extra lines of whitespace.
1724
	$settingsText = preg_replace('~\n\s*\n~', "\n\n", $settingsText);
1725
1726
	/**************************************
1727
	 * PART 4: Check syntax before saving *
1728
	 **************************************/
1729
1730
	$temp_sfile = tempnam(sm_temp_dir(), md5($prefix . 'Settings.php'));
1731
	file_put_contents($temp_sfile, $settingsText);
1732
1733
	$result = get_current_settings(filemtime($temp_sfile), $temp_sfile);
1734
1735
	unlink($temp_sfile);
1736
1737
	// If the syntax is borked, try rebuilding to see if that fixes it.
1738
	if ($result === false)
0 ignored issues
show
introduced by
The condition $result === false is always false.
Loading history...
1739
		return empty($rebuild) ? updateSettingsFile($config_vars, $keep_quotes, true) : false;
1740
1741
	/******************************************
1742
	 * PART 5: Write updated settings to file *
1743
	 ******************************************/
1744
1745
	$success = safe_file_write($settingsFile, $settingsText, dirname($settingsFile) . '/Settings_bak.php', $last_settings_change);
1746
1747
	// Remember this in case updateSettingsFile is called twice.
1748
	$mtime = filemtime($settingsFile);
0 ignored issues
show
Unused Code introduced by
The assignment to $mtime is dead and can be removed.
Loading history...
1749
1750
	return $success;
1751
}
1752
1753
/**
1754
 * Retrieves a copy of the current values of all settings defined in Settings.php.
1755
 *
1756
 * Importantly, it does this without affecting our actual global variables at all,
1757
 * and it performs safety checks before acting. The result is an array of the
1758
 * values as recorded in the settings file.
1759
 *
1760
 * @param int $mtime Timestamp of last known good configuration. Defaults to time SMF started.
1761
 * @param string $settingsFile The settings file. Defaults to SMF's standard Settings.php.
1762
 * @return array An array of name/value pairs for all the settings in the file.
1763
 */
1764
function get_current_settings($mtime = null, $settingsFile = null)
1765
{
1766
	$mtime = is_null($mtime) ? (defined('TIME_START') ? TIME_START : $_SERVER['REQUEST_TIME']) : (int) $mtime;
1767
1768
	if (!is_file($settingsFile))
0 ignored issues
show
Bug introduced by
It seems like $settingsFile can also be of type null; however, parameter $filename of is_file() does only seem to accept string, 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

1768
	if (!is_file(/** @scrutinizer ignore-type */ $settingsFile))
Loading history...
1769
	{
1770
		foreach (get_included_files() as $settingsFile)
1771
			if (basename($settingsFile) === 'Settings.php')
1772
				break;
1773
1774
		if (basename($settingsFile) !== 'Settings.php')
0 ignored issues
show
Bug introduced by
It seems like $settingsFile can also be of type null; however, parameter $path of basename() does only seem to accept string, 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

1774
		if (basename(/** @scrutinizer ignore-type */ $settingsFile) !== 'Settings.php')
Loading history...
1775
			return false;
1776
	}
1777
1778
	// If the file has been changed since the last known good configuration, bail out.
1779
	clearstatcache();
1780
	if (filemtime($settingsFile) > $mtime)
0 ignored issues
show
Bug introduced by
It seems like $settingsFile can also be of type null; however, parameter $filename of filemtime() does only seem to accept string, 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

1780
	if (filemtime(/** @scrutinizer ignore-type */ $settingsFile) > $mtime)
Loading history...
1781
		return false;
1782
1783
	// Strip out opening and closing PHP tags.
1784
	$settingsText = trim(file_get_contents($settingsFile));
0 ignored issues
show
Bug introduced by
It seems like $settingsFile can also be of type null; however, parameter $filename of file_get_contents() does only seem to accept string, 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

1784
	$settingsText = trim(file_get_contents(/** @scrutinizer ignore-type */ $settingsFile));
Loading history...
1785
	if (substr($settingsText, 0, 5) == '<' . '?php')
1786
		$settingsText = substr($settingsText, 5);
1787
	if (substr($settingsText, -2) == '?' . '>')
1788
		$settingsText = substr($settingsText, 0, -2);
1789
1790
	// Since we're using eval, we need to manually replace these with strings.
1791
	$settingsText = strtr($settingsText, array(
1792
		'__FILE__' => var_export($settingsFile, true),
1793
		'__DIR__' => var_export(dirname($settingsFile), true),
0 ignored issues
show
Bug introduced by
It seems like $settingsFile can also be of type null; however, parameter $path of dirname() does only seem to accept string, 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

1793
		'__DIR__' => var_export(dirname(/** @scrutinizer ignore-type */ $settingsFile), true),
Loading history...
1794
	));
1795
1796
	// Prevents warnings about constants that are already defined.
1797
	$settingsText = preg_replace_callback(
1798
		'~\bdefine\s*\(\s*(["\'])(\w+)\1~',
1799
		function ($matches)
1800
		{
1801
			return 'define(\'' . md5(mt_rand()) . '\'';
1802
		},
1803
		$settingsText
1804
	);
1805
1806
	// Handle eval errors gracefully in both PHP 5 and PHP 7
1807
	try
1808
	{
1809
		if($settingsText !== '' && @eval($settingsText) === false)
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1810
			throw new ErrorException('eval error');
1811
1812
		unset($mtime, $settingsFile, $settingsText);
1813
		$defined_vars = get_defined_vars();
1814
	}
1815
	catch (Throwable $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1816
	catch (ErrorException $e) {}
0 ignored issues
show
Coding Style Comprehensibility introduced by
Consider adding a comment why this CATCH block is empty.
Loading history...
1817
	if (isset($e))
1818
		return false;
1819
1820
	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...
1821
}
1822
1823
/**
1824
 * Writes data to a file, optionally making a backup, while avoiding race conditions.
1825
 *
1826
 * @param string $file The filepath of the file where the data should be written.
1827
 * @param string $data The data to be written to $file.
1828
 * @param string $backup_file The filepath where the backup should be saved. Default null.
1829
 * @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.
1830
 * @param bool $append If true, the data will be appended instead of overwriting the existing content of the file. Default false.
1831
 * @return bool Whether the write operation succeeded or not.
1832
 */
1833
function safe_file_write($file, $data, $backup_file = null, $mtime = null, $append = false)
1834
{
1835
	// Sanity checks.
1836
	if (!file_exists($file) && !is_dir(dirname($file)))
1837
		return false;
1838
1839
	if (!is_int($mtime))
1840
		$mtime = $_SERVER['REQUEST_TIME'];
1841
1842
	$temp_dir = sm_temp_dir();
1843
1844
	// Our temp files.
1845
	$temp_sfile = tempnam($temp_dir, pathinfo($file, PATHINFO_FILENAME) . '.');
0 ignored issues
show
Bug introduced by
Are you sure pathinfo($file, PATHINFO_FILENAME) of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

1845
	$temp_sfile = tempnam($temp_dir, /** @scrutinizer ignore-type */ pathinfo($file, PATHINFO_FILENAME) . '.');
Loading history...
1846
1847
	if (!empty($backup_file))
1848
		$temp_bfile = tempnam($temp_dir, pathinfo($backup_file, PATHINFO_FILENAME) . '.');
0 ignored issues
show
Bug introduced by
Are you sure pathinfo($backup_file, PATHINFO_FILENAME) of type array|string can be used in concatenation? ( Ignorable by Annotation )

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

1848
		$temp_bfile = tempnam($temp_dir, /** @scrutinizer ignore-type */ pathinfo($backup_file, PATHINFO_FILENAME) . '.');
Loading history...
1849
1850
	// We need write permissions.
1851
	$failed = false;
1852
	foreach (array($file, $backup_file) as $sf)
1853
	{
1854
		if (empty($sf))
1855
			continue;
1856
1857
		if (!file_exists($sf))
1858
			touch($sf);
1859
		elseif (!is_file($sf))
1860
			$failed = true;
1861
1862
		if (!$failed)
1863
			$failed = !smf_chmod($sf);
1864
	}
1865
1866
	// Now let's see if writing to a temp file succeeds.
1867
	if (!$failed && file_put_contents($temp_sfile, $data, LOCK_EX) !== strlen($data))
1868
		$failed = true;
1869
1870
	// Tests passed, so it's time to do the job.
1871
	if (!$failed)
1872
	{
1873
		// Back up the backup, just in case.
1874
		if (file_exists($backup_file))
0 ignored issues
show
Bug introduced by
It seems like $backup_file can also be of type null; however, parameter $filename of file_exists() does only seem to accept string, 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

1874
		if (file_exists(/** @scrutinizer ignore-type */ $backup_file))
Loading history...
1875
			$temp_bfile_saved = @copy($backup_file, $temp_bfile);
0 ignored issues
show
Bug introduced by
It seems like $backup_file can also be of type null; however, parameter $from of copy() does only seem to accept string, 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

1875
			$temp_bfile_saved = @copy(/** @scrutinizer ignore-type */ $backup_file, $temp_bfile);
Loading history...
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...
1876
1877
		// Make sure no one changed the file while we weren't looking.
1878
		clearstatcache();
1879
		if (filemtime($file) <= $mtime)
1880
		{
1881
			// Attempt to open the file.
1882
			$sfhandle = @fopen($file, 'c');
1883
1884
			// Let's do this thing!
1885
			if ($sfhandle !== false)
1886
			{
1887
				// Immediately get a lock.
1888
				flock($sfhandle, LOCK_EX);
1889
1890
				// Make sure the backup works before we do anything more.
1891
				$temp_sfile_saved = @copy($file, $temp_sfile);
1892
1893
				// Now write our data to the file.
1894
				if ($temp_sfile_saved)
1895
				{
1896
					if (empty($append))
1897
					{
1898
						ftruncate($sfhandle, 0);
1899
						rewind($sfhandle);
1900
					}
1901
1902
					$failed = fwrite($sfhandle, $data) !== strlen($data);
1903
				}
1904
				else
1905
					$failed = true;
1906
1907
				// If writing failed, put everything back the way it was.
1908
				if ($failed)
1909
				{
1910
					if (!empty($temp_sfile_saved))
1911
						@rename($temp_sfile, $file);
1912
1913
					if (!empty($temp_bfile_saved))
1914
						@rename($temp_bfile, $backup_file);
0 ignored issues
show
Bug introduced by
It seems like $backup_file can also be of type null; however, parameter $to of rename() does only seem to accept string, 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

1914
						@rename($temp_bfile, /** @scrutinizer ignore-type */ $backup_file);
Loading history...
1915
				}
1916
				// It worked, so make our temp backup the new permanent backup.
1917
				elseif (!empty($backup_file))
1918
					@rename($temp_sfile, $backup_file);
1919
1920
				// And we're done.
1921
				flock($sfhandle, LOCK_UN);
1922
				fclose($sfhandle);
1923
			}
1924
		}
1925
	}
1926
1927
	// We're done with these.
1928
	@unlink($temp_sfile);
1929
	@unlink($temp_bfile);
1930
1931
	if ($failed)
1932
		return false;
1933
1934
	// Even though on normal installations the filemtime should invalidate any cached version
1935
	// it seems that there are times it might not. So let's MAKE it dump the cache.
1936
	if (function_exists('opcache_invalidate'))
1937
		opcache_invalidate($file, true);
1938
1939
	return true;
1940
}
1941
1942
/**
1943
 * A wrapper around var_export whose output matches SMF coding conventions.
1944
 *
1945
 * @todo Add special handling for objects?
1946
 *
1947
 * @param mixed $var The variable to export
1948
 * @return mixed A PHP-parseable representation of the variable's value
1949
 */
1950
function smf_var_export($var)
1951
{
1952
	/*
1953
	 * Old versions of updateSettingsFile couldn't handle multi-line values.
1954
	 * Even though technically we can now, we'll keep arrays on one line for
1955
	 * the sake of backwards compatibility.
1956
	 */
1957
	if (is_array($var))
1958
	{
1959
		$return = array();
1960
1961
		foreach ($var as $key => $value)
1962
			$return[] = var_export($key, true) . ' => ' . smf_var_export($value);
1963
1964
		return 'array(' . implode(', ', $return) . ')';
1965
	}
1966
1967
	// For the same reason, replace literal returns and newlines with "\r" and "\n"
1968
	elseif (is_string($var) && (strpos($var, "\n") !== false || strpos($var, "\r") !== false))
1969
	{
1970
		return strtr(
1971
			preg_replace_callback(
1972
				'/[\r\n]+/',
1973
				function($m)
1974
				{
1975
					return '\' . "' . strtr($m[0], array("\r" => '\r', "\n" => '\n')) . '" . \'';
1976
				},
1977
				var_export($var, true)
1978
			),
1979
			array("'' . " => '', " . ''" => '')
1980
		);
1981
	}
1982
1983
	// We typically use lowercase true/false/null.
1984
	elseif (in_array(gettype($var), array('boolean', 'NULL')))
1985
		return strtolower(var_export($var, true));
1986
1987
	// Nothing special.
1988
	else
1989
		return var_export($var, true);
1990
};
1991
1992
/**
1993
 * Deletes all PHP comments from a string.
1994
 *
1995
 * @param string $code_str A string containing PHP code.
1996
 * @return string A string of PHP code with no comments in it.
1997
 */
1998
function strip_php_comments($code_str)
1999
{
2000
	// This is the faster, better way.
2001
	if (is_callable('token_get_all'))
2002
	{
2003
		$tokens = token_get_all($code_str);
2004
2005
		$parts = array();
2006
		foreach ($tokens as $token)
2007
		{
2008
			if (is_string($token))
2009
				$parts[] = $token;
2010
			else
2011
			{
2012
				list($id, $text) = $token;
2013
2014
				switch ($id) {
2015
					case T_COMMENT:
2016
					case T_DOC_COMMENT:
2017
						end($parts);
2018
						$prev_part = key($parts);
2019
2020
						// For the sake of tider output, trim any horizontal
2021
						// whitespace that immediately preceded the comment.
2022
						$parts[$prev_part] = rtrim($parts[$prev_part], "\t ");
2023
2024
						// For 'C' style comments, also trim one preceding
2025
						// line break, if present.
2026
						if (strpos($text, '/*') === 0)
2027
						{
2028
							if (substr($parts[$prev_part], -2) === "\r\n")
2029
								$parts[$prev_part] = substr($parts[$prev_part], 0, -2);
2030
							elseif (in_array(substr($parts[$prev_part], -1), array("\r", "\n")))
2031
								$parts[$prev_part] = substr($parts[$prev_part], 0, -1);
2032
						}
2033
2034
						break;
2035
2036
					default:
2037
						$parts[] = $text;
2038
						break;
2039
				}
2040
			}
2041
		}
2042
2043
		$code_str = implode('', $parts);
2044
2045
		return $code_str;
2046
	}
2047
2048
	// If the tokenizer extension has been disabled, do the job manually.
2049
2050
	// Leave any heredocs alone.
2051
	if (preg_match_all('/<<<([\'"]?)(\w+)\1?\R(.*?)\R\h*\2;$/ms', $code_str, $matches))
2052
	{
2053
		$heredoc_replacements = array();
2054
2055
		foreach ($matches[0] as $mkey => $heredoc)
2056
			$heredoc_replacements[$heredoc] = var_export(md5($matches[3][$mkey]), true) . ';';
2057
2058
		$code_str = strtr($code_str, $heredoc_replacements);
2059
	}
2060
2061
	// Split before everything that could possibly delimit a comment or a string.
2062
	$parts = preg_split('~(?=#+|/(?=/|\*)|\*/|\R|(?<!\\\)[\'"])~m', $code_str);
2063
2064
	$in_string = 0;
2065
	$in_comment = 0;
2066
	foreach ($parts as $partkey => $part)
2067
	{
2068
		$one_char = substr($part, 0, 1);
2069
		$two_char = substr($part, 0, 2);
2070
		$to_remove = 0;
2071
2072
		/*
2073
		 * Meaning of $in_string values:
2074
		 *	0: not in a string
2075
		 *	1: in a single quote string
2076
		 *	2: in a double quote string
2077
		 */
2078
		if ($one_char == "'")
2079
		{
2080
			if (!empty($in_comment))
2081
				$in_string = 0;
2082
			elseif (in_array($in_string, array(0, 1)))
2083
				$in_string = ($in_string ^ 1);
2084
		}
2085
		elseif ($one_char == '"')
2086
		{
2087
			if (!empty($in_comment))
2088
				$in_string = 0;
2089
			elseif (in_array($in_string, array(0, 2)))
2090
				$in_string = ($in_string ^ 2);
2091
		}
2092
2093
		/*
2094
		 * Meaning of $in_comment values:
2095
		 * 	0: not in a comment
2096
		 *	1: in a single line comment
2097
		 *	2: in a multi-line comment
2098
		 */
2099
		elseif ($one_char == '#' || $two_char == '//')
2100
		{
2101
			$in_comment = !empty($in_string) ? 0 : (empty($in_comment) ? 1 : $in_comment);
2102
2103
			if ($in_comment == 1)
2104
			{
2105
				$parts[$partkey - 1] = rtrim($parts[$partkey - 1], "\t ");
2106
2107
				if (substr($parts[$partkey - 1], -2) === "\r\n")
2108
					$parts[$partkey - 1] = substr($parts[$partkey - 1], 0, -2);
2109
				elseif (in_array(substr($parts[$partkey - 1], -1), array("\r", "\n")))
2110
					$parts[$partkey - 1] = substr($parts[$partkey - 1], 0, -1);
2111
			}
2112
		}
2113
		elseif ($two_char === "\r\n" || $one_char === "\r" || $one_char === "\n")
2114
		{
2115
			if ($in_comment == 1)
2116
				$in_comment = 0;
2117
		}
2118
		elseif ($two_char == '/*')
2119
		{
2120
			$in_comment = !empty($in_string) ? 0 : (empty($in_comment) ? 2 : $in_comment);
2121
2122
			if ($in_comment == 2)
2123
			{
2124
				$parts[$partkey - 1] = rtrim($parts[$partkey - 1], "\t ");
2125
2126
				if (substr($parts[$partkey - 1], -2) === "\r\n")
2127
					$parts[$partkey - 1] = substr($parts[$partkey - 1], 0, -2);
2128
				elseif (in_array(substr($parts[$partkey - 1], -1), array("\r", "\n")))
2129
					$parts[$partkey - 1] = substr($parts[$partkey - 1], 0, -1);
2130
			}
2131
		}
2132
		elseif ($two_char == '*/')
2133
		{
2134
			if ($in_comment == 2)
2135
			{
2136
				$in_comment = 0;
2137
2138
				// Delete the comment closing.
2139
				$to_remove = 2;
2140
			}
2141
		}
2142
2143
		if (empty($in_comment))
2144
			$parts[$partkey] = strlen($part) > $to_remove ? substr($part, $to_remove) : '';
2145
		else
2146
			$parts[$partkey] = '';
2147
	}
2148
2149
	$code_str = implode('', $parts);
2150
2151
	if (!empty($heredoc_replacements))
2152
		$code_str = strtr($code_str, array_flip($heredoc_replacements));
2153
2154
	return $code_str;
2155
}
2156
2157
/**
2158
 * Saves the time of the last db error for the error log
2159
 * - Done separately from updateSettingsFile to avoid race conditions
2160
 *   which can occur during a db error
2161
 * - If it fails Settings.php will assume 0
2162
 *
2163
 * @param int $time The timestamp of the last DB error
2164
 * @param bool True If we should update the current db_last_error context as well.  This may be useful in cases where the current context needs to know a error was logged since the last check.
2165
 * @return bool True If we could succesfully put the file or not.
2166
 */
2167
function updateDbLastError($time, $update = true)
2168
{
2169
	global $boarddir, $cachedir, $db_last_error;
2170
2171
	// Write out the db_last_error file with the error timestamp
2172
	if (!empty($cachedir) && is_writable($cachedir))
2173
		$errorfile = $cachedir . '/db_last_error.php';
2174
2175
	elseif (file_exists(dirname(__DIR__) . '/cache'))
2176
		$errorfile = dirname(__DIR__) . '/cache/db_last_error.php';
2177
2178
	else
2179
		$errorfile = dirname(__DIR__) . '/db_last_error.php';
2180
2181
	$result = file_put_contents($errorfile, '<' . '?' . "php\n" . '$db_last_error = ' . $time . ';' . "\n" . '?' . '>', LOCK_EX);
2182
2183
	@touch($boarddir . '/' . 'Settings.php');
2184
2185
	// Unless requested, we should update $db_last_error as well.
2186
	if ($update)
2187
		$db_last_error = $time;
2188
2189
	// We  do a loose match here rather than strict (!==) as 0 is also false.
2190
	return $result != false;
0 ignored issues
show
Bug Best Practice introduced by
It seems like you are loosely comparing $result of type integer to the boolean false. If you are specifically checking for non-zero, consider using something more explicit like > 0 or !== 0 instead.
Loading history...
2191
}
2192
2193
/**
2194
 * Saves the admin's current preferences to the database.
2195
 */
2196
function updateAdminPreferences()
2197
{
2198
	global $options, $context, $smcFunc, $settings, $user_info;
2199
2200
	// This must exist!
2201
	if (!isset($context['admin_preferences']))
2202
		return false;
2203
2204
	// This is what we'll be saving.
2205
	$options['admin_preferences'] = $smcFunc['json_encode']($context['admin_preferences']);
2206
2207
	// Just check we haven't ended up with something theme exclusive somehow.
2208
	$smcFunc['db_query']('', '
2209
		DELETE FROM {db_prefix}themes
2210
		WHERE id_theme != {int:default_theme}
2211
			AND variable = {string:admin_preferences}',
2212
		array(
2213
			'default_theme' => 1,
2214
			'admin_preferences' => 'admin_preferences',
2215
		)
2216
	);
2217
2218
	// Update the themes table.
2219
	$smcFunc['db_insert']('replace',
2220
		'{db_prefix}themes',
2221
		array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string-255', 'value' => 'string-65534'),
2222
		array($user_info['id'], 1, 'admin_preferences', $options['admin_preferences']),
2223
		array('id_member', 'id_theme', 'variable')
2224
	);
2225
2226
	// Make sure we invalidate any cache.
2227
	cache_put_data('theme_settings-' . $settings['theme_id'] . ':' . $user_info['id'], null, 0);
2228
}
2229
2230
/**
2231
 * Send all the administrators a lovely email.
2232
 * - loads all users who are admins or have the admin forum permission.
2233
 * - uses the email template and replacements passed in the parameters.
2234
 * - sends them an email.
2235
 *
2236
 * @param string $template Which email template to use
2237
 * @param array $replacements An array of items to replace the variables in the template
2238
 * @param array $additional_recipients An array of arrays of info for additional recipients. Should have 'id', 'email' and 'name' for each.
2239
 */
2240
function emailAdmins($template, $replacements = array(), $additional_recipients = array())
2241
{
2242
	global $smcFunc, $sourcedir, $language, $modSettings;
2243
2244
	// We certainly want this.
2245
	require_once($sourcedir . '/Subs-Post.php');
2246
2247
	// Load all members which are effectively admins.
2248
	require_once($sourcedir . '/Subs-Members.php');
2249
	$members = membersAllowedTo('admin_forum');
2250
2251
	// Load their alert preferences
2252
	require_once($sourcedir . '/Subs-Notify.php');
2253
	$prefs = getNotifyPrefs($members, 'announcements', true);
2254
2255
	$request = $smcFunc['db_query']('', '
2256
		SELECT id_member, member_name, real_name, lngfile, email_address
2257
		FROM {db_prefix}members
2258
		WHERE id_member IN({array_int:members})',
2259
		array(
2260
			'members' => $members,
2261
		)
2262
	);
2263
	$emails_sent = array();
2264
	while ($row = $smcFunc['db_fetch_assoc']($request))
2265
	{
2266
		if (empty($prefs[$row['id_member']]['announcements']))
2267
			continue;
2268
2269
		// Stick their particulars in the replacement data.
2270
		$replacements['IDMEMBER'] = $row['id_member'];
2271
		$replacements['REALNAME'] = $row['member_name'];
2272
		$replacements['USERNAME'] = $row['real_name'];
2273
2274
		// Load the data from the template.
2275
		$emaildata = loadEmailTemplate($template, $replacements, empty($row['lngfile']) || empty($modSettings['userLanguage']) ? $language : $row['lngfile']);
2276
2277
		// Then send the actual email.
2278
		sendmail($row['email_address'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
2279
2280
		// Track who we emailed so we don't do it twice.
2281
		$emails_sent[] = $row['email_address'];
2282
	}
2283
	$smcFunc['db_free_result']($request);
2284
2285
	// Any additional users we must email this to?
2286
	if (!empty($additional_recipients))
2287
		foreach ($additional_recipients as $recipient)
2288
		{
2289
			if (in_array($recipient['email'], $emails_sent))
2290
				continue;
2291
2292
			$replacements['IDMEMBER'] = $recipient['id'];
2293
			$replacements['REALNAME'] = $recipient['name'];
2294
			$replacements['USERNAME'] = $recipient['name'];
2295
2296
			// Load the template again.
2297
			$emaildata = loadEmailTemplate($template, $replacements, empty($recipient['lang']) || empty($modSettings['userLanguage']) ? $language : $recipient['lang']);
2298
2299
			// Send off the email.
2300
			sendmail($recipient['email'], $emaildata['subject'], $emaildata['body'], null, $template, $emaildata['is_html'], 1);
2301
		}
2302
}
2303
2304
/**
2305
 * Locates the most appropriate temp directory.
2306
 *
2307
 * Systems using `open_basedir` restrictions may receive errors with
2308
 * `sys_get_temp_dir()` due to misconfigurations on servers. Other
2309
 * cases sys_temp_dir may not be set to a safe value. Additionally
2310
 * `sys_get_temp_dir` may use a readonly directory. This attempts to
2311
 * find a working temp directory that is accessible under the
2312
 * restrictions and is writable to the web service account.
2313
 *
2314
 * Directories checked against `open_basedir`:
2315
 *
2316
 * - `sys_get_temp_dir()`
2317
 * - `upload_tmp_dir`
2318
 * - `session.save_path`
2319
 * - `cachedir`
2320
 *
2321
 * @return string
2322
*/
2323
function sm_temp_dir()
2324
{
2325
	global $cachedir;
2326
2327
	static $temp_dir = null;
2328
2329
	// Already did this.
2330
	if (!empty($temp_dir))
2331
		return $temp_dir;
2332
2333
	// Temp Directory options order.
2334
	$temp_dir_options = array(
2335
		0 => 'sys_get_temp_dir',
2336
		1 => 'upload_tmp_dir',
2337
		2 => 'session.save_path',
2338
		3 => 'cachedir'
2339
	);
2340
2341
	// Determine if we should detect a restriction and what restrictions that may be.
2342
	$open_base_dir = ini_get('open_basedir');
2343
	$restriction = !empty($open_base_dir) ? explode(':', $open_base_dir) : false;
2344
2345
	// Prevent any errors as we search.
2346
	$old_error_reporting = error_reporting(0);
2347
2348
	// Search for a working temp directory.
2349
	foreach ($temp_dir_options as $id_temp => $temp_option)
2350
	{
2351
		switch ($temp_option) {
2352
			case 'cachedir':
2353
				$possible_temp = rtrim($cachedir, '/');
2354
				break;
2355
2356
			case 'session.save_path':
2357
				$possible_temp = rtrim(ini_get('session.save_path'), '/');
2358
				break;
2359
2360
			case 'upload_tmp_dir':
2361
				$possible_temp = rtrim(ini_get('upload_tmp_dir'), '/');
2362
				break;
2363
2364
			default:
2365
				$possible_temp = sys_get_temp_dir();
2366
				break;
2367
		}
2368
2369
		// Check if we have a restriction preventing this from working.
2370
		if ($restriction)
2371
		{
2372
			foreach ($restriction as $dir)
2373
			{
2374
				if (strpos($possible_temp, $dir) !== false && is_writable($possible_temp))
2375
				{
2376
					$temp_dir = $possible_temp;
2377
					break;
2378
				}
2379
			}
2380
		}
2381
		// No restrictions, but need to check for writable status.
2382
		elseif (is_writable($possible_temp))
2383
		{
2384
			$temp_dir = $possible_temp;
2385
			break;
2386
		}
2387
	}
2388
2389
	// Fall back to sys_get_temp_dir even though it won't work, so we have something.
2390
	if (empty($temp_dir))
2391
		$temp_dir = sys_get_temp_dir();
2392
2393
	// Fix the path.
2394
	$temp_dir = substr($temp_dir, -1) === '/' ? $temp_dir : $temp_dir . '/';
2395
2396
	// Put things back.
2397
	error_reporting($old_error_reporting);
2398
2399
	return $temp_dir;
2400
}
2401
2402
?>