Passed
Pull Request — release-2.1 (#6101)
by Jon
05:38
created

loadMinUserSettings()   C

Complexity

Conditions 11
Paths 196

Size

Total Lines 77
Code Lines 47

Duplication

Lines 0
Ratio 0 %

Importance

Changes 10
Bugs 7 Features 1
Metric Value
cc 11
eloc 47
c 10
b 7
f 1
nc 196
nop 1
dl 0
loc 77
rs 6.5166

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 has the hefty job of loading information for the forum.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 2020 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC2
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Load the $modSettings array.
21
 */
22
function reloadSettings()
23
{
24
	global $modSettings, $boarddir, $smcFunc, $txt, $db_character_set;
25
	global $cache_enable, $sourcedir, $context, $forum_version, $boardurl;
26
	global $image_proxy_enabled;
27
28
	// Most database systems have not set UTF-8 as their default input charset.
29
	if (!empty($db_character_set))
30
		$smcFunc['db_query']('', '
31
			SET NAMES {string:db_character_set}',
32
			array(
33
				'db_character_set' => $db_character_set,
34
			)
35
		);
36
37
	// We need some caching support, maybe.
38
	loadCacheAccelerator();
39
40
	// Try to load it from the cache first; it'll never get cached if the setting is off.
41
	if (($modSettings = cache_get_data('modSettings', 90)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $modSettings = cache_get_data('modSettings', 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
42
	{
43
		$request = $smcFunc['db_query']('', '
44
			SELECT variable, value
45
			FROM {db_prefix}settings',
46
			array(
47
			)
48
		);
49
		$modSettings = array();
50
		if (!$request)
51
			display_db_error();
52
		foreach ($smcFunc['db_fetch_all']($request) as $row)
53
			$modSettings[$row['variable']] = $row['value'];
54
		$smcFunc['db_free_result']($request);
55
56
		// Do a few things to protect against missing settings or settings with invalid values...
57
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
58
			$modSettings['defaultMaxTopics'] = 20;
59
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
60
			$modSettings['defaultMaxMessages'] = 15;
61
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
62
			$modSettings['defaultMaxMembers'] = 30;
63
		if (empty($modSettings['defaultMaxListItems']) || $modSettings['defaultMaxListItems'] <= 0 || $modSettings['defaultMaxListItems'] > 999)
64
			$modSettings['defaultMaxListItems'] = 15;
65
66
		// We explicitly do not use $smcFunc['json_decode'] here yet, as $smcFunc is not fully loaded.
67
		if (!is_array($modSettings['attachmentUploadDir']))
68
		{
69
			$attachmentUploadDir = smf_json_decode($modSettings['attachmentUploadDir'], true);
70
			$modSettings['attachmentUploadDir'] = !empty($attachmentUploadDir) ? $attachmentUploadDir : $modSettings['attachmentUploadDir'];
71
		}
72
73
		if (!empty($cache_enable))
74
			cache_put_data('modSettings', $modSettings, 90);
75
	}
76
77
	// Going anything further when the files don't match the database can make nasty messes (unless we're actively installing or upgrading)
78
	if (!defined('SMF_INSTALLING') && (!isset($_REQUEST['action']) || $_REQUEST['action'] !== 'admin' || !isset($_REQUEST['area']) || $_REQUEST['area'] !== 'packages') && !empty($modSettings['smfVersion']) && version_compare(strtolower(strtr($modSettings['smfVersion'], array(' ' => '.'))), strtolower(strtr(SMF_VERSION, array(' ' => '.'))), '!='))
79
	{
80
		// Wipe the cached $modSettings values so they don't interfere with anything later
81
		cache_put_data('modSettings', null);
82
83
		// Redirect to the upgrader if we can
84
		if (file_exists($boarddir . '/upgrade.php'))
85
			header('location: ' . $boardurl . '/upgrade.php');
86
87
		die('SMF file version (' . SMF_VERSION . ') does not match SMF database version (' . $modSettings['smfVersion'] . ').<br>Run the SMF upgrader to fix this.<br><a href="https://wiki.simplemachines.org/smf/Upgrading">More information</a>.');
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
88
	}
89
90
	$modSettings['cache_enable'] = $cache_enable;
91
92
	// Used to force browsers to download fresh CSS and JavaScript when necessary
93
	$modSettings['browser_cache'] = !empty($modSettings['browser_cache']) ? (int) $modSettings['browser_cache'] : 0;
94
	$context['browser_cache'] = '?' . preg_replace('~\W~', '', strtolower(SMF_FULL_VERSION)) . '_' . $modSettings['browser_cache'];
95
96
	// Disable image proxy if we don't have SSL enabled
97
	if (empty($modSettings['force_ssl']))
98
		$image_proxy_enabled = false;
99
100
	// UTF-8 ?
101
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
102
	$context['utf8'] = $utf8;
103
104
	// Set a list of common functions.
105
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
106
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
107
		{
108
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
109
			return $string;
110
		} : function($string)
111
		{
112
			return $string;
113
		};
114
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
115
	{
116
		if (!$utf8 || $smcFunc['db_mb4'])
117
			return $string;
118
119
		$i = 0;
120
		$len = strlen($string);
121
		$new_string = '';
122
		while ($i < $len)
123
		{
124
			$ord = ord($string[$i]);
125
			if ($ord < 128)
126
			{
127
				$new_string .= $string[$i];
128
				$i++;
129
			}
130
			elseif ($ord < 224)
131
			{
132
				$new_string .= $string[$i] . $string[$i + 1];
133
				$i += 2;
134
			}
135
			elseif ($ord < 240)
136
			{
137
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
138
				$i += 3;
139
			}
140
			elseif ($ord < 248)
141
			{
142
				// Magic happens.
143
				$val = (ord($string[$i]) & 0x07) << 18;
144
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
145
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
146
				$val += (ord($string[$i + 3]) & 0x3F);
147
				$new_string .= '&#' . $val . ';';
148
				$i += 4;
149
			}
150
		}
151
		return $new_string;
152
	};
153
154
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
155
	$smcFunc += array(
156
		'entity_fix' => function($string)
157
		{
158
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
159
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
160
		},
161
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
162
		{
163
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
164
		},
165
		'htmltrim' => function($string) use ($utf8, $ent_check)
166
		{
167
			// Preg_replace space characters depend on the character set in use
168
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
169
170
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
171
		},
172
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
173
		{
174
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
175
		},
176
		'strpos' => function($haystack, $needle, $offset = 0) use ($utf8, $ent_check, $ent_list, $modSettings)
0 ignored issues
show
Unused Code introduced by
The import $modSettings is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
177
		{
178
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
179
180
			if (strlen($needle) === 1)
181
			{
182
				$result = array_search($needle, array_slice($haystack_arr, $offset));
0 ignored issues
show
Bug introduced by
It seems like $haystack_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

182
				$result = array_search($needle, array_slice(/** @scrutinizer ignore-type */ $haystack_arr, $offset));
Loading history...
183
				return is_int($result) ? $result + $offset : false;
184
			}
185
			else
186
			{
187
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
188
				$needle_size = count($needle_arr);
0 ignored issues
show
Bug introduced by
It seems like $needle_arr can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

188
				$needle_size = count(/** @scrutinizer ignore-type */ $needle_arr);
Loading history...
189
190
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
191
				while ((int) $result === $result)
192
				{
193
					$offset += $result;
194
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
195
						return $offset;
196
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
197
				}
198
				return false;
199
			}
200
		},
201
		'substr' => function($string, $start, $length = null) use ($utf8, $ent_check, $ent_list, $modSettings)
0 ignored issues
show
Unused Code introduced by
The import $modSettings is not used and could be removed.

This check looks for imports that have been defined, but are not used in the scope.

Loading history...
202
		{
203
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
204
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
0 ignored issues
show
Bug introduced by
It seems like $ent_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

204
			return $length === null ? implode('', array_slice(/** @scrutinizer ignore-type */ $ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
Loading history...
205
		},
206
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
207
		{
208
			if (!function_exists('mb_strtolower'))
209
			{
210
				require_once($sourcedir . '/Subs-Charset.php');
211
				return utf8_strtolower($string);
212
			}
213
214
			return mb_strtolower($string, 'UTF-8');
215
		} : 'strtolower',
216
		'strtoupper' => $utf8 ? function($string)
217
		{
218
			global $sourcedir;
219
220
			if (!function_exists('mb_strtolower'))
221
			{
222
				require_once($sourcedir . '/Subs-Charset.php');
223
				return utf8_strtoupper($string);
224
			}
225
226
			return mb_strtoupper($string, 'UTF-8');
227
		} : 'strtoupper',
228
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
229
		{
230
			$string = $ent_check($string);
231
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
232
			$string = $matches[0];
233
			while (strlen($string) > $length)
234
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
235
			return $string;
236
		},
237
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
238
		{
239
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
240
		} : 'ucfirst',
241
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
242
		{
243
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
244
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

244
			for ($i = 0, $n = count(/** @scrutinizer ignore-type */ $words); $i < $n; $i += 2)
Loading history...
245
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
246
			return implode('', $words);
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

246
			return implode('', /** @scrutinizer ignore-type */ $words);
Loading history...
247
		} : 'ucwords',
248
		'json_decode' => 'smf_json_decode',
249
		'json_encode' => 'json_encode',
250
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
251
		{
252
			global $sourcedir;
253
254
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
255
			if (!is_callable('random_int'))
256
				require_once($sourcedir . '/random_compat/random.php');
257
258
			return random_int($min, $max);
259
		},
260
		'random_bytes' => function($length = 64)
261
		{
262
			global $sourcedir;
263
264
			if (!is_callable('random_bytes'))
265
				require_once($sourcedir . '/random_compat/random.php');
266
267
			// Make sure length is valid
268
			$length = max(1, (int) $length);
269
270
			return random_bytes($length);
271
		},
272
	);
273
274
	// Setting the timezone is a requirement for some functions.
275
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
It seems like timezone_identifiers_list() can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

275
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
276
		date_default_timezone_set($modSettings['default_timezone']);
277
	else
278
	{
279
		// Get PHP's default timezone, if set
280
		$ini_tz = ini_get('date.timezone');
281
		if (!empty($ini_tz))
282
			$modSettings['default_timezone'] = $ini_tz;
283
		else
284
			$modSettings['default_timezone'] = '';
285
286
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
287
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
288
		{
289
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
290
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
291
		}
292
293
		date_default_timezone_set($modSettings['default_timezone']);
294
	}
295
296
	// Check the load averages?
297
	if (!empty($modSettings['loadavg_enable']))
298
	{
299
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $modSettings['load_avera...get_data('loadavg', 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
300
		{
301
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
302
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
303
				$modSettings['load_average'] = (float) $matches[1];
304
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
305
				$modSettings['load_average'] = (float) $matches[1];
306
			else
307
				unset($modSettings['load_average']);
308
309
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
310
				cache_put_data('loadavg', $modSettings['load_average'], 90);
311
		}
312
313
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
314
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
315
316
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
317
			display_loadavg_error();
318
	}
319
320
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
321
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
322
323
	// Here to justify the name of this function. :P
324
	// It should be added to the install and upgrade scripts.
325
	// But since the converters need to be updated also. This is easier.
326
	if (empty($modSettings['currentAttachmentUploadDir']))
327
	{
328
		updateSettings(array(
329
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
330
			'currentAttachmentUploadDir' => 1,
331
		));
332
	}
333
334
	// Respect PHP's limits.
335
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
336
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
337
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
338
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
339
340
	// Integration is cool.
341
	if (defined('SMF_INTEGRATION_SETTINGS'))
342
	{
343
		$integration_settings = $smcFunc['json_decode'](SMF_INTEGRATION_SETTINGS, true);
0 ignored issues
show
Bug introduced by
The constant SMF_INTEGRATION_SETTINGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
344
		foreach ($integration_settings as $hook => $function)
345
			add_integration_function($hook, $function, false);
346
	}
347
348
	// Any files to pre include?
349
	if (!empty($modSettings['integrate_pre_include']))
350
	{
351
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
352
		foreach ($pre_includes as $include)
353
		{
354
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
355
			if (file_exists($include))
356
				require_once($include);
357
		}
358
	}
359
360
	// This determines the server... not used in many places, except for login fixing.
361
	$context['server'] = array(
362
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
363
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
364
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
365
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
366
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
367
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
368
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
369
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
370
	);
371
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
372
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
373
374
	// Define a list of icons used across multiple places.
375
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
376
377
	// Define an array for custom profile fields placements.
378
	$context['cust_profile_fields_placement'] = array(
379
		'standard',
380
		'icons',
381
		'above_signature',
382
		'below_signature',
383
		'below_avatar',
384
		'above_member',
385
		'bottom_poster',
386
		'before_member',
387
		'after_member',
388
	);
389
390
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
391
	$context['meta_tags'] = array();
392
393
	// Define an array of allowed HTML tags.
394
	$context['allowed_html_tags'] = array(
395
		'<img>',
396
		'<div>',
397
	);
398
399
	// These are the only valid image types for SMF attachments, by default anyway.
400
	// Note: The values are for image mime types, not file extensions.
401
	$context['valid_image_types'] = array(
402
		IMAGETYPE_GIF => 'gif',
403
		IMAGETYPE_JPEG => 'jpeg',
404
		IMAGETYPE_PNG => 'png',
405
		IMAGETYPE_PSD => 'psd',
406
		IMAGETYPE_BMP => 'bmp',
407
		IMAGETYPE_TIFF_II => 'tiff',
408
		IMAGETYPE_TIFF_MM => 'tiff',
409
		IMAGETYPE_IFF => 'iff'
410
	);
411
412
	// Define a list of allowed tags for descriptions.
413
	$context['description_allowed_tags'] = array(
414
		'abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img',
415
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
416
		'sup', 'table', 'td', 'tr', 'u', 'url',
417
	);
418
419
	// Define a list of deprecated BBC tags
420
	// Even when enabled, they'll only work in old posts and not new ones
421
	$context['legacy_bbc'] = array(
422
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
423
		'green', 'move', 'red', 'shadow', 'tt', 'white',
424
	);
425
426
	// Define a list of BBC tags that require permissions to use
427
	$context['restricted_bbc'] = array(
428
		'html',
429
	);
430
431
	// Call pre load integration functions.
432
	call_integration_hook('integrate_pre_load');
433
}
434
435
/**
436
 * Load all the important user information.
437
 * What it does:
438
 * 	- sets up the $user_info array
439
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
440
 * 	- first checks for cookie or integration validation.
441
 * 	- uses the current session if no integration function or cookie is found.
442
 * 	- checks password length, if member is activated and the login span isn't over.
443
 * 		- if validation fails for the user, $id_member is set to 0.
444
 * 		- updates the last visit time when needed.
445
 */
446
function loadUserSettings()
447
{
448
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
449
	global $cookiename, $user_info, $language, $context, $cache_enable;
450
451
	require_once($sourcedir . '/Subs-Auth.php');
452
453
	// Check first the integration, then the cookie, and last the session.
454
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
455
	{
456
		$id_member = 0;
457
		foreach ($integration_ids as $integration_id)
458
		{
459
			$integration_id = (int) $integration_id;
460
			if ($integration_id > 0)
461
			{
462
				$id_member = $integration_id;
463
				$already_verified = true;
464
				break;
465
			}
466
		}
467
	}
468
	else
469
		$id_member = 0;
470
471
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
472
	{
473
		// First try 2.1 json-format cookie
474
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
475
476
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
477
		if (empty($cookie_data))
478
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
479
480
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
481
482
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
483
484
		// Make sure the cookie is set to the correct domain and path
485
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
486
			setLoginCookie((int) $login_span - time(), $id_member);
487
	}
488
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
489
	{
490
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
491
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
492
493
		if (empty($cookie_data))
494
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
495
496
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
497
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
498
	}
499
500
	// Only load this stuff if the user isn't a guest.
501
	if ($id_member != 0)
502
	{
503
		// Is the member data cached?
504
		if (empty($cache_enable) || $cache_enable < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $user_settings = cache_g...ngs-' . $id_member, 60) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
505
		{
506
			$request = $smcFunc['db_query']('', '
507
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
508
				FROM {db_prefix}members AS mem
509
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
510
				WHERE mem.id_member = {int:id_member}
511
				LIMIT 1',
512
				array(
513
					'id_member' => $id_member,
514
				)
515
			);
516
			$user_settings = $smcFunc['db_fetch_assoc']($request);
517
			$smcFunc['db_free_result']($request);
518
519
			if (!empty($user_settings['avatar']))
520
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
521
522
			if (!empty($cache_enable) && $cache_enable >= 2)
523
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
524
		}
525
526
		// Did we find 'im?  If not, junk it.
527
		if (!empty($user_settings))
528
		{
529
			// As much as the password should be right, we can assume the integration set things up.
530
			if (!empty($already_verified) && $already_verified === true)
531
				$check = true;
532
			// SHA-512 hash should be 128 characters long.
533
			elseif (strlen($password) == 128)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.
Loading history...
534
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
535
			else
536
				$check = false;
537
538
			// Wrong password or not activated - either way, you're going nowhere.
539
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
540
		}
541
		else
542
			$id_member = 0;
543
544
		// Check if we are forcing TFA
545
		$force_tfasetup = !empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']) && SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != '.xml');
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
546
547
		// Don't force TFA on popups
548
		if ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
549
		{
550
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
551
				$force_tfasetup = false;
552
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
553
				$force_tfasetup = false;
554
555
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
556
		}
557
558
		// If we no longer have the member maybe they're being all hackey, stop brute force!
559
		if (!$id_member)
560
		{
561
			require_once($sourcedir . '/LogInOut.php');
562
			validatePasswordFlood(
563
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
564
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
565
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
566
				$id_member != 0
567
			);
568
		}
569
		// Validate for Two Factor Authentication
570
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
571
		{
572
			$tfacookie = $cookiename . '_tfa';
573
			$tfasecret = null;
574
575
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
576
577
			if (empty($verified) || !in_array(true, $verified))
578
			{
579
				if (!empty($_COOKIE[$tfacookie]))
580
				{
581
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
582
583
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
584
585
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
586
						$tfasecret = null;
587
				}
588
589
				// They didn't finish logging in before coming here? Then they're no one to us.
590
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
591
				{
592
					setLoginCookie(-3600, $id_member);
593
					$id_member = 0;
594
					$user_settings = array();
595
				}
596
			}
597
		}
598
		// When authenticating their two factor code, make sure to reset their ID for security
599
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
600
		{
601
			$id_member = 0;
602
			$context['tfa_member'] = $user_settings;
603
			$user_settings = array();
604
		}
605
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
606
		elseif ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
607
		{
608
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
609
			{
610
				//Build an array of ALL user membergroups.
611
				$full_groups = array($user_settings['id_group']);
612
				if (!empty($user_settings['additional_groups']))
613
				{
614
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
615
					$full_groups = array_unique($full_groups); //duplicates, maybe?
616
				}
617
618
				//Find out if any group requires 2FA
619
				$request = $smcFunc['db_query']('', '
620
					SELECT COUNT(id_group) AS total
621
					FROM {db_prefix}membergroups
622
					WHERE tfa_required = {int:tfa_required}
623
						AND id_group IN ({array_int:full_groups})',
624
					array(
625
						'tfa_required' => 1,
626
						'full_groups' => $full_groups,
627
					)
628
				);
629
				$row = $smcFunc['db_fetch_assoc']($request);
630
				$smcFunc['db_free_result']($request);
631
			}
632
			else
633
				$row['total'] = 1; //simplifies logics in the next "if"
0 ignored issues
show
Comprehensibility Best Practice introduced by
$row was never initialized. Although not strictly required by PHP, it is generally a good practice to add $row = array(); before regardless.
Loading history...
634
635
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
636
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
637
638
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: ($row['total'] > 0 && ! ... && $area != 'tfasetup', Probably Intended Meaning: $row['total'] > 0 && (! ...&& $area != 'tfasetup')
Loading history...
639
				redirectexit('action=profile;area=tfasetup;forced');
640
		}
641
	}
642
643
	// Found 'im, let's set up the variables.
644
	if ($id_member != 0)
645
	{
646
		// Let's not update the last visit time in these cases...
647
		// 1. SSI doesn't count as visiting the forum.
648
		// 2. RSS feeds and XMLHTTP requests don't count either.
649
		// 3. If it was set within this session, no need to set it again.
650
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
651
		// 5. We're still logging in or authenticating
652
		if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))) && empty($_SESSION['id_msg_last_visit']) && (empty($cache_enable) || ($_SESSION['id_msg_last_visit'] = cache_get_data('user_last_visit-' . $id_member, 5 * 3600)) === null))
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
653
		{
654
			// @todo can this be cached?
655
			// Do a quick query to make sure this isn't a mistake.
656
			$result = $smcFunc['db_query']('', '
657
				SELECT poster_time
658
				FROM {db_prefix}messages
659
				WHERE id_msg = {int:id_msg}
660
				LIMIT 1',
661
				array(
662
					'id_msg' => $user_settings['id_msg_last_visit'],
663
				)
664
			);
665
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
666
			$smcFunc['db_free_result']($result);
667
668
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
669
670
			// If it was *at least* five hours ago...
671
			if ($visitTime < time() - 5 * 3600)
672
			{
673
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $_SERVER['REMOTE_ADDR'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']));
674
				$user_settings['last_login'] = time();
675
676
				if (!empty($cache_enable) && $cache_enable >= 2)
677
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
678
679
				if (!empty($cache_enable))
680
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
681
			}
682
		}
683
		elseif (empty($_SESSION['id_msg_last_visit']))
684
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
685
686
		$username = $user_settings['member_name'];
687
688
		if (empty($user_settings['additional_groups']))
689
			$user_info = array(
690
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
691
			);
692
693
		else
694
			$user_info = array(
695
				'groups' => array_merge(
696
					array($user_settings['id_group'], $user_settings['id_post_group']),
697
					explode(',', $user_settings['additional_groups'])
698
				)
699
			);
700
701
		// Because history has proven that it is possible for groups to go bad - clean up in case.
702
		$user_info['groups'] = array_map('intval', $user_info['groups']);
703
704
		// This is a logged in user, so definitely not a spider.
705
		$user_info['possibly_robot'] = false;
706
707
		// Figure out the new time offset.
708
		if (!empty($user_settings['timezone']))
709
		{
710
			// Get the offsets from UTC for the server, then for the user.
711
			$tz_system = new DateTimeZone(@date_default_timezone_get());
712
			$tz_user = new DateTimeZone($user_settings['timezone']);
713
			$time_system = new DateTime('now', $tz_system);
714
			$time_user = new DateTime('now', $tz_user);
715
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
716
		}
717
		else
718
		{
719
			// !!! Compatibility.
720
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
721
		}
722
	}
723
	// If the user is a guest, initialize all the critical user settings.
724
	else
725
	{
726
		// This is what a guest's variables should be.
727
		$username = '';
728
		$user_info = array('groups' => array(-1));
729
		$user_settings = array();
730
731
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
732
			$_COOKIE[$cookiename] = '';
733
734
		// Expire the 2FA cookie
735
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
736
		{
737
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
738
739
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
740
741
			if (time() > $exp)
742
			{
743
				$_COOKIE[$cookiename . '_tfa'] = '';
744
				setTFACookie(-3600, 0, '');
745
			}
746
		}
747
748
		// Create a login token if it doesn't exist yet.
749
		if (!isset($_SESSION['token']['post-login']))
750
			createToken('login');
751
		else
752
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
753
754
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
755
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
756
		{
757
			require_once($sourcedir . '/ManageSearchEngines.php');
758
			$user_info['possibly_robot'] = SpiderCheck();
759
		}
760
		elseif (!empty($modSettings['spider_mode']))
761
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
762
		// If we haven't turned on proper spider hunts then have a guess!
763
		else
764
		{
765
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
766
			$user_info['possibly_robot'] = (strpos($_SERVER['HTTP_USER_AGENT'], 'Mozilla') === false && strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') === false) || strpos($ci_user_agent, 'googlebot') !== false || strpos($ci_user_agent, 'slurp') !== false || strpos($ci_user_agent, 'crawl') !== false || strpos($ci_user_agent, 'bingbot') !== false || strpos($ci_user_agent, 'bingpreview') !== false || strpos($ci_user_agent, 'adidxbot') !== false || strpos($ci_user_agent, 'msnbot') !== false;
767
		}
768
769
		// We don't know the offset...
770
		$user_info['time_offset'] = 0;
771
	}
772
773
	// Login Cookie times. Format: time => txt
774
	$context['login_cookie_times'] = array(
775
		3153600 => 'always_logged_in',
776
		60 => 'one_hour',
777
		1440 => 'one_day',
778
		10080 => 'one_week',
779
		43200 => 'one_month',
780
	);
781
782
	// Set up the $user_info array.
783
	$user_info += array(
784
		'id' => $id_member,
785
		'username' => $username,
786
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
787
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
788
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
789
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
790
		'is_guest' => $id_member == 0,
791
		'is_admin' => in_array(1, $user_info['groups']),
792
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
793
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
794
		'ip' => $_SERVER['REMOTE_ADDR'],
795
		'ip2' => $_SERVER['BAN_CHECK_IP'],
796
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
797
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
798
		'avatar' => array(
799
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
800
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
801
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
802
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
803
		),
804
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
805
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
806
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
807
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
808
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
809
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
810
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
811
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
812
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
813
		'permissions' => array(),
814
	);
815
	$user_info['groups'] = array_unique($user_info['groups']);
816
	$user_info['can_manage_boards'] = !empty($user_info['is_admin']) || (!empty($modSettings['board_manager_groups']) && count(array_intersect($user_info['groups'], explode(',', $modSettings['board_manager_groups']))) > 0);
817
818
	// Make sure that the last item in the ignore boards array is valid. If the list was too long it could have an ending comma that could cause problems.
819
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
820
		unset($user_info['ignoreboards'][$tmp]);
821
822
	// Allow the user to change their language.
823
	if (!empty($modSettings['userLanguage']))
824
	{
825
		$languages = getLanguages();
826
827
		// Is it valid?
828
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
829
		{
830
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
831
832
			// Make it permanent for members.
833
			if (!empty($user_info['id']))
834
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
835
			else
836
				$_SESSION['language'] = $user_info['language'];
837
		}
838
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
839
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
840
	}
841
842
	$temp = build_query_board($user_info['id']);
843
	$user_info['query_see_board'] = $temp['query_see_board'];
844
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
845
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
846
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
847
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
848
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
849
850
	call_integration_hook('integrate_user_info');
851
}
852
853
/**
854
 * Load minimal user info from members table.
855
 * Intended for use by background tasks that need to populate $user_info.
856
 *
857
 * @param int|array $user_ids The users IDs to get the data for.
858
 * @return array
859
 * @throws Exception
860
 */
861
function loadMinUserInfo($user_ids = array())
862
{
863
	global $smcFunc, $modSettings, $language;
864
	static $user_info_min = array();
865
866
	$user_ids = (array) $user_ids;
867
868
	// Already loaded?
869
	if (!empty($user_ids))
870
		$user_ids = array_diff($user_ids, array_keys($user_info_min));
871
872
	if (empty($user_ids))
873
		return $user_info_min;
874
875
	$columns_to_load = array(
876
		'id_member',
877
		'member_name',
878
		'real_name',
879
		'time_offset',
880
		'additional_groups',
881
		'id_group',
882
		'id_post_group',
883
		'lngfile',
884
		'smiley_set',
885
		'timezone',
886
	);
887
888
	call_integration_hook('integrate_load_min_user_settings_columns', array(&$columns_to_load));
889
890
	$request = $smcFunc['db_query']('', '
891
		SELECT {raw:columns}
892
		FROM {db_prefix}members
893
		WHERE id_member IN ({array_int:user_ids})',
894
		array(
895
			'user_ids' => array_map('intval', array_unique($user_ids)),
896
			'columns' => implode(', ', $columns_to_load)
897
		)
898
	);
899
900
	while ($row = $smcFunc['db_fetch_assoc']($request))
901
	{
902
		$user_info_min[$row['id_member']] = array(
903
			'id' => $row['id_member'],
904
			'username' => $row['member_name'],
905
			'name' => isset($row['real_name']) ? $row['real_name'] : '',
906
			'language' => (empty($row['lngfile']) || empty($modSettings['userLanguage'])) ? $language : $row['lngfile'],
907
			'is_guest' => false,
908
			'time_format' => empty($row['time_format']) ? $modSettings['time_format'] : $row['time_format'],
909
			'smiley_set' => empty($row['smiley_set']) ? $modSettings['smiley_sets_default'] : $row['smiley_set'],
910
		);
911
912
		if (empty($row['additional_groups']))
913
			$user_info_min[$row['id_member']]['groups'] = array($row['id_group'], $row['id_post_group']);
914
915
		else
916
			$user_info_min[$row['id_member']]['groups'] = array_merge(
917
				array($row['id_group'], $row['id_post_group']),
918
				explode(',', $row['additional_groups'])
919
			);
920
921
		$user_info_min[$row['id_member']]['is_admin'] = in_array(1, $user_info_min[$row['id_member']]['groups']);
922
923
		if (!empty($row['timezone']))
924
		{
925
			$tz_system = new \DateTimeZone(@date_default_timezone_get());
926
			$tz_user = new \DateTimeZone($row['timezone']);
927
			$time_system = new \DateTime('now', $tz_system);
928
			$time_user = new \DateTime('now', $tz_user);
929
			$user_info_min[$row['id_member']]['time_offset'] = ($tz_user->getOffset($time_user) -
930
					$tz_system->getOffset($time_system)) / 3600;
931
		}
932
933
		else
934
			$user_info_min[$row['id_member']]['time_offset'] = empty($row['time_offset']) ? 0 : $row['time_offset'];
935
	}
936
937
	$smcFunc['db_free_result']($request);
938
939
	call_integration_hook('integrate_load_min_user_settings', array(&$user_info_min));
940
941
	return $user_info_min;
942
}
943
944
/**
945
 * Check for moderators and see if they have access to the board.
946
 * What it does:
947
 * - sets up the $board_info array for current board information.
948
 * - if cache is enabled, the $board_info array is stored in cache.
949
 * - redirects to appropriate post if only message id is requested.
950
 * - is only used when inside a topic or board.
951
 * - determines the local moderators for the board.
952
 * - adds group id 3 if the user is a local moderator for the board they are in.
953
 * - prevents access if user is not in proper group nor a local moderator of the board.
954
 */
955
function loadBoard()
956
{
957
	global $txt, $scripturl, $context, $modSettings;
958
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
959
960
	// Assume they are not a moderator.
961
	$user_info['is_mod'] = false;
962
	$context['user']['is_mod'] = &$user_info['is_mod'];
963
964
	// Start the linktree off empty..
965
	$context['linktree'] = array();
966
967
	// Have they by chance specified a message id but nothing else?
968
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
969
	{
970
		// Make sure the message id is really an int.
971
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
972
973
		// Looking through the message table can be slow, so try using the cache first.
974
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
975
		{
976
			$request = $smcFunc['db_query']('', '
977
				SELECT id_topic
978
				FROM {db_prefix}messages
979
				WHERE id_msg = {int:id_msg}
980
				LIMIT 1',
981
				array(
982
					'id_msg' => $_REQUEST['msg'],
983
				)
984
			);
985
986
			// So did it find anything?
987
			if ($smcFunc['db_num_rows']($request))
988
			{
989
				list ($topic) = $smcFunc['db_fetch_row']($request);
990
				$smcFunc['db_free_result']($request);
991
				// Save save save.
992
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
993
			}
994
		}
995
996
		// Remember redirection is the key to avoiding fallout from your bosses.
997
		if (!empty($topic))
998
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
999
		else
1000
		{
1001
			loadPermissions();
1002
			loadTheme();
1003
			fatal_lang_error('topic_gone', false);
1004
		}
1005
	}
1006
1007
	// Load this board only if it is specified.
1008
	if (empty($board) && empty($topic))
1009
	{
1010
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
1011
		return;
1012
	}
1013
1014
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1015
	{
1016
		// @todo SLOW?
1017
		if (!empty($topic))
1018
			$temp = cache_get_data('topic_board-' . $topic, 120);
1019
		else
1020
			$temp = cache_get_data('board-' . $board, 120);
1021
1022
		if (!empty($temp))
1023
		{
1024
			$board_info = $temp;
1025
			$board = $board_info['id'];
1026
		}
1027
	}
1028
1029
	if (empty($temp))
1030
	{
1031
		$custom_column_selects = [];
1032
		$custom_column_parameters = [
1033
			'current_topic' => $topic,
1034
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
1035
		];
1036
1037
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
1038
1039
		$request = $smcFunc['db_query']('load_board_info', '
1040
			SELECT
1041
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
1042
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
1043
				COALESCE(mem.id_member, 0) AS id_moderator,
1044
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
1045
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
1046
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
1047
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
1048
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
1049
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
1050
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1051
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
1052
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
1053
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
1054
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1055
			WHERE b.id_board = {raw:board_link}',
1056
			$custom_column_parameters
1057
		);
1058
1059
		// If there aren't any, skip.
1060
		if ($smcFunc['db_num_rows']($request) > 0)
1061
		{
1062
			$row = $smcFunc['db_fetch_assoc']($request);
1063
1064
			// Set the current board.
1065
			if (!empty($row['id_board']))
1066
				$board = $row['id_board'];
1067
1068
			// Basic operating information. (globals... :/)
1069
			$board_info = array(
1070
				'id' => $board,
1071
				'moderators' => array(),
1072
				'moderator_groups' => array(),
1073
				'cat' => array(
1074
					'id' => $row['id_cat'],
1075
					'name' => $row['cname']
1076
				),
1077
				'name' => $row['bname'],
1078
				'description' => $row['description'],
1079
				'num_topics' => $row['num_topics'],
1080
				'unapproved_topics' => $row['unapproved_topics'],
1081
				'unapproved_posts' => $row['unapproved_posts'],
1082
				'unapproved_user_topics' => 0,
1083
				'parent_boards' => getBoardParents($row['id_parent']),
1084
				'parent' => $row['id_parent'],
1085
				'child_level' => $row['child_level'],
1086
				'theme' => $row['id_theme'],
1087
				'override_theme' => !empty($row['override_theme']),
1088
				'profile' => $row['id_profile'],
1089
				'redirect' => $row['redirect'],
1090
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
1091
				'posts_count' => empty($row['count_posts']),
1092
				'cur_topic_approved' => empty($topic) || $row['approved'],
1093
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1094
			);
1095
1096
			// Load the membergroups allowed, and check permissions.
1097
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1098
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1099
1100
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1101
1102
			if (!empty($modSettings['board_manager_groups']))
1103
			{
1104
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1105
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1106
			}
1107
1108
			do
1109
			{
1110
				if (!empty($row['id_moderator']))
1111
					$board_info['moderators'][$row['id_moderator']] = array(
1112
						'id' => $row['id_moderator'],
1113
						'name' => $row['real_name'],
1114
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1115
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1116
					);
1117
1118
				if (!empty($row['id_moderator_group']))
1119
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1120
						'id' => $row['id_moderator_group'],
1121
						'name' => $row['group_name'],
1122
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1123
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1124
					);
1125
			}
1126
			while ($row = $smcFunc['db_fetch_assoc']($request));
1127
1128
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1129
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1130
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1131
			{
1132
				// Free the previous result
1133
				$smcFunc['db_free_result']($request);
1134
1135
				// @todo why is this using id_topic?
1136
				// @todo Can this get cached?
1137
				$request = $smcFunc['db_query']('', '
1138
					SELECT COUNT(id_topic)
1139
					FROM {db_prefix}topics
1140
					WHERE id_member_started={int:id_member}
1141
						AND approved = {int:unapproved}
1142
						AND id_board = {int:board}',
1143
					array(
1144
						'id_member' => $user_info['id'],
1145
						'unapproved' => 0,
1146
						'board' => $board,
1147
					)
1148
				);
1149
1150
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1151
			}
1152
1153
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1154
			{
1155
				// @todo SLOW?
1156
				if (!empty($topic))
1157
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1158
				cache_put_data('board-' . $board, $board_info, 120);
1159
			}
1160
		}
1161
		else
1162
		{
1163
			// Otherwise the topic is invalid, there are no moderators, etc.
1164
			$board_info = array(
1165
				'moderators' => array(),
1166
				'moderator_groups' => array(),
1167
				'error' => 'exist'
1168
			);
1169
			$topic = null;
1170
			$board = 0;
1171
		}
1172
		$smcFunc['db_free_result']($request);
1173
	}
1174
1175
	if (!empty($topic))
1176
		$_GET['board'] = (int) $board;
1177
1178
	if (!empty($board))
1179
	{
1180
		// Get this into an array of keys for array_intersect
1181
		$moderator_groups = array_keys($board_info['moderator_groups']);
1182
1183
		// Now check if the user is a moderator.
1184
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1185
1186
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1187
			$board_info['error'] = 'access';
1188
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1189
			$board_info['error'] = 'access';
1190
1191
		// Build up the linktree.
1192
		$context['linktree'] = array_merge(
1193
			$context['linktree'],
1194
			array(array(
1195
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1196
				'name' => $board_info['cat']['name']
1197
			)),
1198
			array_reverse($board_info['parent_boards']),
1199
			array(array(
1200
				'url' => $scripturl . '?board=' . $board . '.0',
1201
				'name' => $board_info['name']
1202
			))
1203
		);
1204
	}
1205
1206
	// Set the template contextual information.
1207
	$context['user']['is_mod'] = &$user_info['is_mod'];
1208
	$context['current_topic'] = $topic;
1209
	$context['current_board'] = $board;
1210
1211
	// No posting in redirection boards!
1212
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1213
		$board_info['error'] = 'post_in_redirect';
1214
1215
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1216
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1217
	{
1218
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1219
		loadPermissions();
1220
		loadTheme();
1221
1222
		$_GET['board'] = '';
1223
		$_GET['topic'] = '';
1224
1225
		// The linktree should not give the game away mate!
1226
		$context['linktree'] = array(
1227
			array(
1228
				'url' => $scripturl,
1229
				'name' => $context['forum_name_html_safe']
1230
			)
1231
		);
1232
1233
		// If it's a prefetching agent or we're requesting an attachment.
1234
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1235
		{
1236
			ob_end_clean();
1237
			send_http_status(403);
1238
			die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1239
		}
1240
		elseif ($board_info['error'] == 'post_in_redirect')
1241
		{
1242
			// Slightly different error message here...
1243
			fatal_lang_error('cannot_post_redirect', false);
1244
		}
1245
		elseif ($user_info['is_guest'])
1246
		{
1247
			loadLanguage('Errors');
1248
			is_not_guest($txt['topic_gone']);
1249
		}
1250
		else
1251
			fatal_lang_error('topic_gone', false);
1252
	}
1253
1254
	if ($user_info['is_mod'])
1255
		$user_info['groups'][] = 3;
1256
}
1257
1258
/**
1259
 * Load this user's permissions.
1260
 */
1261
function loadPermissions()
1262
{
1263
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1264
1265
	if ($user_info['is_admin'])
1266
	{
1267
		banPermissions();
1268
		return;
1269
	}
1270
1271
	if (!empty($cache_enable))
1272
	{
1273
		$cache_groups = $user_info['groups'];
1274
		asort($cache_groups);
1275
		$cache_groups = implode(',', $cache_groups);
1276
		// If it's a spider then cache it different.
1277
		if ($user_info['possibly_robot'])
1278
			$cache_groups .= '-spider';
1279
1280
		if ($cache_enable >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...ps . ':' . $board, 240) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1281
		{
1282
			list ($user_info['permissions']) = $temp;
1283
			banPermissions();
1284
1285
			return;
1286
		}
1287
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...' . $cache_groups, 240) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1288
			list ($user_info['permissions'], $removals) = $temp;
1289
	}
1290
1291
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1292
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1293
1294
	if (empty($user_info['permissions']))
1295
	{
1296
		// Get the general permissions.
1297
		$request = $smcFunc['db_query']('', '
1298
			SELECT permission, add_deny
1299
			FROM {db_prefix}permissions
1300
			WHERE id_group IN ({array_int:member_groups})
1301
				' . $spider_restrict,
1302
			array(
1303
				'member_groups' => $user_info['groups'],
1304
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1305
			)
1306
		);
1307
		$removals = array();
1308
		while ($row = $smcFunc['db_fetch_assoc']($request))
1309
		{
1310
			if (empty($row['add_deny']))
1311
				$removals[] = $row['permission'];
1312
			else
1313
				$user_info['permissions'][] = $row['permission'];
1314
		}
1315
		$smcFunc['db_free_result']($request);
1316
1317
		if (isset($cache_groups))
1318
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1319
	}
1320
1321
	// Get the board permissions.
1322
	if (!empty($board))
1323
	{
1324
		// Make sure the board (if any) has been loaded by loadBoard().
1325
		if (!isset($board_info['profile']))
1326
			fatal_lang_error('no_board');
1327
1328
		$request = $smcFunc['db_query']('', '
1329
			SELECT permission, add_deny
1330
			FROM {db_prefix}board_permissions
1331
			WHERE (id_group IN ({array_int:member_groups})
1332
				' . $spider_restrict . ')
1333
				AND id_profile = {int:id_profile}',
1334
			array(
1335
				'member_groups' => $user_info['groups'],
1336
				'id_profile' => $board_info['profile'],
1337
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1338
			)
1339
		);
1340
		while ($row = $smcFunc['db_fetch_assoc']($request))
1341
		{
1342
			if (empty($row['add_deny']))
1343
				$removals[] = $row['permission'];
1344
			else
1345
				$user_info['permissions'][] = $row['permission'];
1346
		}
1347
		$smcFunc['db_free_result']($request);
1348
	}
1349
1350
	// Remove all the permissions they shouldn't have ;).
1351
	if (!empty($modSettings['permission_enable_deny']))
1352
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $removals does not seem to be defined for all execution paths leading up to this point.
Loading history...
1353
1354
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1355
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1356
1357
	// Banned?  Watch, don't touch..
1358
	banPermissions();
1359
1360
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1361
	if (!$user_info['is_guest'])
1362
	{
1363
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1364
		{
1365
			require_once($sourcedir . '/Subs-Auth.php');
1366
			rebuildModCache();
1367
		}
1368
		else
1369
			$user_info['mod_cache'] = $_SESSION['mc'];
1370
1371
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1372
		// For example this drastically simplifies certain changes to the profile area.
1373
		$user_info['permissions'][] = 'is_not_guest';
1374
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1375
		$user_info['permissions'][] = 'profile_view_own';
1376
		if (in_array('profile_view', $user_info['permissions']))
1377
			$user_info['permissions'][] = 'profile_view_any';
1378
	}
1379
}
1380
1381
/**
1382
 * Loads an array of users' data by ID or member_name.
1383
 *
1384
 * @param array|string $users An array of users by id or name or a single username/id
1385
 * @param bool $is_name Whether $users contains names
1386
 * @param string $set What kind of data to load (normal, profile, minimal)
1387
 * @return array The ids of the members loaded
1388
 */
1389
function loadMemberData($users, $is_name = false, $set = 'normal')
1390
{
1391
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1392
	global $user_info, $cache_enable;
1393
1394
	// Can't just look for no users :P.
1395
	if (empty($users))
1396
		return array();
1397
1398
	// Pass the set value
1399
	$context['loadMemberContext_set'] = $set;
1400
1401
	// Make sure it's an array.
1402
	$users = !is_array($users) ? array($users) : array_unique($users);
1403
	$loaded_ids = array();
1404
1405
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1406
	{
1407
		$users = array_values($users);
1408
		for ($i = 0, $n = count($users); $i < $n; $i++)
1409
		{
1410
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1411
			if ($data == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $data of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1412
				continue;
1413
1414
			$loaded_ids[] = $data['id_member'];
1415
			$user_profile[$data['id_member']] = $data;
1416
			unset($users[$i]);
1417
		}
1418
	}
1419
1420
	// Used by default
1421
	$select_columns = '
1422
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1423
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1424
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1425
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.id_post_group, mem.lngfile, mem.id_group, mem.time_offset, mem.timezone, mem.show_online,
1426
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1427
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1428
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1429
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1430
	$select_tables = '
1431
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1432
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1433
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1434
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1435
1436
	// We add or replace according the the set
1437
	switch ($set)
1438
	{
1439
		case 'normal':
1440
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1441
			break;
1442
		case 'profile':
1443
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1444
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1445
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1446
			break;
1447
		case 'minimal':
1448
			$select_columns = '
1449
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1450
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1451
			$select_tables = '';
1452
			break;
1453
		default:
1454
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1455
	}
1456
1457
	// Allow mods to easily add to the selected member data
1458
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1459
1460
	if (!empty($users))
1461
	{
1462
		// Load the member's data.
1463
		$request = $smcFunc['db_query']('', '
1464
			SELECT' . $select_columns . '
1465
			FROM {db_prefix}members AS mem' . $select_tables . '
1466
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1467
			array(
1468
				'blank_string' => '',
1469
				'users' => $users,
1470
			)
1471
		);
1472
		$new_loaded_ids = array();
1473
		while ($row = $smcFunc['db_fetch_assoc']($request))
1474
		{
1475
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1476
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1477
1478
			// Take care of proxying avatar if required, do this here for maximum reach
1479
			if (!empty($row['avatar']))
1480
				$row['avatar'] = get_proxied_url($row['avatar']);
1481
1482
			// Keep track of the member's normal member group
1483
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1484
1485
			if (isset($row['member_ip']))
1486
				$row['member_ip'] = inet_dtop($row['member_ip']);
1487
			if (isset($row['member_ip2']))
1488
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1489
			$row['id_member'] = (int) $row['id_member'];
1490
			$new_loaded_ids[] = $row['id_member'];
1491
			$loaded_ids[] = $row['id_member'];
1492
			$row['options'] = array();
1493
			$user_profile[$row['id_member']] = $row;
1494
		}
1495
		$smcFunc['db_free_result']($request);
1496
	}
1497
1498
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1499
	{
1500
		$request = $smcFunc['db_query']('', '
1501
			SELECT id_member, variable, value
1502
			FROM {db_prefix}themes
1503
			WHERE id_member IN ({array_int:loaded_ids})',
1504
			array(
1505
				'loaded_ids' => $new_loaded_ids,
1506
			)
1507
		);
1508
		while ($row = $smcFunc['db_fetch_assoc']($request))
1509
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1510
		$smcFunc['db_free_result']($request);
1511
	}
1512
1513
	$additional_mods = array();
1514
1515
	// Are any of these users in groups assigned to moderate this board?
1516
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1517
	{
1518
		foreach ($loaded_ids as $a_member)
1519
		{
1520
			if (!empty($user_profile[$a_member]['additional_groups']))
1521
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1522
			else
1523
				$groups = array($user_profile[$a_member]['id_group']);
1524
1525
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1526
1527
			if (!empty($temp))
1528
			{
1529
				$additional_mods[] = $a_member;
1530
			}
1531
		}
1532
	}
1533
1534
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1535
	{
1536
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1537
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1538
	}
1539
1540
	// Are we loading any moderators?  If so, fix their group data...
1541
	if (!empty($loaded_ids) && (!empty($board_info['moderators']) || !empty($board_info['moderator_groups'])) && $set === 'normal' && count($temp_mods = array_merge(array_intersect($loaded_ids, array_keys($board_info['moderators'])), $additional_mods)) !== 0)
1542
	{
1543
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $row = cache_get_data('m...rator_group_info', 480) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1544
		{
1545
			$request = $smcFunc['db_query']('', '
1546
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1547
				FROM {db_prefix}membergroups
1548
				WHERE id_group = {int:moderator_group}
1549
				LIMIT 1',
1550
				array(
1551
					'moderator_group' => 3,
1552
				)
1553
			);
1554
			$row = $smcFunc['db_fetch_assoc']($request);
1555
			$smcFunc['db_free_result']($request);
1556
1557
			cache_put_data('moderator_group_info', $row, 480);
1558
		}
1559
1560
		foreach ($temp_mods as $id)
1561
		{
1562
			// By popular demand, don't show admins or global moderators as moderators.
1563
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1564
				$user_profile[$id]['member_group'] = $row['member_group'];
1565
1566
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1567
			if (!empty($row['icons']))
1568
				$user_profile[$id]['icons'] = $row['icons'];
1569
			if (!empty($row['member_group_color']))
1570
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1571
		}
1572
	}
1573
1574
	return $loaded_ids;
1575
}
1576
1577
/**
1578
 * Loads the user's basic values... meant for template/theme usage.
1579
 *
1580
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1581
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1582
 * @return boolean|array  False if the data wasn't loaded or the loaded data.
1583
 * @throws Exception
1584
 */
1585
function loadMemberContext($user, $display_custom_fields = false)
1586
{
1587
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1588
	global $context, $modSettings, $settings, $smcFunc;
1589
	static $already_loaded_custom_fields = array();
1590
	static $loadedLanguages = array();
1591
1592
	// If this person's data is already loaded, skip it.
1593
	if (!empty($memberContext[$user]) && !empty($already_loaded_custom_fields[$user]) >= $display_custom_fields)
1594
		return $memberContext[$user];
1595
1596
	// We can't load guests or members not loaded by loadMemberData()!
1597
	if ($user == 0)
1598
		return false;
1599
	if (!isset($user_profile[$user]))
1600
	{
1601
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1602
		return false;
1603
	}
1604
1605
	// Well, it's loaded now anyhow.
1606
	$profile = $user_profile[$user];
1607
1608
	// Censor everything.
1609
	censorText($profile['signature']);
1610
	censorText($profile['personal_text']);
1611
1612
	// Set things up to be used before hand.
1613
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1614
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1615
1616
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $profile['is_online'] = ...ofile['is_online'] > 0), Probably Intended Meaning: $profile['is_online'] = ...ofile['is_online'] > 0)
Loading history...
1617
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1618
	// Setup the buddy status here (One whole in_array call saved :P)
1619
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1620
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1621
1622
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1623
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1624
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1625
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1626
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1627
	else //not set, bye bye
1628
		$group_icon_url = '';
1629
1630
	// These minimal values are always loaded
1631
	$memberContext[$user] = array(
1632
		'username' => $profile['member_name'],
1633
		'name' => $profile['real_name'],
1634
		'id' => $profile['id_member'],
1635
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1636
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($modSettings['onlineEnable']) ? 'class="pm_icon"' : '') . '>' . $profile['real_name'] . '</a>',
1637
		'email' => $profile['email_address'],
1638
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1639
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1640
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1641
	);
1642
1643
	// If the set isn't minimal then load the monstrous array.
1644
	if ($context['loadMemberContext_set'] != 'minimal')
1645
	{
1646
		// Go the extra mile and load the user's native language name.
1647
		if (empty($loadedLanguages))
1648
			$loadedLanguages = getLanguages();
1649
1650
		// Figure out the new time offset.
1651
		if (!empty($profile['timezone']))
1652
		{
1653
			// Get the offsets from UTC for the server, then for the user.
1654
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1655
			$tz_user = new DateTimeZone($profile['timezone']);
1656
			$time_system = new DateTime('now', $tz_system);
1657
			$time_user = new DateTime('now', $tz_user);
1658
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1659
		}
1660
1661
		else
1662
		{
1663
			// !!! Compatibility.
1664
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1665
		}
1666
1667
		$memberContext[$user] += array(
1668
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1669
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1670
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</a>',
1671
			'is_buddy' => $profile['buddy'],
1672
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1673
			'buddies' => $buddy_list,
1674
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1675
			'blurb' => $profile['personal_text'],
1676
			'website' => array(
1677
				'title' => $profile['website_title'],
1678
				'url' => $profile['website_url'],
1679
			),
1680
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1681
			'signature' => $profile['signature'],
1682
			'real_posts' => $profile['posts'],
1683
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1684
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1685
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1686
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1687
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1688
			'online' => array(
1689
				'is_online' => $profile['is_online'],
1690
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1691
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1692
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1693
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1694
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1695
			),
1696
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1697
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1698
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1699
			'options' => $profile['options'],
1700
			'is_guest' => false,
1701
			'primary_group' => $profile['primary_group'],
1702
			'group' => $profile['member_group'],
1703
			'group_color' => $profile['member_group_color'],
1704
			'group_id' => $profile['id_group'],
1705
			'post_group' => $profile['post_group'],
1706
			'post_group_color' => $profile['post_group_color'],
1707
			'group_icons' => str_repeat('<img src="' . str_replace('$language', $context['user']['language'], isset($profile['icons'][1]) ? $group_icon_url : '') . '" alt="*">', empty($profile['icons'][0]) || empty($profile['icons'][1]) ? 0 : $profile['icons'][0]),
1708
			'warning' => $profile['warning'],
1709
			'warning_status' => !empty($modSettings['warning_mute']) && $modSettings['warning_mute'] <= $profile['warning'] ? 'mute' : (!empty($modSettings['warning_moderate']) && $modSettings['warning_moderate'] <= $profile['warning'] ? 'moderate' : (!empty($modSettings['warning_watch']) && $modSettings['warning_watch'] <= $profile['warning'] ? 'watch' : (''))),
1710
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1711
			'custom_fields' => array(),
1712
		);
1713
	}
1714
1715
	// If the set isn't minimal then load their avatar as well.
1716
	if ($context['loadMemberContext_set'] != 'minimal')
1717
	{
1718
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1719
		{
1720
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1721
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1722
			else
1723
				$image = get_gravatar_url($profile['email_address']);
1724
		}
1725
		else
1726
		{
1727
			// So it's stored in the member table?
1728
			if (!empty($profile['avatar']))
1729
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1730
1731
			elseif (!empty($profile['filename']))
1732
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1733
1734
			// Right... no avatar...use the default one
1735
			else
1736
				$image = $modSettings['avatar_url'] . '/default.png';
1737
		}
1738
1739
		if (!empty($image))
1740
			$memberContext[$user]['avatar'] = array(
1741
				'name' => $profile['avatar'],
1742
				'image' => '<img class="avatar" src="' . $image . '" alt="">',
1743
				'href' => $image,
1744
				'url' => $image,
1745
			);
1746
	}
1747
1748
	// Are we also loading the members custom fields into context?
1749
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1750
	{
1751
		$memberContext[$user]['custom_fields'] = array();
1752
1753
		if (!isset($context['display_fields']))
1754
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1755
1756
		foreach ($context['display_fields'] as $custom)
1757
		{
1758
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1759
				continue;
1760
1761
			$value = $profile['options'][$custom['col_name']];
1762
1763
			$fieldOptions = array();
1764
			$currentKey = 0;
1765
1766
			// Create a key => value array for multiple options fields
1767
			if (!empty($custom['options']))
1768
				foreach ($custom['options'] as $k => $v)
1769
				{
1770
					$fieldOptions[] = $v;
1771
					if (empty($currentKey))
1772
						$currentKey = $v == $value ? $k : 0;
1773
				}
1774
1775
			// BBC?
1776
			if ($custom['bbc'])
1777
				$value = parse_bbc($value);
1778
1779
			// ... or checkbox?
1780
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1781
				$value = $value ? $txt['yes'] : $txt['no'];
1782
1783
			// Enclosing the user input within some other text?
1784
			if (!empty($custom['enclose']))
1785
				$value = strtr($custom['enclose'], array(
1786
					'{SCRIPTURL}' => $scripturl,
1787
					'{IMAGES_URL}' => $settings['images_url'],
1788
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1789
					'{INPUT}' => $value,
1790
					'{KEY}' => $currentKey,
1791
				));
1792
1793
			$memberContext[$user]['custom_fields'][] = array(
1794
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1795
				'col_name' => $custom['col_name'],
1796
				'value' => un_htmlspecialchars($value),
1797
				'raw' => $profile['options'][$custom['col_name']],
1798
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1799
			);
1800
		}
1801
	}
1802
1803
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1804
1805
	$already_loaded_custom_fields[$user] = !empty($already_loaded_custom_fields[$user]) | $display_custom_fields;
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
1806
1807
	return $memberContext[$user];
1808
}
1809
1810
/**
1811
 * Loads the user's custom profile fields
1812
 *
1813
 * @param integer|array $users A single user ID or an array of user IDs
1814
 * @param string|array $params Either a string or an array of strings with profile field names
1815
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1816
 */
1817
function loadMemberCustomFields($users, $params)
1818
{
1819
	global $smcFunc, $txt, $scripturl, $settings;
1820
1821
	// Do not waste my time...
1822
	if (empty($users) || empty($params))
1823
		return false;
1824
1825
	// Make sure it's an array.
1826
	$users = !is_array($users) ? array($users) : array_unique($users);
1827
	$params = !is_array($params) ? array($params) : array_unique($params);
1828
	$return = array();
1829
1830
	$request = $smcFunc['db_query']('', '
1831
		SELECT c.id_field, c.col_name, c.field_name, c.field_desc, c.field_type, c.field_order, c.field_length, c.field_options, c.mask, show_reg,
1832
		c.show_display, c.show_profile, c.private, c.active, c.bbc, c.can_search, c.default_value, c.enclose, c.placement, t.variable, t.value, t.id_member
1833
		FROM {db_prefix}themes AS t
1834
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1835
		WHERE id_member IN ({array_int:loaded_ids})
1836
			AND variable IN ({array_string:params})
1837
		ORDER BY field_order',
1838
		array(
1839
			'loaded_ids' => $users,
1840
			'params' => $params,
1841
		)
1842
	);
1843
1844
	while ($row = $smcFunc['db_fetch_assoc']($request))
1845
	{
1846
		$fieldOptions = array();
1847
		$currentKey = 0;
1848
1849
		// Create a key => value array for multiple options fields
1850
		if (!empty($row['field_options']))
1851
			foreach (explode(',', $row['field_options']) as $k => $v)
1852
			{
1853
				$fieldOptions[] = $v;
1854
				if (empty($currentKey))
1855
					$currentKey = $v == $row['value'] ? $k : 0;
1856
			}
1857
1858
		// BBC?
1859
		if (!empty($row['bbc']))
1860
			$row['value'] = parse_bbc($row['value']);
1861
1862
		// ... or checkbox?
1863
		elseif (isset($row['type']) && $row['type'] == 'check')
1864
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1865
1866
		// Enclosing the user input within some other text?
1867
		if (!empty($row['enclose']))
1868
			$row['value'] = strtr($row['enclose'], array(
1869
				'{SCRIPTURL}' => $scripturl,
1870
				'{IMAGES_URL}' => $settings['images_url'],
1871
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1872
				'{INPUT}' => un_htmlspecialchars($row['value']),
1873
				'{KEY}' => $currentKey,
1874
			));
1875
1876
		// Send a simple array if there is just 1 param
1877
		if (count($params) == 1)
1878
			$return[$row['id_member']] = $row;
1879
1880
		// More than 1? knock yourself out...
1881
		else
1882
		{
1883
			if (!isset($return[$row['id_member']]))
1884
				$return[$row['id_member']] = array();
1885
1886
			$return[$row['id_member']][$row['variable']] = $row;
1887
		}
1888
	}
1889
1890
	$smcFunc['db_free_result']($request);
1891
1892
	return !empty($return) ? $return : false;
1893
}
1894
1895
/**
1896
 * Loads information about what browser the user is viewing with and places it in $context
1897
 *  - uses the class from {@link Class-BrowserDetect.php}
1898
 */
1899
function detectBrowser()
1900
{
1901
	// Load the current user's browser of choice
1902
	$detector = new browser_detector;
1903
	$detector->detectBrowser();
1904
}
1905
1906
/**
1907
 * Are we using this browser?
1908
 *
1909
 * Wrapper function for detectBrowser
1910
 *
1911
 * @param string $browser The browser we are checking for.
1912
 * @return bool Whether or not the current browser is what we're looking for
1913
 */
1914
function isBrowser($browser)
1915
{
1916
	global $context;
1917
1918
	// Don't know any browser!
1919
	if (empty($context['browser']))
1920
		detectBrowser();
1921
1922
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1923
}
1924
1925
/**
1926
 * Load a theme, by ID.
1927
 *
1928
 * @param int $id_theme The ID of the theme to load
1929
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1930
 */
1931
function loadTheme($id_theme = 0, $initialize = true)
1932
{
1933
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1934
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1935
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1936
1937
	if (empty($id_theme))
1938
	{
1939
		// The theme was specified by the board.
1940
		if (!empty($board_info['theme']))
1941
			$id_theme = $board_info['theme'];
1942
		// The theme is the forum's default.
1943
		else
1944
			$id_theme = $modSettings['theme_guests'];
1945
1946
		// Sometimes the user can choose their own theme.
1947
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1948
		{
1949
			// The theme was specified by REQUEST.
1950
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1951
			{
1952
				$id_theme = (int) $_REQUEST['theme'];
1953
				$_SESSION['id_theme'] = $id_theme;
1954
			}
1955
			// The theme was specified by REQUEST... previously.
1956
			elseif (!empty($_SESSION['id_theme']))
1957
				$id_theme = (int) $_SESSION['id_theme'];
1958
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1959
			elseif (!empty($user_info['theme']))
1960
				$id_theme = $user_info['theme'];
1961
		}
1962
1963
		// Verify the id_theme... no foul play.
1964
		// Always allow the board specific theme, if they are overriding.
1965
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1966
			$id_theme = $board_info['theme'];
1967
		elseif (!empty($modSettings['enableThemes']))
1968
		{
1969
			$themes = explode(',', $modSettings['enableThemes']);
1970
			if (!in_array($id_theme, $themes))
1971
				$id_theme = $modSettings['theme_guests'];
1972
			else
1973
				$id_theme = (int) $id_theme;
1974
		}
1975
	}
1976
1977
	// Allow mod authors the option to override the theme id for custom page themes
1978
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1979
1980
	// We already load the basic stuff?
1981
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1982
	{
1983
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1984
1985
		if (!empty($cache_enable) && $cache_enable >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...me . ':' . $member, 60) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1986
		{
1987
			$themeData = $temp;
1988
			$flag = true;
1989
		}
1990
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $temp = cache_get_data('...ings-' . $id_theme, 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1991
			$themeData = $temp + array($member => array());
1992
		else
1993
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1994
1995
		if (empty($flag))
1996
		{
1997
			// Load variables from the current or default theme, global or this user's.
1998
			$result = $smcFunc['db_query']('', '
1999
				SELECT variable, value, id_member, id_theme
2000
				FROM {db_prefix}themes
2001
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
2002
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
2003
				ORDER BY id_theme asc',
2004
				array(
2005
					'id_theme' => $id_theme,
2006
					'id_member' => $member,
2007
				)
2008
			);
2009
			// Pick between $settings and $options depending on whose data it is.
2010
			foreach ($smcFunc['db_fetch_all']($result) as $row)
2011
			{
2012
				// There are just things we shouldn't be able to change as members.
2013
				if ($row['id_member'] != 0 && in_array($row['variable'], array('actual_theme_url', 'actual_images_url', 'base_theme_dir', 'base_theme_url', 'default_images_url', 'default_theme_dir', 'default_theme_url', 'default_template', 'images_url', 'number_recent_posts', 'smiley_sets_default', 'theme_dir', 'theme_id', 'theme_layers', 'theme_templates', 'theme_url')))
2014
					continue;
2015
2016
				// If this is the theme_dir of the default theme, store it.
2017
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
2018
					$themeData[0]['default_' . $row['variable']] = $row['value'];
2019
2020
				// If this isn't set yet, is a theme option, or is not the default theme..
2021
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
2022
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
2023
			}
2024
			$smcFunc['db_free_result']($result);
2025
2026
			if (!empty($themeData[-1]))
2027
				foreach ($themeData[-1] as $k => $v)
2028
				{
2029
					if (!isset($themeData[$member][$k]))
2030
						$themeData[$member][$k] = $v;
2031
				}
2032
2033
			if (!empty($cache_enable) && $cache_enable >= 2)
2034
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
2035
			// Only if we didn't already load that part of the cache...
2036
			elseif (!isset($temp))
2037
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
2038
		}
2039
2040
		$settings = $themeData[0];
2041
		$options = $themeData[$member];
2042
2043
		$settings['theme_id'] = $id_theme;
2044
2045
		$settings['actual_theme_url'] = $settings['theme_url'];
2046
		$settings['actual_images_url'] = $settings['images_url'];
2047
		$settings['actual_theme_dir'] = $settings['theme_dir'];
2048
2049
		$settings['template_dirs'] = array();
2050
		// This theme first.
2051
		$settings['template_dirs'][] = $settings['theme_dir'];
2052
2053
		// Based on theme (if there is one).
2054
		if (!empty($settings['base_theme_dir']))
2055
			$settings['template_dirs'][] = $settings['base_theme_dir'];
2056
2057
		// Lastly the default theme.
2058
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
2059
			$settings['template_dirs'][] = $settings['default_theme_dir'];
2060
	}
2061
2062
	if (!$initialize)
2063
		return;
2064
2065
	// Check to see if we're forcing SSL
2066
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2067
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2068
	{
2069
		if (isset($_GET['sslRedirect']))
2070
		{
2071
			loadLanguage('Errors');
2072
			fatal_lang_error('login_ssl_required', false);
2073
		}
2074
2075
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2076
	}
2077
2078
	// Check to see if they're accessing it from the wrong place.
2079
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2080
	{
2081
		$detected_url = httpsOn() ? 'https://' : 'http://';
2082
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2083
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2084
		if ($temp != '/')
2085
			$detected_url .= $temp;
2086
	}
2087
	if (isset($detected_url) && $detected_url != $boardurl)
2088
	{
2089
		// Try #1 - check if it's in a list of alias addresses.
2090
		if (!empty($modSettings['forum_alias_urls']))
2091
		{
2092
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2093
2094
			foreach ($aliases as $alias)
2095
			{
2096
				// Rip off all the boring parts, spaces, etc.
2097
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2098
					$do_fix = true;
2099
			}
2100
		}
2101
2102
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2103
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2104
		{
2105
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2106
			if (empty($_GET))
2107
				redirectexit('wwwRedirect');
2108
			else
2109
			{
2110
				$k = key($_GET);
2111
				$v = current($_GET);
2112
2113
				if ($k != 'wwwRedirect')
2114
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2115
			}
2116
		}
2117
2118
		// #3 is just a check for SSL...
2119
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2120
			$do_fix = true;
2121
2122
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2123
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $detected_url does not seem to be defined for all execution paths leading up to this point.
Loading history...
2124
		{
2125
			// Caching is good ;).
2126
			$oldurl = $boardurl;
2127
2128
			// Fix $boardurl and $scripturl.
2129
			$boardurl = $detected_url;
2130
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2131
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2132
2133
			// Fix the theme urls...
2134
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2135
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2136
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2137
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2138
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2139
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2140
2141
			// And just a few mod settings :).
2142
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2143
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2144
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2145
2146
			// Clean up after loadBoard().
2147
			if (isset($board_info['moderators']))
2148
			{
2149
				foreach ($board_info['moderators'] as $k => $dummy)
2150
				{
2151
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2152
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2153
				}
2154
			}
2155
			foreach ($context['linktree'] as $k => $dummy)
2156
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2157
		}
2158
	}
2159
	// Set up the contextual user array.
2160
	if (!empty($user_info))
2161
	{
2162
		$context['user'] = array(
2163
			'id' => $user_info['id'],
2164
			'is_logged' => !$user_info['is_guest'],
2165
			'is_guest' => &$user_info['is_guest'],
2166
			'is_admin' => &$user_info['is_admin'],
2167
			'is_mod' => &$user_info['is_mod'],
2168
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2169
			'can_mod' => allowedTo('access_mod_center') || (!$user_info['is_guest'] && ($user_info['mod_cache']['gq'] != '0=1' || $user_info['mod_cache']['bq'] != '0=1' || ($modSettings['postmod_active'] && !empty($user_info['mod_cache']['ap'])))),
2170
			'name' => $user_info['username'],
2171
			'language' => $user_info['language'],
2172
			'email' => $user_info['email'],
2173
			'ignoreusers' => $user_info['ignoreusers'],
2174
		);
2175
		if (!$context['user']['is_guest'])
2176
			$context['user']['name'] = $user_info['name'];
2177
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2178
			$context['user']['name'] = $txt['guest_title'];
2179
2180
		// Determine the current smiley set.
2181
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2182
		$user_info['smiley_set'] = (!in_array($user_info['smiley_set'], $smiley_sets_known) && $user_info['smiley_set'] != 'none') || empty($modSettings['smiley_sets_enable']) ? (!empty($settings['smiley_sets_default']) ? $settings['smiley_sets_default'] : $modSettings['smiley_sets_default']) : $user_info['smiley_set'];
2183
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2184
	}
2185
	else
2186
	{
2187
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2188
		$context['user'] = array(
2189
			'id' => -1,
2190
			'is_logged' => false,
2191
			'is_guest' => true,
2192
			'is_mod' => false,
2193
			'can_mod' => false,
2194
			'name' => $txt['guest_title'],
2195
			'language' => $language,
2196
			'email' => '',
2197
			'ignoreusers' => array(),
2198
		);
2199
		// Note we should stuff $user_info with some guest values also...
2200
		$user_info = array(
2201
			'id' => 0,
2202
			'is_guest' => true,
2203
			'is_admin' => false,
2204
			'is_mod' => false,
2205
			'username' => $txt['guest_title'],
2206
			'language' => $language,
2207
			'email' => '',
2208
			'smiley_set' => '',
2209
			'permissions' => array(),
2210
			'groups' => array(),
2211
			'ignoreusers' => array(),
2212
			'possibly_robot' => true,
2213
			'time_offset' => 0,
2214
			'time_format' => $modSettings['time_format'],
2215
		);
2216
	}
2217
2218
	// Some basic information...
2219
	if (!isset($context['html_headers']))
2220
		$context['html_headers'] = '';
2221
	if (!isset($context['javascript_files']))
2222
		$context['javascript_files'] = array();
2223
	if (!isset($context['css_files']))
2224
		$context['css_files'] = array();
2225
	if (!isset($context['css_header']))
2226
		$context['css_header'] = array();
2227
	if (!isset($context['javascript_inline']))
2228
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2229
	if (!isset($context['javascript_vars']))
2230
		$context['javascript_vars'] = array();
2231
2232
	$context['login_url'] = $scripturl . '?action=login2';
2233
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2234
	$context['session_var'] = $_SESSION['session_var'];
2235
	$context['session_id'] = $_SESSION['session_value'];
2236
	$context['forum_name'] = $mbname;
2237
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2238
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2239
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2240
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2241
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2242
	if (isset($modSettings['load_average']))
2243
		$context['load_average'] = $modSettings['load_average'];
2244
2245
	// Detect the browser. This is separated out because it's also used in attachment downloads
2246
	detectBrowser();
2247
2248
	// Set the top level linktree up.
2249
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2250
	if (empty($context['linktree']))
2251
		$context['linktree'] = array();
2252
	array_unshift($context['linktree'], array(
2253
		'url' => $scripturl,
2254
		'name' => $context['forum_name_html_safe']
2255
	));
2256
2257
	// This allows sticking some HTML on the page output - useful for controls.
2258
	$context['insert_after_template'] = '';
2259
2260
	if (!isset($txt))
2261
		$txt = array();
2262
2263
	$simpleActions = array(
2264
		'findmember',
2265
		'helpadmin',
2266
		'printpage',
2267
		'spellcheck',
2268
	);
2269
2270
	// Parent action => array of areas
2271
	$simpleAreas = array(
2272
		'profile' => array('popup', 'alerts_popup',),
2273
	);
2274
2275
	// Parent action => array of subactions
2276
	$simpleSubActions = array(
2277
		'pm' => array('popup',),
2278
		'signup' => array('usernamecheck'),
2279
	);
2280
2281
	// Extra params like ;preview ;js, etc.
2282
	$extraParams = array(
2283
		'preview',
2284
		'splitjs',
2285
	);
2286
2287
	// Actions that specifically uses XML output.
2288
	$xmlActions = array(
2289
		'quotefast',
2290
		'jsmodify',
2291
		'xmlhttp',
2292
		'post2',
2293
		'suggest',
2294
		'stats',
2295
		'notifytopic',
2296
		'notifyboard',
2297
	);
2298
2299
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2300
2301
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2302
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2303
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2304
2305
	// See if theres any extra param to check.
2306
	$requiresXML = false;
2307
	foreach ($extraParams as $key => $extra)
2308
		if (isset($_REQUEST[$extra]))
2309
			$requiresXML = true;
2310
2311
	// Output is fully XML, so no need for the index template.
2312
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2313
	{
2314
		loadLanguage('index+Modifications');
2315
		loadTemplate('Xml');
2316
		$context['template_layers'] = array();
2317
	}
2318
2319
	// These actions don't require the index template at all.
2320
	elseif (!empty($context['simple_action']))
2321
	{
2322
		loadLanguage('index+Modifications');
2323
		$context['template_layers'] = array();
2324
	}
2325
2326
	else
2327
	{
2328
		// Custom templates to load, or just default?
2329
		if (isset($settings['theme_templates']))
2330
			$templates = explode(',', $settings['theme_templates']);
2331
		else
2332
			$templates = array('index');
2333
2334
		// Load each template...
2335
		foreach ($templates as $template)
2336
			loadTemplate($template);
2337
2338
		// ...and attempt to load their associated language files.
2339
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2340
		loadLanguage($required_files, '', false);
2341
2342
		// Custom template layers?
2343
		if (isset($settings['theme_layers']))
2344
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2345
		else
2346
			$context['template_layers'] = array('html', 'body');
2347
	}
2348
2349
	// Initialize the theme.
2350
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Bug introduced by
'ignore' of type string is incompatible with the type boolean expected by parameter $fatal of loadSubTemplate(). ( Ignorable by Annotation )

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

2350
	loadSubTemplate('init', /** @scrutinizer ignore-type */ 'ignore');
Loading history...
2351
2352
	// Allow overriding the board wide time/number formats.
2353
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2354
		$user_info['time_format'] = $txt['time_format'];
2355
2356
	// Set the character set from the template.
2357
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2358
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2359
2360
	// Guests may still need a name.
2361
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2362
		$context['user']['name'] = $txt['guest_title'];
2363
2364
	// Any theme-related strings that need to be loaded?
2365
	if (!empty($settings['require_theme_strings']))
2366
		loadLanguage('ThemeStrings', '', false);
2367
2368
	// Make a special URL for the language.
2369
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2370
2371
	// And of course, let's load the default CSS file.
2372
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2373
2374
	// Here is my luvly Responsive CSS
2375
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2376
2377
	if ($context['right_to_left'])
2378
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2379
2380
	// We allow theme variants, because we're cool.
2381
	$context['theme_variant'] = '';
2382
	$context['theme_variant_url'] = '';
2383
	if (!empty($settings['theme_variants']))
2384
	{
2385
		// Overriding - for previews and that ilk.
2386
		if (!empty($_REQUEST['variant']))
2387
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2388
		// User selection?
2389
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2390
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2391
		// If not a user variant, select the default.
2392
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2393
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2394
2395
		// Do this to keep things easier in the templates.
2396
		$context['theme_variant'] = '_' . $context['theme_variant'];
2397
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2398
2399
		if (!empty($context['theme_variant']))
2400
		{
2401
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2402
			if ($context['right_to_left'])
2403
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2404
		}
2405
	}
2406
2407
	// Let's be compatible with old themes!
2408
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2409
		$context['template_layers'] = array('main');
2410
2411
	$context['tabindex'] = 1;
2412
2413
	// Compatibility.
2414
	if (!isset($settings['theme_version']))
2415
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2416
2417
	// Default JS variables for use in every theme
2418
	$context['javascript_vars'] = array(
2419
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2420
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2421
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2422
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2423
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2424
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2425
		'smf_scripturl' => '"' . $scripturl . '"',
2426
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2427
		'smf_charset' => '"' . $context['character_set'] . '"',
2428
		'smf_session_id' => '"' . $context['session_id'] . '"',
2429
		'smf_session_var' => '"' . $context['session_var'] . '"',
2430
		'smf_member_id' => $context['user']['id'],
2431
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2432
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2433
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2434
		'smf_txt_expand' => JavaScriptEscape($txt['code_expand']),
2435
		'smf_txt_shrink' => JavaScriptEscape($txt['code_shrink']),
2436
		'smf_quote_expand' => !empty($modSettings['quote_expand']) ? $modSettings['quote_expand'] : 'false',
2437
	);
2438
2439
	// Add the JQuery library to the list of files to load.
2440
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2441
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2442
2443
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2444
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2445
2446
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2447
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2448
2449
	// Auto loading? template_javascript() will take care of the local half of this.
2450
	else
2451
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2452
2453
	// Queue our JQuery plugins!
2454
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2455
	if (!$user_info['is_guest'])
2456
	{
2457
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2458
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2459
	}
2460
2461
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2462
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2463
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2464
2465
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2466
	if ((!empty($modSettings['mail_next_send']) && $modSettings['mail_next_send'] < time() && empty($modSettings['mail_queue_use_cron'])) || empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2467
	{
2468
		if (isBrowser('possibly_robot'))
2469
		{
2470
			// @todo Maybe move this somewhere better?!
2471
			require_once($sourcedir . '/ScheduledTasks.php');
2472
2473
			// What to do, what to do?!
2474
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2475
				AutoTask();
2476
			else
2477
				ReduceMailQueue();
2478
		}
2479
		else
2480
		{
2481
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2482
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2483
2484
			addInlineJavaScript('
2485
		function smfAutoTask()
2486
		{
2487
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2488
		}
2489
		window.setTimeout("smfAutoTask();", 1);');
2490
		}
2491
	}
2492
2493
	// And we should probably trigger the cron too.
2494
	if (empty($modSettings['cron_is_real_cron']))
2495
	{
2496
		$ts = time();
2497
		$ts -= $ts % 15;
2498
		addInlineJavaScript('
2499
	function triggerCron()
2500
	{
2501
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2502
	}
2503
	window.setTimeout(triggerCron, 1);', true);
2504
	}
2505
2506
	// Filter out the restricted boards from the linktree
2507
	if (!$user_info['is_admin'] && !empty($board))
2508
	{
2509
		foreach ($context['linktree'] as $k => $element)
2510
		{
2511
			if (!empty($element['groups']) &&
2512
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2513
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2514
			{
2515
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2516
				$context['linktree'][$k]['extra_before'] = '<i>';
2517
				$context['linktree'][$k]['extra_after'] = '</i>';
2518
				unset($context['linktree'][$k]['url']);
2519
			}
2520
		}
2521
	}
2522
2523
	// Any files to include at this point?
2524
	if (!empty($modSettings['integrate_theme_include']))
2525
	{
2526
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2527
		foreach ($theme_includes as $include)
2528
		{
2529
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2530
			if (file_exists($include))
2531
				require_once($include);
2532
		}
2533
	}
2534
2535
	// Call load theme integration functions.
2536
	call_integration_hook('integrate_load_theme');
2537
2538
	// We are ready to go.
2539
	$context['theme_loaded'] = true;
2540
}
2541
2542
/**
2543
 * Load a template - if the theme doesn't include it, use the default.
2544
 * What this function does:
2545
 *  - loads a template file with the name template_name from the current, default, or base theme.
2546
 *  - detects a wrong default theme directory and tries to work around it.
2547
 *
2548
 * @uses template_include() to include the file.
2549
 * @param string $template_name The name of the template to load
2550
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2551
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2552
 * @return boolean Whether or not the template was loaded
2553
 */
2554
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2555
{
2556
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2557
2558
	// Do any style sheets first, cause we're easy with those.
2559
	if (!empty($style_sheets))
2560
	{
2561
		if (!is_array($style_sheets))
2562
			$style_sheets = array($style_sheets);
2563
2564
		foreach ($style_sheets as $sheet)
2565
			loadCSSFile($sheet . '.css', array(), $sheet);
2566
	}
2567
2568
	// No template to load?
2569
	if ($template_name === false)
0 ignored issues
show
introduced by
The condition $template_name === false is always false.
Loading history...
2570
		return true;
2571
2572
	$loaded = false;
2573
	foreach ($settings['template_dirs'] as $template_dir)
2574
	{
2575
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2576
		{
2577
			$loaded = true;
2578
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2579
			break;
2580
		}
2581
	}
2582
2583
	if ($loaded)
0 ignored issues
show
introduced by
The condition $loaded is always false.
Loading history...
2584
	{
2585
		if ($db_show_debug === true)
2586
			$context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $template_dir seems to be defined by a foreach iteration on line 2573. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2587
2588
		// If they have specified an initialization function for this template, go ahead and call it now.
2589
		if (function_exists('template_' . $template_name . '_init'))
2590
			call_user_func('template_' . $template_name . '_init');
2591
	}
2592
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2593
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2594
	{
2595
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2596
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2597
2598
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2599
		{
2600
			loadLanguage('Errors');
2601
			echo '
2602
<div class="alert errorbox">
2603
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2604
</div>';
2605
		}
2606
2607
		loadTemplate($template_name);
2608
	}
2609
	// Cause an error otherwise.
2610
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2611
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2612
	elseif ($fatal)
2613
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load Themes/default/%s.template.php!', (string) $template_name), 'template'));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2614
	else
2615
		return false;
2616
}
2617
2618
/**
2619
 * Load a sub-template.
2620
 * What it does:
2621
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2622
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2623
 *	for debugging purposes.
2624
 *
2625
 * @todo get rid of reading $_REQUEST directly
2626
 *
2627
 * @param string $sub_template_name The name of the sub-template to load
2628
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2629
 */
2630
function loadSubTemplate($sub_template_name, $fatal = false)
2631
{
2632
	global $context, $txt, $db_show_debug;
2633
2634
	if ($db_show_debug === true)
2635
		$context['debug']['sub_templates'][] = $sub_template_name;
2636
2637
	// Figure out what the template function is named.
2638
	$theme_function = 'template_' . $sub_template_name;
2639
	if (function_exists($theme_function))
2640
		$theme_function();
2641
	elseif ($fatal === false)
2642
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2643
	elseif ($fatal !== 'ignore')
0 ignored issues
show
introduced by
The condition $fatal !== 'ignore' is always true.
Loading history...
2644
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template'));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2645
2646
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2647
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2648
	{
2649
		echo '
2650
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2651
	}
2652
}
2653
2654
/**
2655
 * Add a CSS file for output later
2656
 *
2657
 * @param string $fileName The name of the file to load
2658
 * @param array $params An array of parameters
2659
 * Keys are the following:
2660
 * 	- ['external'] (true/false): define if the file is a externally located file. Needs to be set to true if you are loading an external file
2661
 * 	- ['default_theme'] (true/false): force use of default theme url
2662
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the default theme if not found in the current theme
2663
 *  - ['validate'] (true/false): if true script will validate the local file exists
2664
 *  - ['rtl'] (string): additional file to load in RTL mode
2665
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2666
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2667
 *  - ['order_pos'] int define the load order, when not define it's loaded in the middle, before index.css = -500, after index.css = 500, middle = 3000, end (i.e. after responsive.css) = 10000
2668
 *  - ['attributes'] array extra attributes to add to the element
2669
 * @param string $id An ID to stick on the end of the filename for caching purposes
2670
 */
2671
function loadCSSFile($fileName, $params = array(), $id = '')
2672
{
2673
	global $settings, $context, $modSettings;
2674
2675
	if (empty($context['css_files_order']))
2676
		$context['css_files_order'] = array();
2677
2678
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') : (is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2679
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2680
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2681
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2682
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2683
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2684
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2685
2686
	// If this is an external file, automatically set this to false.
2687
	if (!empty($params['external']))
2688
		$params['minimize'] = false;
2689
2690
	// Account for shorthand like admin.css?alp21 filenames
2691
	$id = empty($id) ? strtr(str_replace('.css', '', basename($fileName)), '?', '_') : $id;
2692
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2693
2694
	// Is this a local file?
2695
	if (empty($params['external']))
2696
	{
2697
		// Are we validating the the file exists?
2698
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/css/' . $fileName)) === false)
2699
		{
2700
			// Maybe the default theme has it?
2701
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/css/' . $fileName) !== false))
2702
			{
2703
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName;
2704
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName;
2705
			}
2706
2707
			else
2708
			{
2709
				$fileUrl = false;
2710
				$filePath = false;
2711
			}
2712
		}
2713
2714
		else
2715
		{
2716
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName;
2717
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName;
2718
			$mtime = @filemtime($filePath);
2719
		}
2720
	}
2721
2722
	// An external file doesn't have a filepath. Mock one for simplicity.
2723
	else
2724
	{
2725
		$fileUrl = $fileName;
2726
		$filePath = $fileName;
2727
	}
2728
2729
	$mtime = empty($mtime) ? 0 : $mtime;
2730
2731
	// Add it to the array for use in the template
2732
	if (!empty($fileName))
2733
	{
2734
		// find a free number/position
2735
		while (isset($context['css_files_order'][$params['order_pos']]))
2736
			$params['order_pos']++;
2737
		$context['css_files_order'][$params['order_pos']] = $id;
2738
2739
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2740
	}
2741
2742
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2743
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2744
2745
	if ($mtime > $modSettings['browser_cache'])
2746
		updateSettings(array('browser_cache' => $mtime));
2747
}
2748
2749
/**
2750
 * Add a block of inline css code to be executed later
2751
 *
2752
 * - only use this if you have to, generally external css files are better, but for very small changes
2753
 *   or for scripts that require help from PHP/whatever, this can be useful.
2754
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2755
 *
2756
 * @param string $css Some css code
2757
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2758
 */
2759
function addInlineCss($css)
2760
{
2761
	global $context;
2762
2763
	// Gotta add something...
2764
	if (empty($css))
2765
		return false;
2766
2767
	$context['css_header'][] = $css;
2768
}
2769
2770
/**
2771
 * Add a Javascript file for output later
2772
 *
2773
 * @param string $fileName The name of the file to load
2774
 * @param array $params An array of parameter info
2775
 * Keys are the following:
2776
 * 	- ['external'] (true/false): define if the file is a externally located file. Needs to be set to true if you are loading an external file
2777
 * 	- ['default_theme'] (true/false): force use of default theme url
2778
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2779
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2780
 *	default theme if not found in the current theme
2781
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2782
 *  - ['validate'] (true/false): if true script will validate the local file exists
2783
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2784
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2785
 *  - ['attributes'] array extra attributes to add to the element
2786
 *
2787
 * @param string $id An ID to stick on the end of the filename
2788
 */
2789
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2790
{
2791
	global $settings, $context, $modSettings;
2792
2793
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') : (is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2794
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2795
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2796
	$params['async'] = isset($params['async']) ? $params['async'] : false;
2797
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2798
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2799
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2800
2801
	// If this is an external file, automatically set this to false.
2802
	if (!empty($params['external']))
2803
		$params['minimize'] = false;
2804
2805
	// Account for shorthand like admin.js?alp21 filenames
2806
	$id = empty($id) ? strtr(str_replace('.js', '', basename($fileName)), '?', '_') : $id;
2807
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2808
2809
	// Is this a local file?
2810
	if (empty($params['external']))
2811
	{
2812
		// Are we validating it exists on disk?
2813
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/scripts/' . $fileName)) === false)
2814
		{
2815
			// Can't find it in this theme, how about the default?
2816
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/scripts/' . $fileName)) !== false)
2817
			{
2818
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName;
2819
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName;
2820
			}
2821
2822
			else
2823
			{
2824
				$fileUrl = false;
2825
				$filePath = false;
2826
			}
2827
		}
2828
2829
		else
2830
		{
2831
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName;
2832
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName;
2833
			$mtime = @filemtime($filePath);
2834
		}
2835
	}
2836
2837
	// An external file doesn't have a filepath. Mock one for simplicity.
2838
	else
2839
	{
2840
		$fileUrl = $fileName;
2841
		$filePath = $fileName;
2842
	}
2843
2844
	$mtime = empty($mtime) ? 0 : $mtime;
2845
2846
	// Add it to the array for use in the template
2847
	if (!empty($fileName))
2848
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2849
2850
	if ($mtime > $modSettings['browser_cache'])
2851
		updateSettings(array('browser_cache' => $mtime));
2852
}
2853
2854
/**
2855
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2856
 * Cleaner and easier (for modders) than to use the function below.
2857
 *
2858
 * @param string $key The key for this variable
2859
 * @param string $value The value
2860
 * @param bool $escape Whether or not to escape the value
2861
 */
2862
function addJavaScriptVar($key, $value, $escape = false)
2863
{
2864
	global $context;
2865
2866
	if (!empty($key) && (!empty($value) || $value === '0'))
2867
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2868
}
2869
2870
/**
2871
 * Add a block of inline Javascript code to be executed later
2872
 *
2873
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2874
 *   or for scripts that require help from PHP/whatever, this can be useful.
2875
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2876
 *
2877
 * @param string $javascript Some JS code
2878
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2879
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2880
 */
2881
function addInlineJavaScript($javascript, $defer = false)
2882
{
2883
	global $context;
2884
2885
	if (empty($javascript))
2886
		return false;
2887
2888
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2889
}
2890
2891
/**
2892
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2893
 *
2894
 * @param string $template_name The name of a template file
2895
 * @param string $lang A specific language to load this file from
2896
 * @param bool $fatal Whether to die with an error if it can't be loaded
2897
 * @param bool $force_reload Whether to load the file again if it's already loaded
2898
 * @return string The language actually loaded.
2899
 */
2900
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2901
{
2902
	global $user_info, $language, $settings, $context, $modSettings;
2903
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2904
	static $already_loaded = array();
2905
2906
	// Default to the user's language.
2907
	if ($lang == '')
2908
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2909
2910
	// Do we want the English version of language file as fallback?
2911
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2912
		loadLanguage($template_name, 'english', false);
2913
2914
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2915
		return $lang;
2916
2917
	// Make sure we have $settings - if not we're in trouble and need to find it!
2918
	if (empty($settings['default_theme_dir']))
2919
	{
2920
		require_once($sourcedir . '/ScheduledTasks.php');
2921
		loadEssentialThemeData();
2922
	}
2923
2924
	// What theme are we in?
2925
	$theme_name = basename($settings['theme_url']);
2926
	if (empty($theme_name))
2927
		$theme_name = 'unknown';
2928
2929
	// For each file open it up and write it out!
2930
	foreach (explode('+', $template_name) as $template)
2931
	{
2932
		// Obviously, the current theme is most important to check.
2933
		$attempts = array(
2934
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2935
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2936
		);
2937
2938
		// Do we have a base theme to worry about?
2939
		if (isset($settings['base_theme_dir']))
2940
		{
2941
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2942
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2943
		}
2944
2945
		// Fall back on the default theme if necessary.
2946
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2947
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2948
2949
		// Fall back on the English language if none of the preferred languages can be found.
2950
		if (!in_array('english', array($lang, $language)))
2951
		{
2952
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2953
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2954
		}
2955
2956
		// Try to find the language file.
2957
		$found = false;
2958
		foreach ($attempts as $k => $file)
2959
		{
2960
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2961
			{
2962
				// Include it!
2963
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2964
2965
				// Note that we found it.
2966
				$found = true;
2967
2968
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2969
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2970
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2971
2972
				break;
2973
			}
2974
		}
2975
2976
		// That couldn't be found!  Log the error, but *try* to continue normally.
2977
		if (!$found && $fatal)
2978
		{
2979
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2980
			break;
2981
		}
2982
2983
		// For the sake of backward compatibility
2984
		if (!empty($txt['emails']))
2985
		{
2986
			foreach ($txt['emails'] as $key => $value)
2987
			{
2988
				$txt[$key . '_subject'] = $value['subject'];
2989
				$txt[$key . '_body'] = $value['body'];
2990
			}
2991
			$txt['emails'] = array();
2992
		}
2993
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2994
		// empty in a normal install. If it isn't it means the forum is using
2995
		// something "old" (it may be the translation, it may be a mod) and this
2996
		// code (like the piece above) takes care of converting it to the new format
2997
		if (!empty($birthdayEmails))
2998
		{
2999
			foreach ($birthdayEmails as $key => $value)
3000
			{
3001
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
3002
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
3003
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
3004
			}
3005
			$birthdayEmails = array();
3006
		}
3007
	}
3008
3009
	// Keep track of what we're up to soldier.
3010
	if ($db_show_debug === true)
3011
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
3012
3013
	// Remember what we have loaded, and in which language.
3014
	$already_loaded[$template_name] = $lang;
3015
3016
	// Return the language actually loaded.
3017
	return $lang;
3018
}
3019
3020
/**
3021
 * Get all parent boards (requires first parent as parameter)
3022
 * It finds all the parents of id_parent, and that board itself.
3023
 * Additionally, it detects the moderators of said boards.
3024
 *
3025
 * @param int $id_parent The ID of the parent board
3026
 * @return array An array of information about the boards found.
3027
 */
3028
function getBoardParents($id_parent)
3029
{
3030
	global $scripturl, $smcFunc;
3031
3032
	// First check if we have this cached already.
3033
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
3034
	{
3035
		$boards = array();
3036
		$original_parent = $id_parent;
3037
3038
		// Loop while the parent is non-zero.
3039
		while ($id_parent != 0)
3040
		{
3041
			$result = $smcFunc['db_query']('', '
3042
				SELECT
3043
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
3044
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
3045
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
3046
				FROM {db_prefix}boards AS b
3047
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
3048
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
3049
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
3050
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
3051
				WHERE b.id_board = {int:board_parent}',
3052
				array(
3053
					'board_parent' => $id_parent,
3054
				)
3055
			);
3056
			// In the EXTREMELY unlikely event this happens, give an error message.
3057
			if ($smcFunc['db_num_rows']($result) == 0)
3058
				fatal_lang_error('parent_not_found', 'critical');
3059
			while ($row = $smcFunc['db_fetch_assoc']($result))
3060
			{
3061
				if (!isset($boards[$row['id_board']]))
3062
				{
3063
					$id_parent = $row['id_parent'];
3064
					$boards[$row['id_board']] = array(
3065
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
3066
						'name' => $row['name'],
3067
						'level' => $row['child_level'],
3068
						'groups' => explode(',', $row['member_groups']),
3069
						'deny_groups' => explode(',', $row['deny_member_groups']),
3070
						'moderators' => array(),
3071
						'moderator_groups' => array()
3072
					);
3073
				}
3074
				// If a moderator exists for this board, add that moderator for all children too.
3075
				if (!empty($row['id_moderator']))
3076
					foreach ($boards as $id => $dummy)
3077
					{
3078
						$boards[$id]['moderators'][$row['id_moderator']] = array(
3079
							'id' => $row['id_moderator'],
3080
							'name' => $row['real_name'],
3081
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
3082
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
3083
						);
3084
					}
3085
3086
				// If a moderator group exists for this board, add that moderator group for all children too
3087
				if (!empty($row['id_moderator_group']))
3088
					foreach ($boards as $id => $dummy)
3089
					{
3090
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
3091
							'id' => $row['id_moderator_group'],
3092
							'name' => $row['group_name'],
3093
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
3094
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
3095
						);
3096
					}
3097
			}
3098
			$smcFunc['db_free_result']($result);
3099
		}
3100
3101
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
3102
	}
3103
3104
	return $boards;
3105
}
3106
3107
/**
3108
 * Attempt to reload our known languages.
3109
 * It will try to choose only utf8 or non-utf8 languages.
3110
 *
3111
 * @param bool $use_cache Whether or not to use the cache
3112
 * @return array An array of information about available languages
3113
 */
3114
function getLanguages($use_cache = true)
3115
{
3116
	global $context, $smcFunc, $settings, $modSettings, $cache_enable;
3117
3118
	// Either we don't use the cache, or its expired.
3119
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600)) == null)
0 ignored issues
show
Bug introduced by
It seems like you are loosely comparing $context['languages'] = ...ble < 1 ? 86400 : 3600) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
3120
	{
3121
		// If we don't have our ucwords function defined yet, let's load the settings data.
3122
		if (empty($smcFunc['ucwords']))
3123
			reloadSettings();
3124
3125
		// If we don't have our theme information yet, let's get it.
3126
		if (empty($settings['default_theme_dir']))
3127
			loadTheme(0, false);
3128
3129
		// Default language directories to try.
3130
		$language_directories = array(
3131
			$settings['default_theme_dir'] . '/languages',
3132
		);
3133
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
3134
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
3135
3136
		// We possibly have a base theme directory.
3137
		if (!empty($settings['base_theme_dir']))
3138
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
3139
3140
		// Remove any duplicates.
3141
		$language_directories = array_unique($language_directories);
3142
3143
		foreach ($language_directories as $language_dir)
3144
		{
3145
			// Can't look in here... doesn't exist!
3146
			if (!file_exists($language_dir))
3147
				continue;
3148
3149
			$dir = dir($language_dir);
3150
			while ($entry = $dir->read())
3151
			{
3152
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
3153
				if (!preg_match('~^index\.((?:.(?!-utf8))+)\.php$~', $entry, $matches))
3154
					continue;
3155
3156
				$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
3157
3158
				if (($spos = strpos($langName, ' ')) !== false)
3159
					$langName = substr($langName, 0, ++$spos) . '(' . substr($langName, $spos) . ')';
3160
3161
				// Get the line we need.
3162
				$fp = @fopen($language_dir . '/' . $entry, 'r');
3163
3164
				// Yay!
3165
				if ($fp)
3166
				{
3167
					while (($line = fgets($fp)) !== false)
3168
					{
3169
						if (strpos($line, '$txt[\'native_name\']') === false)
3170
							continue;
3171
3172
						preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative);
3173
3174
						// Set the language's name.
3175
						if (!empty($matchNative) && !empty($matchNative[1]))
3176
						{
3177
							// Don't mislabel the language if the translator missed this one.
3178
							if ($langName !== 'English' && $matchNative[1] === 'English')
3179
								break;
3180
3181
							$langName = un_htmlspecialchars($matchNative[1]);
3182
							break;
3183
						}
3184
					}
3185
3186
					fclose($fp);
3187
				}
3188
3189
				// Build this language entry.
3190
				$context['languages'][$matches[1]] = array(
3191
					'name' => $langName,
3192
					'selected' => false,
3193
					'filename' => $matches[1],
3194
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
3195
				);
3196
			}
3197
			$dir->close();
3198
		}
3199
3200
		// Avoid confusion when we have more than one English variant installed.
3201
		// Honestly, our default English version should always have been called "English (US)"
3202
		if (substr_count(implode(' ', array_keys($context['languages'])), 'english') > 1 && $context['languages']['english']['name'] === 'English')
3203
			$context['languages']['english']['name'] = 'English (US)';
3204
3205
		// Let's cash in on this deal.
3206
		if (!empty($cache_enable))
3207
			cache_put_data('known_languages', $context['languages'], !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600);
3208
	}
3209
3210
	return $context['languages'];
3211
}
3212
3213
/**
3214
 * Replace all vulgar words with respective proper words. (substring or whole words..)
3215
 * What this function does:
3216
 *  - it censors the passed string.
3217
 *  - if the theme setting allow_no_censored is on, and the theme option
3218
 *	show_no_censored is enabled, does not censor, unless force is also set.
3219
 *  - it caches the list of censored words to reduce parsing.
3220
 *
3221
 * @param string &$text The text to censor
3222
 * @param bool $force Whether to censor the text regardless of settings
3223
 * @return string The censored text
3224
 */
3225
function censorText(&$text, $force = false)
3226
{
3227
	global $modSettings, $options, $txt;
3228
	static $censor_vulgar = null, $censor_proper;
3229
3230
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
3231
		return $text;
3232
3233
	// If they haven't yet been loaded, load them.
3234
	if ($censor_vulgar == null)
3235
	{
3236
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
3237
		$censor_proper = explode("\n", $modSettings['censor_proper']);
3238
3239
		// Quote them for use in regular expressions.
3240
		if (!empty($modSettings['censorWholeWord']))
3241
		{
3242
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3243
3244
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3245
			{
3246
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3247
3248
				// Use the faster \b if we can, or something more complex if we can't
3249
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3250
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3251
3252
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3253
			}
3254
		}
3255
	}
3256
3257
	// Censoring isn't so very complicated :P.
3258
	if (empty($modSettings['censorWholeWord']))
3259
	{
3260
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3261
		$text = $func($censor_vulgar, $censor_proper, $text);
3262
	}
3263
	else
3264
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3265
3266
	return $text;
3267
}
3268
3269
/**
3270
 * Load the template/language file using require
3271
 * 	- loads the template or language file specified by filename.
3272
 * 	- uses eval unless disableTemplateEval is enabled.
3273
 * 	- outputs a parse error if the file did not exist or contained errors.
3274
 * 	- attempts to detect the error and line, and show detailed information.
3275
 *
3276
 * @param string $filename The name of the file to include
3277
 * @param bool $once If true only includes the file once (like include_once)
3278
 */
3279
function template_include($filename, $once = false)
3280
{
3281
	global $context, $txt, $scripturl, $modSettings;
3282
	global $boardurl, $boarddir;
3283
	global $maintenance, $mtitle, $mmessage;
3284
	static $templates = array();
3285
3286
	// We want to be able to figure out any errors...
3287
	@ini_set('track_errors', '1');
3288
3289
	// Don't include the file more than once, if $once is true.
3290
	if ($once && in_array($filename, $templates))
3291
		return;
3292
	// Add this file to the include list, whether $once is true or not.
3293
	else
3294
		$templates[] = $filename;
3295
3296
	$file_found = file_exists($filename);
3297
3298
	if ($once && $file_found)
3299
		require_once($filename);
3300
	elseif ($file_found)
3301
		require($filename);
3302
3303
	if ($file_found !== true)
3304
	{
3305
		ob_end_clean();
3306
		if (!empty($modSettings['enableCompressedOutput']))
3307
			@ob_start('ob_gzhandler');
3308
		else
3309
			ob_start();
3310
3311
		if (isset($_GET['debug']))
3312
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3313
3314
		// Don't cache error pages!!
3315
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3316
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3317
		header('cache-control: no-cache');
3318
3319
		if (!isset($txt['template_parse_error']))
3320
		{
3321
			$txt['template_parse_error'] = 'Template Parse Error!';
3322
			$txt['template_parse_error_message'] = 'It seems something has gone sour on the forum with the template system.  This problem should only be temporary, so please come back later and try again.  If you continue to see this message, please contact the administrator.<br><br>You can also try <a href="javascript:location.reload();">refreshing this page</a>.';
3323
			$txt['template_parse_error_details'] = 'There was a problem loading the <pre><strong>%1$s</strong></pre> template or language file.  Please check the syntax and try again - remember, single quotes (<pre>\'</pre>) often have to be escaped with a slash (<pre>\\</pre>).  To see more specific error information from PHP, try <a href="%2$s%1$s" class="extern">accessing the file directly</a>.<br><br>You may want to try to <a href="javascript:location.reload();">refresh this page</a> or <a href="%3$s?theme=1">use the default theme</a>.';
3324
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3325
		}
3326
3327
		// First, let's get the doctype and language information out of the way.
3328
		echo '<!DOCTYPE html>
3329
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3330
	<head>';
3331
		if (isset($context['character_set']))
3332
			echo '
3333
		<meta charset="', $context['character_set'], '">';
3334
3335
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3336
			echo '
3337
		<title>', $mtitle, '</title>
3338
	</head>
3339
	<body>
3340
		<h3>', $mtitle, '</h3>
3341
		', $mmessage, '
3342
	</body>
3343
</html>';
3344
		elseif (!allowedTo('admin_forum'))
3345
			echo '
3346
		<title>', $txt['template_parse_error'], '</title>
3347
	</head>
3348
	<body>
3349
		<h3>', $txt['template_parse_error'], '</h3>
3350
		', $txt['template_parse_error_message'], '
3351
	</body>
3352
</html>';
3353
		else
3354
		{
3355
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3356
			$error_array = error_get_last();
3357
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3358
				$error = $error_array['message'];
3359
			if (empty($error))
3360
				$error = $txt['template_parse_errmsg'];
3361
3362
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3363
3364
			echo '
3365
		<title>', $txt['template_parse_error'], '</title>
3366
	</head>
3367
	<body>
3368
		<h3>', $txt['template_parse_error'], '</h3>
3369
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')), $boardurl, $scripturl);
3370
3371
			if (!empty($error))
3372
				echo '
3373
		<hr>
3374
3375
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3376
3377
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3378
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3379
			{
3380
				$data = file($filename);
3381
				$data2 = highlight_php_code(implode('', $data));
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3381
				$data2 = highlight_php_code(implode('', /** @scrutinizer ignore-type */ $data));
Loading history...
3382
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3383
3384
				// Fix the PHP code stuff...
3385
				if (!isBrowser('gecko'))
3386
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3387
				else
3388
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3389
3390
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3391
				$j = -1;
3392
				foreach ($data as $line)
3393
				{
3394
					$j++;
3395
3396
					if (substr_count($line, '<br>') == 0)
3397
						continue;
3398
3399
					$n = substr_count($line, '<br>');
3400
					for ($i = 0; $i < $n; $i++)
3401
					{
3402
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3403
						unset($data2[$j + $i + 1]);
3404
					}
3405
					$j += $n;
3406
				}
3407
				$data2 = array_values($data2);
3408
				array_unshift($data2, '');
3409
3410
				echo '
3411
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3412
3413
				// Figure out what the color coding was before...
3414
				$line = max($match[1] - 9, 1);
3415
				$last_line = '';
3416
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3417
					if (strpos($data2[$line2], '<') !== false)
3418
					{
3419
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3420
							$last_line = $color_match[1];
3421
						break;
3422
					}
3423
3424
				// Show the relevant lines...
3425
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3426
				{
3427
					if ($line == $match[1])
3428
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3429
3430
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3431
					if (isset($data2[$line]) && $data2[$line] != '')
3432
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3433
3434
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3435
					{
3436
						$last_line = $color_match[1];
3437
						echo '</', substr($last_line, 1, 4), '>';
3438
					}
3439
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3440
						$last_line = '';
3441
					elseif ($last_line != '' && $data2[$line] != '')
3442
						echo '</', substr($last_line, 1, 4), '>';
3443
3444
					if ($line == $match[1])
3445
						echo '</pre></div><pre style="margin: 0;">';
3446
					else
3447
						echo "\n";
3448
				}
3449
3450
				echo '</pre></div>';
3451
			}
3452
3453
			echo '
3454
	</body>
3455
</html>';
3456
		}
3457
3458
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
3459
	}
3460
}
3461
3462
/**
3463
 * Initialize a database connection.
3464
 */
3465
function loadDatabase()
3466
{
3467
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3468
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port, $db_mb4;
3469
3470
	// Figure out what type of database we are using.
3471
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3472
		$db_type = 'mysql';
3473
3474
	// Load the file for the database.
3475
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3476
3477
	$db_options = array();
3478
3479
	// Add in the port if needed
3480
	if (!empty($db_port))
3481
		$db_options['port'] = $db_port;
3482
3483
	if (!empty($db_mb4))
3484
		$db_options['db_mb4'] = $db_mb4;
3485
3486
	// If we are in SSI try them first, but don't worry if it doesn't work, we have the normal username and password we can use.
3487
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3488
	{
3489
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3490
3491
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3492
	}
3493
3494
	// Either we aren't in SSI mode, or it failed.
3495
	if (empty($db_connection))
3496
	{
3497
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3498
3499
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3500
	}
3501
3502
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3503
	if (!$db_connection)
3504
		display_db_error();
3505
3506
	// If in SSI mode fix up the prefix.
3507
	if (SMF == 'SSI')
0 ignored issues
show
introduced by
The condition SMF == 'SSI' is always true.
Loading history...
3508
		db_fix_prefix($db_prefix, $db_name);
3509
}
3510
3511
/**
3512
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3513
 *
3514
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
3515
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3516
 * @return object|false A object of $cacheAPI, or False on failure.
3517
 */
3518
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3519
{
3520
	global $sourcedir, $cacheAPI, $cache_accelerator, $cache_enable;
3521
3522
	// is caching enabled?
3523
	if (empty($cache_enable) && empty($overrideCache))
3524
		return false;
3525
3526
	// Not overriding this and we have a cacheAPI, send it back.
3527
	if (empty($overrideCache) && is_object($cacheAPI))
3528
		return $cacheAPI;
3529
	elseif (is_null($cacheAPI))
3530
		$cacheAPI = false;
3531
3532
	// Make sure our class is in session.
3533
	require_once($sourcedir . '/Class-CacheAPI.php');
3534
3535
	// What accelerator we are going to try.
3536
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : (!empty($cache_accelerator) ? $cache_accelerator : 'smf');
3537
	$tryAccelerator = strtolower($tryAccelerator);
3538
3539
	// Do some basic tests.
3540
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3541
	{
3542
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3543
3544
		$cache_class_name = $tryAccelerator . '_cache';
3545
		$testAPI = new $cache_class_name();
3546
3547
		// No Support?  NEXT!
3548
		if (!$testAPI->isSupported())
3549
		{
3550
			// Can we save ourselves?
3551
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3552
				return loadCacheAccelerator(null, false);
3553
			return false;
3554
		}
3555
3556
		// Connect up to the accelerator.
3557
		$testAPI->connect();
3558
3559
		// Don't set this if we are overriding the cache.
3560
		if (is_null($overrideCache))
3561
		{
3562
			$cacheAPI = $testAPI;
3563
			return $cacheAPI;
3564
		}
3565
		else
3566
			return $testAPI;
3567
	}
3568
}
3569
3570
/**
3571
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3572
 *
3573
 * @param string $key The key for this entry
3574
 * @param string $file The file associated with this entry
3575
 * @param string $function The function to call
3576
 * @param array $params Parameters to be passed to the specified function
3577
 * @param int $level The cache level
3578
 * @return string The cached data
3579
 */
3580
function cache_quick_get($key, $file, $function, $params, $level = 1)
3581
{
3582
	global $modSettings, $sourcedir, $cache_enable;
3583
3584
	// @todo Why are we doing this if caching is disabled?
3585
3586
	if (function_exists('call_integration_hook'))
3587
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3588
3589
	/* Refresh the cache if either:
3590
		1. Caching is disabled.
3591
		2. The cache level isn't high enough.
3592
		3. The item has not been cached or the cached item expired.
3593
		4. The cached item has a custom expiration condition evaluating to true.
3594
		5. The expire time set in the cache item has passed (needed for Zend).
3595
	*/
3596
	if (empty($cache_enable) || $cache_enable < $level || !is_array($cache_block = cache_get_data($key, 3600)) || (!empty($cache_block['refresh_eval']) && eval($cache_block['refresh_eval'])) || (!empty($cache_block['expires']) && $cache_block['expires'] < time()))
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
introduced by
The condition is_array($cache_block = ...e_get_data($key, 3600)) is always false.
Loading history...
3597
	{
3598
		require_once($sourcedir . '/' . $file);
3599
		$cache_block = call_user_func_array($function, $params);
3600
3601
		if (!empty($cache_enable) && $cache_enable >= $level)
3602
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3603
	}
3604
3605
	// Some cached data may need a freshening up after retrieval.
3606
	if (!empty($cache_block['post_retri_eval']))
3607
		eval($cache_block['post_retri_eval']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
3608
3609
	if (function_exists('call_integration_hook'))
3610
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3611
3612
	return $cache_block['data'];
3613
}
3614
3615
/**
3616
 * Puts value in the cache under key for ttl seconds.
3617
 *
3618
 * - It may "miss" so shouldn't be depended on
3619
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3620
 * - It supports:
3621
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3622
 *	 memcache: https://php.net/memcache
3623
 *	 APC: https://php.net/apc
3624
 *   APCu: https://php.net/book.apcu
3625
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3626
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3627
 *
3628
 * @param string $key A key for this value
3629
 * @param mixed $value The data to cache
3630
 * @param int $ttl How long (in seconds) the data should be cached for
3631
 */
3632
function cache_put_data($key, $value, $ttl = 120)
3633
{
3634
	global $smcFunc, $cache_enable, $cacheAPI;
3635
	global $cache_hits, $cache_count, $db_show_debug;
3636
3637
	if (empty($cache_enable) || empty($cacheAPI))
3638
		return;
3639
3640
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3641
	if (isset($db_show_debug) && $db_show_debug === true)
3642
	{
3643
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'put', 's' => $value === null ? 0 : strlen(isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value)));
3644
		$st = microtime(true);
3645
	}
3646
3647
	// The API will handle the rest.
3648
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3649
	$cacheAPI->putData($key, $value, $ttl);
3650
3651
	if (function_exists('call_integration_hook'))
3652
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3653
3654
	if (isset($db_show_debug) && $db_show_debug === true)
3655
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.
Loading history...
3656
}
3657
3658
/**
3659
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3660
 * - It may often "miss", so shouldn't be depended on.
3661
 * - It supports the same as cache_put_data().
3662
 *
3663
 * @param string $key The key for the value to retrieve
3664
 * @param int $ttl The maximum age of the cached data
3665
 * @return string|null The cached data or null if nothing was loaded
3666
 */
3667
function cache_get_data($key, $ttl = 120)
3668
{
3669
	global $smcFunc, $cache_enable, $cacheAPI;
3670
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
3671
3672
	if (empty($cache_enable) || empty($cacheAPI))
3673
		return;
3674
3675
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3676
	if (isset($db_show_debug) && $db_show_debug === true)
3677
	{
3678
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3679
		$st = microtime(true);
3680
		$original_key = $key;
3681
	}
3682
3683
	// Ask the API to get the data.
3684
	$value = $cacheAPI->getData($key, $ttl);
3685
3686
	if (isset($db_show_debug) && $db_show_debug === true)
3687
	{
3688
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.
Loading history...
3689
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3690
3691
		if (empty($value))
3692
		{
3693
			if (!is_array($cache_misses))
3694
				$cache_misses = array();
3695
3696
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3697
			$cache_misses[$cache_count_misses] = array('k' => $original_key, 'd' => 'get');
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $original_key does not seem to be defined for all execution paths leading up to this point.
Loading history...
3698
		}
3699
	}
3700
3701
	if (function_exists('call_integration_hook') && isset($value))
3702
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3703
3704
	return empty($value) ? null : (isset($smcFunc['json_decode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3705
}
3706
3707
/**
3708
 * Empty out the cache in use as best it can
3709
 *
3710
 * It may only remove the files of a certain type (if the $type parameter is given)
3711
 * Type can be user, data or left blank
3712
 * 	- user clears out user data
3713
 *  - data clears out system / opcode data
3714
 *  - If no type is specified will perform a complete cache clearing
3715
 * For cache engines that do not distinguish on types, a full cache flush will be done
3716
 *
3717
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3718
 */
3719
function clean_cache($type = '')
3720
{
3721
	global $cacheAPI;
3722
3723
	// If we can't get to the API, can't do this.
3724
	if (empty($cacheAPI))
3725
		return;
3726
3727
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3728
	$cacheAPI->cleanCache($type);
3729
3730
	call_integration_hook('integrate_clean_cache');
3731
	clearstatcache();
3732
}
3733
3734
/**
3735
 * Helper function to set an array of data for an user's avatar.
3736
 *
3737
 * Makes assumptions based on the data provided, the following keys are required:
3738
 * - avatar The raw "avatar" column in members table
3739
 * - email The user's email. Used to get the gravatar info
3740
 * - filename The attachment filename
3741
 *
3742
 * @param array $data An array of raw info
3743
 * @return array An array of avatar data
3744
 */
3745
function set_avatar_data($data = array())
3746
{
3747
	global $modSettings, $smcFunc, $user_info;
3748
3749
	// Come on!
3750
	if (empty($data))
3751
		return array();
3752
3753
	// Set a nice default var.
3754
	$image = '';
3755
3756
	// Gravatar has been set as mandatory!
3757
	if (!empty($modSettings['gravatarOverride']))
3758
	{
3759
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3760
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3761
3762
		elseif (!empty($data['email']))
3763
			$image = get_gravatar_url($data['email']);
3764
	}
3765
3766
	// Look if the user has a gravatar field or has set an external url as avatar.
3767
	else
3768
	{
3769
		// So it's stored in the member table?
3770
		if (!empty($data['avatar']))
3771
		{
3772
			// Gravatar.
3773
			if (stristr($data['avatar'], 'gravatar://'))
3774
			{
3775
				if ($data['avatar'] == 'gravatar://')
3776
					$image = get_gravatar_url($data['email']);
3777
3778
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3779
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3780
			}
3781
3782
			// External url.
3783
			else
3784
				$image = parse_url($data['avatar'], PHP_URL_SCHEME) !== null ? get_proxied_url($data['avatar']) : $modSettings['avatar_url'] . '/' . $data['avatar'];
3785
		}
3786
3787
		// Perhaps this user has an attachment as avatar...
3788
		elseif (!empty($data['filename']))
3789
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3790
3791
		// Right... no avatar... use our default image.
3792
		else
3793
			$image = $modSettings['avatar_url'] . '/default.png';
3794
	}
3795
3796
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3797
3798
	// At this point in time $image has to be filled unless you chose to force gravatar and the user doesn't have the needed data to retrieve it... thus a check for !empty() is still needed.
3799
	if (!empty($image))
3800
		return array(
3801
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3802
			'image' => '<img class="avatar" src="' . $image . '" alt="">',
3803
			'href' => $image,
3804
			'url' => $image,
3805
		);
3806
3807
	// Fallback to make life easier for everyone...
3808
	else
3809
		return array(
3810
			'name' => '',
3811
			'image' => '',
3812
			'href' => '',
3813
			'url' => '',
3814
		);
3815
}
3816
3817
/**
3818
 * Gets, and if necessary creates, the authentication secret to use for cookies, tokens, etc.
3819
 *
3820
 * Note: Never use the $auth_secret variable directly. Always call this function instead.
3821
 *
3822
 * @return string The authentication secret.
3823
 */
3824
function get_auth_secret()
3825
{
3826
	global $auth_secret, $sourcedir, $smcFunc;
3827
3828
	if (empty($auth_secret))
3829
	{
3830
		$auth_secret = bin2hex($smcFunc['random_bytes'](32));
3831
3832
		// It is important to store this in Settings.php, not the database.
3833
		require_once($sourcedir . '/Subs-Admin.php');
3834
		updateSettingsFile(array('auth_secret' => $auth_secret));
3835
	}
3836
3837
	return $auth_secret;
3838
}
3839
3840
?>