Passed
Push — release-2.1 ( 0e5bfc...908430 )
by Mathias
07:53 queued 12s
created

addJavaScriptVar()   C

Complexity

Conditions 15

Size

Total Lines 47
Code Lines 23

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 15
eloc 23
nop 3
dl 0
loc 47
rs 5.9166
c 0
b 0
f 0

How to fix   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 2021 Simple Machines and individual contributors
11
 * @license https://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 RC4
14
 */
15
16
use SMF\Cache\CacheApi;
17
use SMF\Cache\CacheApiInterface;
18
19
if (!defined('SMF'))
20
	die('No direct access...');
21
22
/**
23
 * Load the $modSettings array.
24
 */
25
function reloadSettings()
26
{
27
	global $modSettings, $boarddir, $smcFunc, $txt, $db_character_set;
28
	global $cache_enable, $sourcedir, $context, $forum_version, $boardurl;
29
	global $image_proxy_enabled;
30
31
	// Most database systems have not set UTF-8 as their default input charset.
32
	if (!empty($db_character_set))
33
		$smcFunc['db_query']('', '
34
			SET NAMES {string:db_character_set}',
35
			array(
36
				'db_character_set' => $db_character_set,
37
			)
38
		);
39
40
	// We need some caching support, maybe.
41
	loadCacheAccelerator();
42
43
	// Try to load it from the cache first; it'll never get cached if the setting is off.
44
	if (($modSettings = cache_get_data('modSettings', 90)) == null)
45
	{
46
		$request = $smcFunc['db_query']('', '
47
			SELECT variable, value
48
			FROM {db_prefix}settings',
49
			array(
50
			)
51
		);
52
		$modSettings = array();
53
		if (!$request)
54
			display_db_error();
55
		foreach ($smcFunc['db_fetch_all']($request) as $row)
56
			$modSettings[$row['variable']] = $row['value'];
57
		$smcFunc['db_free_result']($request);
58
59
		// Do a few things to protect against missing settings or settings with invalid values...
60
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
61
			$modSettings['defaultMaxTopics'] = 20;
62
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
63
			$modSettings['defaultMaxMessages'] = 15;
64
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
65
			$modSettings['defaultMaxMembers'] = 30;
66
		if (empty($modSettings['defaultMaxListItems']) || $modSettings['defaultMaxListItems'] <= 0 || $modSettings['defaultMaxListItems'] > 999)
67
			$modSettings['defaultMaxListItems'] = 15;
68
69
		// We explicitly do not use $smcFunc['json_decode'] here yet, as $smcFunc is not fully loaded.
70
		if (!is_array($modSettings['attachmentUploadDir']))
71
		{
72
			$attachmentUploadDir = smf_json_decode($modSettings['attachmentUploadDir'], true, false);
73
			$modSettings['attachmentUploadDir'] = !empty($attachmentUploadDir) ? $attachmentUploadDir : $modSettings['attachmentUploadDir'];
74
		}
75
76
		if (!empty($cache_enable))
77
			cache_put_data('modSettings', $modSettings, 90);
78
	}
79
80
	// Going anything further when the files don't match the database can make nasty messes (unless we're actively installing or upgrading)
81
	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(' ' => '.'))), '!='))
82
	{
83
		// Wipe the cached $modSettings values so they don't interfere with anything later
84
		cache_put_data('modSettings', null);
85
86
		// Redirect to the upgrader if we can
87
		if (file_exists($boarddir . '/upgrade.php'))
88
			header('location: ' . $boardurl . '/upgrade.php');
89
90
		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...
91
	}
92
93
	$modSettings['cache_enable'] = $cache_enable;
94
95
	// Used to force browsers to download fresh CSS and JavaScript when necessary
96
	$modSettings['browser_cache'] = !empty($modSettings['browser_cache']) ? (int) $modSettings['browser_cache'] : 0;
97
	$context['browser_cache'] = '?' . preg_replace('~\W~', '', strtolower(SMF_FULL_VERSION)) . '_' . $modSettings['browser_cache'];
98
99
	// Disable image proxy if we don't have SSL enabled
100
	if (empty($modSettings['force_ssl']))
101
		$image_proxy_enabled = false;
102
103
	// UTF-8 ?
104
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
105
	$context['utf8'] = $utf8;
106
107
	// Set a list of common functions.
108
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
109
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
110
		{
111
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
112
			return $string;
113
		} : function($string)
114
		{
115
			return $string;
116
		};
117
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
118
	{
119
		if (!$utf8 || $smcFunc['db_mb4'])
120
			return $string;
121
122
		$i = 0;
123
		$len = strlen($string);
124
		$new_string = '';
125
		while ($i < $len)
126
		{
127
			$ord = ord($string[$i]);
128
			if ($ord < 128)
129
			{
130
				$new_string .= $string[$i];
131
				$i++;
132
			}
133
			elseif ($ord < 224)
134
			{
135
				$new_string .= $string[$i] . $string[$i + 1];
136
				$i += 2;
137
			}
138
			elseif ($ord < 240)
139
			{
140
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
141
				$i += 3;
142
			}
143
			elseif ($ord < 248)
144
			{
145
				// Magic happens.
146
				$val = (ord($string[$i]) & 0x07) << 18;
147
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
148
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
149
				$val += (ord($string[$i + 3]) & 0x3F);
150
				$new_string .= '&#' . $val . ';';
151
				$i += 4;
152
			}
153
		}
154
		return $new_string;
155
	};
156
157
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
158
	$smcFunc += array(
159
		'entity_fix' => function($string)
160
		{
161
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
162
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
163
		},
164
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
165
		{
166
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
167
		},
168
		'htmltrim' => function($string) use ($utf8, $ent_check)
169
		{
170
			// Preg_replace space characters depend on the character set in use
171
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
172
173
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
174
		},
175
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
176
		{
177
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
178
		},
179
		'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...
180
		{
181
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
182
183
			if (strlen($needle) === 1)
184
			{
185
				$result = array_search($needle, array_slice($haystack_arr, $offset));
186
				return is_int($result) ? $result + $offset : false;
187
			}
188
			else
189
			{
190
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
191
				$needle_size = count($needle_arr);
192
193
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
194
				while ((int) $result === $result)
195
				{
196
					$offset += $result;
197
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
198
						return $offset;
199
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
200
				}
201
				return false;
202
			}
203
		},
204
		'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...
205
		{
206
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
207
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
208
		},
209
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
210
		{
211
			if (!function_exists('mb_strtolower'))
212
			{
213
				require_once($sourcedir . '/Subs-Charset.php');
214
				return utf8_strtolower($string);
215
			}
216
217
			return mb_strtolower($string, 'UTF-8');
218
		} : 'strtolower',
219
		'strtoupper' => $utf8 ? function($string)
220
		{
221
			global $sourcedir;
222
223
			if (!function_exists('mb_strtolower'))
224
			{
225
				require_once($sourcedir . '/Subs-Charset.php');
226
				return utf8_strtoupper($string);
227
			}
228
229
			return mb_strtoupper($string, 'UTF-8');
230
		} : 'strtoupper',
231
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
232
		{
233
			$string = $ent_check($string);
234
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
235
			$string = $matches[0];
236
			while (strlen($string) > $length)
237
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
238
			return $string;
239
		},
240
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
241
		{
242
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
243
		} : 'ucfirst',
244
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
245
		{
246
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
247
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
248
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
249
			return implode('', $words);
250
		} : 'ucwords',
251
		'json_decode' => 'smf_json_decode',
252
		'json_encode' => 'json_encode',
253
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
254
		{
255
			global $sourcedir;
256
257
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
258
			if (!is_callable('random_int'))
259
				require_once($sourcedir . '/random_compat/random.php');
260
261
			return random_int($min, $max);
262
		},
263
		'random_bytes' => function($length = 64)
264
		{
265
			global $sourcedir;
266
267
			if (!is_callable('random_bytes'))
268
				require_once($sourcedir . '/random_compat/random.php');
269
270
			// Make sure length is valid
271
			$length = max(1, (int) $length);
272
273
			return random_bytes($length);
274
		},
275
	);
276
277
	// Setting the timezone is a requirement for some functions.
278
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
Are you sure the usage of timezone_identifiers_list() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
Bug introduced by
timezone_identifiers_list() of type void is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

278
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
279
		date_default_timezone_set($modSettings['default_timezone']);
280
	else
281
	{
282
		// Get PHP's default timezone, if set
283
		$ini_tz = ini_get('date.timezone');
284
		if (!empty($ini_tz))
285
			$modSettings['default_timezone'] = $ini_tz;
286
		else
287
			$modSettings['default_timezone'] = '';
288
289
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
290
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
Are you sure the usage of timezone_identifiers_list() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
291
		{
292
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
293
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
0 ignored issues
show
Bug introduced by
It seems like $server_offset can also be of type false; however, parameter $utcOffset of timezone_name_from_abbr() does only seem to accept integer, 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

293
			$modSettings['default_timezone'] = timezone_name_from_abbr('', /** @scrutinizer ignore-type */ $server_offset, 0);
Loading history...
294
		}
295
296
		date_default_timezone_set($modSettings['default_timezone']);
297
	}
298
299
	// Check the load averages?
300
	if (!empty($modSettings['loadavg_enable']))
301
	{
302
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
303
		{
304
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
305
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
306
				$modSettings['load_average'] = (float) $matches[1];
307
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
308
				$modSettings['load_average'] = (float) $matches[1];
309
			else
310
				unset($modSettings['load_average']);
311
312
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
313
				cache_put_data('loadavg', $modSettings['load_average'], 90);
314
		}
315
316
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
317
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
318
319
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
320
			display_loadavg_error();
321
	}
322
323
	// Ensure we know who can manage boards.
324
	if (!isset($modSettings['board_manager_groups']))
325
	{
326
		require_once($sourcedir . '/Subs-Members.php');
327
		$board_managers = groupsAllowedTo('manage_boards', null);
328
		$board_managers = implode(',', $board_managers['allowed']);
329
		updateSettings(array('board_manager_groups' => $board_managers));
330
	}
331
332
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
333
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
334
335
	// Here to justify the name of this function. :P
336
	// It should be added to the install and upgrade scripts.
337
	// But since the converters need to be updated also. This is easier.
338
	if (empty($modSettings['currentAttachmentUploadDir']))
339
	{
340
		updateSettings(array(
341
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
342
			'currentAttachmentUploadDir' => 1,
343
		));
344
	}
345
346
	// Respect PHP's limits.
347
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
348
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
349
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
350
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
351
352
	// Integration is cool.
353
	if (defined('SMF_INTEGRATION_SETTINGS'))
354
	{
355
		$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...
356
		foreach ($integration_settings as $hook => $function)
357
			add_integration_function($hook, $function, false);
358
	}
359
360
	// Any files to pre include?
361
	if (!empty($modSettings['integrate_pre_include']))
362
	{
363
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
364
		foreach ($pre_includes as $include)
365
		{
366
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
367
			if (file_exists($include))
368
				require_once($include);
369
		}
370
	}
371
372
	// This determines the server... not used in many places, except for login fixing.
373
	$context['server'] = array(
374
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
375
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
376
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
377
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
378
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
379
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
380
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
381
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
382
	);
383
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
384
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
385
386
	// Define a list of icons used across multiple places.
387
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
388
389
	// Define an array for custom profile fields placements.
390
	$context['cust_profile_fields_placement'] = array(
391
		'standard',
392
		'icons',
393
		'above_signature',
394
		'below_signature',
395
		'below_avatar',
396
		'above_member',
397
		'bottom_poster',
398
		'before_member',
399
		'after_member',
400
	);
401
402
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
403
	$context['meta_tags'] = array();
404
405
	// Define an array of allowed HTML tags.
406
	$context['allowed_html_tags'] = array(
407
		'<img>',
408
		'<div>',
409
	);
410
411
	// These are the only valid image types for SMF attachments, by default anyway.
412
	// Note: The values are for image mime types, not file extensions.
413
	$context['valid_image_types'] = array(
414
		IMAGETYPE_GIF => 'gif',
415
		IMAGETYPE_JPEG => 'jpeg',
416
		IMAGETYPE_PNG => 'png',
417
		IMAGETYPE_PSD => 'psd',
418
		IMAGETYPE_BMP => 'bmp',
419
		IMAGETYPE_TIFF_II => 'tiff',
420
		IMAGETYPE_TIFF_MM => 'tiff',
421
		IMAGETYPE_IFF => 'iff'
422
	);
423
424
	// Define a list of allowed tags for descriptions.
425
	$context['description_allowed_tags'] = array(
426
		'abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img',
427
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
428
		'sup', 'table', 'td', 'tr', 'u', 'url',
429
	);
430
431
	// Define a list of deprecated BBC tags
432
	// Even when enabled, they'll only work in old posts and not new ones
433
	$context['legacy_bbc'] = array(
434
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
435
		'green', 'move', 'red', 'shadow', 'tt', 'white',
436
	);
437
438
	// Define a list of BBC tags that require permissions to use
439
	$context['restricted_bbc'] = array(
440
		'html',
441
	);
442
443
	// Login Cookie times. Format: time => txt
444
	$context['login_cookie_times'] = array(
445
		3153600 => 'always_logged_in',
446
		60 => 'one_hour',
447
		1440 => 'one_day',
448
		10080 => 'one_week',
449
		43200 => 'one_month',
450
	);
451
452
	$context['show_spellchecking'] = false;
453
454
	// Call pre load integration functions.
455
	call_integration_hook('integrate_pre_load');
456
}
457
458
/**
459
 * Load all the important user information.
460
 * What it does:
461
 * 	- sets up the $user_info array
462
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
463
 * 	- first checks for cookie or integration validation.
464
 * 	- uses the current session if no integration function or cookie is found.
465
 * 	- checks password length, if member is activated and the login span isn't over.
466
 * 		- if validation fails for the user, $id_member is set to 0.
467
 * 		- updates the last visit time when needed.
468
 */
469
function loadUserSettings()
470
{
471
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
472
	global $cookiename, $user_info, $language, $context, $cache_enable;
473
474
	require_once($sourcedir . '/Subs-Auth.php');
475
476
	// Check first the integration, then the cookie, and last the session.
477
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
478
	{
479
		$id_member = 0;
480
		foreach ($integration_ids as $integration_id)
481
		{
482
			$integration_id = (int) $integration_id;
483
			if ($integration_id > 0)
484
			{
485
				$id_member = $integration_id;
486
				$already_verified = true;
487
				break;
488
			}
489
		}
490
	}
491
	else
492
		$id_member = 0;
493
494
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
495
	{
496
		// First try 2.1 json-format cookie
497
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
498
499
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
500
		if (empty($cookie_data))
501
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
502
503
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
504
505
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
506
507
		// Make sure the cookie is set to the correct domain and path
508
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
509
			setLoginCookie((int) $login_span - time(), $id_member);
510
	}
511
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
512
	{
513
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
514
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
515
516
		if (empty($cookie_data))
517
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
518
519
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
520
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
521
	}
522
523
	// Only load this stuff if the user isn't a guest.
524
	if ($id_member != 0)
525
	{
526
		// Is the member data cached?
527
		if (empty($cache_enable) || $cache_enable < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
528
		{
529
			$request = $smcFunc['db_query']('', '
530
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type, a.width AS "attachment_width", a.height AS "attachment_height"
531
				FROM {db_prefix}members AS mem
532
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
533
				WHERE mem.id_member = {int:id_member}
534
				LIMIT 1',
535
				array(
536
					'id_member' => $id_member,
537
				)
538
			);
539
			$user_settings = $smcFunc['db_fetch_assoc']($request);
540
			$smcFunc['db_free_result']($request);
541
542
			if (!empty($user_settings['avatar']))
543
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
544
545
			if (!empty($cache_enable) && $cache_enable >= 2)
546
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
547
		}
548
549
		// Did we find 'im?  If not, junk it.
550
		if (!empty($user_settings))
551
		{
552
			// As much as the password should be right, we can assume the integration set things up.
553
			if (!empty($already_verified) && $already_verified === true)
554
				$check = true;
555
			// SHA-512 hash should be 128 characters long.
556
			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...
557
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
558
			else
559
				$check = false;
560
561
			// Wrong password or not activated - either way, you're going nowhere.
562
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
563
		}
564
		else
565
			$id_member = 0;
566
567
		// Check if we are forcing TFA
568
		$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...
569
570
		// Don't force TFA on popups
571
		if ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
572
		{
573
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
574
				$force_tfasetup = false;
575
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
576
				$force_tfasetup = false;
577
578
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
579
		}
580
581
		// If we no longer have the member maybe they're being all hackey, stop brute force!
582
		if (!$id_member)
583
		{
584
			require_once($sourcedir . '/LogInOut.php');
585
			validatePasswordFlood(
586
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
587
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
588
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
589
				$id_member != 0
590
			);
591
		}
592
		// Validate for Two Factor Authentication
593
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
594
		{
595
			$tfacookie = $cookiename . '_tfa';
596
			$tfasecret = null;
597
598
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
599
600
			if (empty($verified) || !in_array(true, $verified))
601
			{
602
				if (!empty($_COOKIE[$tfacookie]))
603
				{
604
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
605
606
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
607
608
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
609
						$tfasecret = null;
610
				}
611
612
				// They didn't finish logging in before coming here? Then they're no one to us.
613
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
614
				{
615
					setLoginCookie(-3600, $id_member);
616
					$id_member = 0;
617
					$user_settings = array();
618
				}
619
			}
620
		}
621
		// When authenticating their two factor code, make sure to reset their ID for security
622
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
623
		{
624
			$id_member = 0;
625
			$context['tfa_member'] = $user_settings;
626
			$user_settings = array();
627
		}
628
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
629
		elseif ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
630
		{
631
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
632
			{
633
				//Build an array of ALL user membergroups.
634
				$full_groups = array($user_settings['id_group']);
635
				if (!empty($user_settings['additional_groups']))
636
				{
637
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
638
					$full_groups = array_unique($full_groups); //duplicates, maybe?
639
				}
640
641
				//Find out if any group requires 2FA
642
				$request = $smcFunc['db_query']('', '
643
					SELECT COUNT(id_group) AS total
644
					FROM {db_prefix}membergroups
645
					WHERE tfa_required = {int:tfa_required}
646
						AND id_group IN ({array_int:full_groups})',
647
					array(
648
						'tfa_required' => 1,
649
						'full_groups' => $full_groups,
650
					)
651
				);
652
				$row = $smcFunc['db_fetch_assoc']($request);
653
				$smcFunc['db_free_result']($request);
654
			}
655
			else
656
				$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...
657
658
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
659
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
660
661
			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...
662
				redirectexit('action=profile;area=tfasetup;forced');
663
		}
664
	}
665
666
	// Found 'im, let's set up the variables.
667
	if ($id_member != 0)
668
	{
669
		// Let's not update the last visit time in these cases...
670
		// 1. SSI doesn't count as visiting the forum.
671
		// 2. RSS feeds and XMLHTTP requests don't count either.
672
		// 3. If it was set within this session, no need to set it again.
673
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
674
		// 5. We're still logging in or authenticating
675
		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...
676
		{
677
			// @todo can this be cached?
678
			// Do a quick query to make sure this isn't a mistake.
679
			$result = $smcFunc['db_query']('', '
680
				SELECT poster_time
681
				FROM {db_prefix}messages
682
				WHERE id_msg = {int:id_msg}
683
				LIMIT 1',
684
				array(
685
					'id_msg' => $user_settings['id_msg_last_visit'],
686
				)
687
			);
688
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
689
			$smcFunc['db_free_result']($result);
690
691
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
692
693
			// If it was *at least* five hours ago...
694
			if ($visitTime < time() - 5 * 3600)
695
			{
696
				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']));
697
				$user_settings['last_login'] = time();
698
699
				if (!empty($cache_enable) && $cache_enable >= 2)
700
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
701
702
				if (!empty($cache_enable))
703
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
704
			}
705
		}
706
		elseif (empty($_SESSION['id_msg_last_visit']))
707
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
708
709
		$username = $user_settings['member_name'];
710
711
		if (empty($user_settings['additional_groups']))
712
			$user_info = array(
713
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
714
			);
715
716
		else
717
			$user_info = array(
718
				'groups' => array_merge(
719
					array($user_settings['id_group'], $user_settings['id_post_group']),
720
					explode(',', $user_settings['additional_groups'])
721
				)
722
			);
723
724
		// Because history has proven that it is possible for groups to go bad - clean up in case.
725
		$user_info['groups'] = array_map('intval', $user_info['groups']);
726
727
		// This is a logged in user, so definitely not a spider.
728
		$user_info['possibly_robot'] = false;
729
730
		// Figure out the new time offset.
731
		if (!empty($user_settings['timezone']))
732
		{
733
			// Get the offsets from UTC for the server, then for the user.
734
			$tz_system = new DateTimeZone(@date_default_timezone_get());
735
			$tz_user = new DateTimeZone($user_settings['timezone']);
736
			$time_system = new DateTime('now', $tz_system);
737
			$time_user = new DateTime('now', $tz_user);
738
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
739
		}
740
		else
741
		{
742
			// !!! Compatibility.
743
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
744
		}
745
	}
746
	// If the user is a guest, initialize all the critical user settings.
747
	else
748
	{
749
		// This is what a guest's variables should be.
750
		$username = '';
751
		$user_info = array('groups' => array(-1));
752
		$user_settings = array();
753
754
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
755
			$_COOKIE[$cookiename] = '';
756
757
		// Expire the 2FA cookie
758
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
759
		{
760
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
761
762
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
763
764
			if (time() > $exp)
765
			{
766
				$_COOKIE[$cookiename . '_tfa'] = '';
767
				setTFACookie(-3600, 0, '');
768
			}
769
		}
770
771
		// Create a login token if it doesn't exist yet.
772
		if (!isset($_SESSION['token']['post-login']))
773
			createToken('login');
774
		else
775
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
776
777
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
778
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
779
		{
780
			require_once($sourcedir . '/ManageSearchEngines.php');
781
			$user_info['possibly_robot'] = SpiderCheck();
782
		}
783
		elseif (!empty($modSettings['spider_mode']))
784
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
785
		// If we haven't turned on proper spider hunts then have a guess!
786
		else
787
		{
788
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
789
			$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;
790
		}
791
792
		// We don't know the offset...
793
		$user_info['time_offset'] = 0;
794
	}
795
796
	// Set up the $user_info array.
797
	$user_info += array(
798
		'id' => $id_member,
799
		'username' => $username,
800
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
801
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
802
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
803
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
804
		'is_guest' => $id_member == 0,
805
		'is_admin' => in_array(1, $user_info['groups']),
806
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
807
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
808
		'ip' => $_SERVER['REMOTE_ADDR'],
809
		'ip2' => $_SERVER['BAN_CHECK_IP'],
810
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
811
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
812
		'avatar' => array(
813
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
814
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
815
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
816
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0,
817
			'width' => isset($user_settings['attachment_width']) > 0 ? $user_settings['attachment_width']: 0,
818
			'height' => isset($user_settings['attachment_height']) > 0 ? $user_settings['attachment_height'] : 0,
819
		),
820
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
821
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
822
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
823
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
824
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
825
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
826
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
827
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
828
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
829
		'permissions' => array(),
830
	);
831
	$user_info['groups'] = array_unique($user_info['groups']);
832
	$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);
833
834
	// 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.
835
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
836
		unset($user_info['ignoreboards'][$tmp]);
837
838
	// Allow the user to change their language.
839
	if (!empty($modSettings['userLanguage']))
840
	{
841
		$languages = getLanguages();
842
843
		// Is it valid?
844
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
845
		{
846
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
847
848
			// Make it permanent for members.
849
			if (!empty($user_info['id']))
850
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
851
			else
852
				$_SESSION['language'] = $user_info['language'];
853
			// Reload same url with new language, if it exist
854
			if (isset($_SESSION['old_url']))
855
				redirectexit($_SESSION['old_url']);
856
		}
857
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
858
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
859
	}
860
861
	$temp = build_query_board($user_info['id']);
862
	$user_info['query_see_board'] = $temp['query_see_board'];
863
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
864
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
865
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
866
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
867
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
868
869
	call_integration_hook('integrate_user_info');
870
}
871
872
/**
873
 * Load minimal user info from members table.
874
 * Intended for use by background tasks that need to populate $user_info.
875
 *
876
 * @param int|array $user_ids The users IDs to get the data for.
877
 * @return array
878
 * @throws Exception
879
 */
880
function loadMinUserInfo($user_ids = array())
881
{
882
	global $smcFunc, $modSettings, $language;
883
	static $user_info_min = array();
884
885
	$user_ids = (array) $user_ids;
886
887
	// Already loaded?
888
	if (!empty($user_ids))
889
		$user_ids = array_diff($user_ids, array_keys($user_info_min));
890
891
	if (empty($user_ids))
892
		return $user_info_min;
893
894
	$columns_to_load = array(
895
		'id_member',
896
		'member_name',
897
		'real_name',
898
		'time_offset',
899
		'additional_groups',
900
		'id_group',
901
		'id_post_group',
902
		'lngfile',
903
		'smiley_set',
904
		'timezone',
905
	);
906
907
	call_integration_hook('integrate_load_min_user_settings_columns', array(&$columns_to_load));
908
909
	$request = $smcFunc['db_query']('', '
910
		SELECT {raw:columns}
911
		FROM {db_prefix}members
912
		WHERE id_member IN ({array_int:user_ids})',
913
		array(
914
			'user_ids' => array_map('intval', array_unique($user_ids)),
915
			'columns' => implode(', ', $columns_to_load)
916
		)
917
	);
918
919
	while ($row = $smcFunc['db_fetch_assoc']($request))
920
	{
921
		$user_info_min[$row['id_member']] = array(
922
			'id' => $row['id_member'],
923
			'username' => $row['member_name'],
924
			'name' => isset($row['real_name']) ? $row['real_name'] : '',
925
			'language' => (empty($row['lngfile']) || empty($modSettings['userLanguage'])) ? $language : $row['lngfile'],
926
			'is_guest' => false,
927
			'time_format' => empty($row['time_format']) ? $modSettings['time_format'] : $row['time_format'],
928
			'smiley_set' => empty($row['smiley_set']) ? $modSettings['smiley_sets_default'] : $row['smiley_set'],
929
		);
930
931
		if (empty($row['additional_groups']))
932
			$user_info_min[$row['id_member']]['groups'] = array($row['id_group'], $row['id_post_group']);
933
934
		else
935
			$user_info_min[$row['id_member']]['groups'] = array_merge(
936
				array($row['id_group'], $row['id_post_group']),
937
				explode(',', $row['additional_groups'])
938
			);
939
940
		$user_info_min[$row['id_member']]['is_admin'] = in_array(1, $user_info_min[$row['id_member']]['groups']);
941
942
		if (!empty($row['timezone']))
943
		{
944
			$tz_system = new \DateTimeZone(@date_default_timezone_get());
945
			$tz_user = new \DateTimeZone($row['timezone']);
946
			$time_system = new \DateTime('now', $tz_system);
947
			$time_user = new \DateTime('now', $tz_user);
948
			$user_info_min[$row['id_member']]['time_offset'] = ($tz_user->getOffset($time_user) -
949
					$tz_system->getOffset($time_system)) / 3600;
950
		}
951
952
		else
953
			$user_info_min[$row['id_member']]['time_offset'] = empty($row['time_offset']) ? 0 : $row['time_offset'];
954
	}
955
956
	$smcFunc['db_free_result']($request);
957
958
	call_integration_hook('integrate_load_min_user_settings', array(&$user_info_min));
959
960
	return $user_info_min;
961
}
962
963
/**
964
 * Check for moderators and see if they have access to the board.
965
 * What it does:
966
 * - sets up the $board_info array for current board information.
967
 * - if cache is enabled, the $board_info array is stored in cache.
968
 * - redirects to appropriate post if only message id is requested.
969
 * - is only used when inside a topic or board.
970
 * - determines the local moderators for the board.
971
 * - adds group id 3 if the user is a local moderator for the board they are in.
972
 * - prevents access if user is not in proper group nor a local moderator of the board.
973
 */
974
function loadBoard()
975
{
976
	global $txt, $scripturl, $context, $modSettings;
977
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
978
979
	// Assume they are not a moderator.
980
	$user_info['is_mod'] = false;
981
	$context['user']['is_mod'] = &$user_info['is_mod'];
982
983
	// Start the linktree off empty..
984
	$context['linktree'] = array();
985
986
	// Have they by chance specified a message id but nothing else?
987
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
988
	{
989
		// Make sure the message id is really an int.
990
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
991
992
		// Looking through the message table can be slow, so try using the cache first.
993
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
994
		{
995
			$request = $smcFunc['db_query']('', '
996
				SELECT id_topic
997
				FROM {db_prefix}messages
998
				WHERE id_msg = {int:id_msg}
999
				LIMIT 1',
1000
				array(
1001
					'id_msg' => $_REQUEST['msg'],
1002
				)
1003
			);
1004
1005
			// So did it find anything?
1006
			if ($smcFunc['db_num_rows']($request))
1007
			{
1008
				list ($topic) = $smcFunc['db_fetch_row']($request);
1009
				$smcFunc['db_free_result']($request);
1010
				// Save save save.
1011
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
1012
			}
1013
		}
1014
1015
		// Remember redirection is the key to avoiding fallout from your bosses.
1016
		if (!empty($topic))
1017
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
1018
		else
1019
		{
1020
			loadPermissions();
1021
			loadTheme();
1022
			fatal_lang_error('topic_gone', false);
1023
		}
1024
	}
1025
1026
	// Load this board only if it is specified.
1027
	if (empty($board) && empty($topic))
1028
	{
1029
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
1030
		return;
1031
	}
1032
1033
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1034
	{
1035
		// @todo SLOW?
1036
		if (!empty($topic))
1037
			$temp = cache_get_data('topic_board-' . $topic, 120);
1038
		else
1039
			$temp = cache_get_data('board-' . $board, 120);
1040
1041
		if (!empty($temp))
1042
		{
1043
			$board_info = $temp;
1044
			$board = $board_info['id'];
1045
		}
1046
	}
1047
1048
	if (empty($temp))
1049
	{
1050
		$custom_column_selects = array();
1051
		$custom_column_parameters = [
1052
			'current_topic' => $topic,
1053
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
1054
		];
1055
1056
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
1057
1058
		$request = $smcFunc['db_query']('load_board_info', '
1059
			SELECT
1060
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
1061
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
1062
				COALESCE(mem.id_member, 0) AS id_moderator,
1063
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
1064
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
1065
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
1066
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
1067
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
1068
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
1069
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1070
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
1071
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
1072
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
1073
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1074
			WHERE b.id_board = {raw:board_link}',
1075
			$custom_column_parameters
1076
		);
1077
1078
		// If there aren't any, skip.
1079
		if ($smcFunc['db_num_rows']($request) > 0)
1080
		{
1081
			$row = $smcFunc['db_fetch_assoc']($request);
1082
1083
			// Set the current board.
1084
			if (!empty($row['id_board']))
1085
				$board = $row['id_board'];
1086
1087
			// Basic operating information. (globals... :/)
1088
			$board_info = array(
1089
				'id' => $board,
1090
				'moderators' => array(),
1091
				'moderator_groups' => array(),
1092
				'cat' => array(
1093
					'id' => $row['id_cat'],
1094
					'name' => $row['cname']
1095
				),
1096
				'name' => $row['bname'],
1097
				'description' => $row['description'],
1098
				'num_topics' => $row['num_topics'],
1099
				'unapproved_topics' => $row['unapproved_topics'],
1100
				'unapproved_posts' => $row['unapproved_posts'],
1101
				'unapproved_user_topics' => 0,
1102
				'parent_boards' => getBoardParents($row['id_parent']),
1103
				'parent' => $row['id_parent'],
1104
				'child_level' => $row['child_level'],
1105
				'theme' => $row['id_theme'],
1106
				'override_theme' => !empty($row['override_theme']),
1107
				'profile' => $row['id_profile'],
1108
				'redirect' => $row['redirect'],
1109
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
1110
				'posts_count' => empty($row['count_posts']),
1111
				'cur_topic_approved' => empty($topic) || $row['approved'],
1112
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1113
			);
1114
1115
			// Load the membergroups allowed, and check permissions.
1116
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1117
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1118
1119
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1120
1121
			if (!empty($modSettings['board_manager_groups']))
1122
			{
1123
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1124
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1125
			}
1126
1127
			do
1128
			{
1129
				if (!empty($row['id_moderator']))
1130
					$board_info['moderators'][$row['id_moderator']] = array(
1131
						'id' => $row['id_moderator'],
1132
						'name' => $row['real_name'],
1133
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1134
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1135
					);
1136
1137
				if (!empty($row['id_moderator_group']))
1138
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1139
						'id' => $row['id_moderator_group'],
1140
						'name' => $row['group_name'],
1141
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1142
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1143
					);
1144
			}
1145
			while ($row = $smcFunc['db_fetch_assoc']($request));
1146
1147
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1148
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1149
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1150
			{
1151
				// Free the previous result
1152
				$smcFunc['db_free_result']($request);
1153
1154
				// @todo why is this using id_topic?
1155
				// @todo Can this get cached?
1156
				$request = $smcFunc['db_query']('', '
1157
					SELECT COUNT(id_topic)
1158
					FROM {db_prefix}topics
1159
					WHERE id_member_started={int:id_member}
1160
						AND approved = {int:unapproved}
1161
						AND id_board = {int:board}',
1162
					array(
1163
						'id_member' => $user_info['id'],
1164
						'unapproved' => 0,
1165
						'board' => $board,
1166
					)
1167
				);
1168
1169
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1170
			}
1171
1172
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1173
			{
1174
				// @todo SLOW?
1175
				if (!empty($topic))
1176
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1177
				cache_put_data('board-' . $board, $board_info, 120);
1178
			}
1179
		}
1180
		else
1181
		{
1182
			// Otherwise the topic is invalid, there are no moderators, etc.
1183
			$board_info = array(
1184
				'moderators' => array(),
1185
				'moderator_groups' => array(),
1186
				'error' => 'exist'
1187
			);
1188
			$topic = null;
1189
			$board = 0;
1190
		}
1191
		$smcFunc['db_free_result']($request);
1192
	}
1193
1194
	if (!empty($topic))
1195
		$_GET['board'] = (int) $board;
1196
1197
	if (!empty($board))
1198
	{
1199
		// Get this into an array of keys for array_intersect
1200
		$moderator_groups = array_keys($board_info['moderator_groups']);
1201
1202
		// Now check if the user is a moderator.
1203
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1204
1205
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1206
			$board_info['error'] = 'access';
1207
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1208
			$board_info['error'] = 'access';
1209
1210
		// Build up the linktree.
1211
		$context['linktree'] = array_merge(
1212
			$context['linktree'],
1213
			array(array(
1214
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1215
				'name' => $board_info['cat']['name']
1216
			)),
1217
			array_reverse($board_info['parent_boards']),
1218
			array(array(
1219
				'url' => $scripturl . '?board=' . $board . '.0',
1220
				'name' => $board_info['name']
1221
			))
1222
		);
1223
	}
1224
1225
	// Set the template contextual information.
1226
	$context['user']['is_mod'] = &$user_info['is_mod'];
1227
	$context['current_topic'] = $topic;
1228
	$context['current_board'] = $board;
1229
1230
	// No posting in redirection boards!
1231
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1232
		$board_info['error'] = 'post_in_redirect';
1233
1234
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1235
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1236
	{
1237
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1238
		loadPermissions();
1239
		loadTheme();
1240
1241
		$_GET['board'] = '';
1242
		$_GET['topic'] = '';
1243
1244
		// The linktree should not give the game away mate!
1245
		$context['linktree'] = array(
1246
			array(
1247
				'url' => $scripturl,
1248
				'name' => $context['forum_name_html_safe']
1249
			)
1250
		);
1251
1252
		// If it's a prefetching agent or we're requesting an attachment.
1253
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1254
		{
1255
			ob_end_clean();
1256
			send_http_status(403);
1257
			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...
1258
		}
1259
		elseif ($board_info['error'] == 'post_in_redirect')
1260
		{
1261
			// Slightly different error message here...
1262
			fatal_lang_error('cannot_post_redirect', false);
1263
		}
1264
		elseif ($user_info['is_guest'])
1265
		{
1266
			loadLanguage('Errors');
1267
			is_not_guest($txt['topic_gone']);
1268
		}
1269
		else
1270
			fatal_lang_error('topic_gone', false);
1271
	}
1272
1273
	if ($user_info['is_mod'])
1274
		$user_info['groups'][] = 3;
1275
}
1276
1277
/**
1278
 * Load this user's permissions.
1279
 */
1280
function loadPermissions()
1281
{
1282
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1283
1284
	if ($user_info['is_admin'])
1285
	{
1286
		banPermissions();
1287
		return;
1288
	}
1289
1290
	if (!empty($cache_enable))
1291
	{
1292
		$cache_groups = $user_info['groups'];
1293
		asort($cache_groups);
1294
		$cache_groups = implode(',', $cache_groups);
1295
		// If it's a spider then cache it different.
1296
		if ($user_info['possibly_robot'])
1297
			$cache_groups .= '-spider';
1298
1299
		if ($cache_enable >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1300
		{
1301
			list ($user_info['permissions']) = $temp;
1302
			banPermissions();
1303
1304
			return;
1305
		}
1306
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1307
			list ($user_info['permissions'], $removals) = $temp;
1308
	}
1309
1310
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1311
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1312
1313
	if (empty($user_info['permissions']))
1314
	{
1315
		// Get the general permissions.
1316
		$request = $smcFunc['db_query']('', '
1317
			SELECT permission, add_deny
1318
			FROM {db_prefix}permissions
1319
			WHERE id_group IN ({array_int:member_groups})
1320
				' . $spider_restrict,
1321
			array(
1322
				'member_groups' => $user_info['groups'],
1323
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1324
			)
1325
		);
1326
		$removals = array();
1327
		while ($row = $smcFunc['db_fetch_assoc']($request))
1328
		{
1329
			if (empty($row['add_deny']))
1330
				$removals[] = $row['permission'];
1331
			else
1332
				$user_info['permissions'][] = $row['permission'];
1333
		}
1334
		$smcFunc['db_free_result']($request);
1335
1336
		if (isset($cache_groups))
1337
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1338
	}
1339
1340
	// Get the board permissions.
1341
	if (!empty($board))
1342
	{
1343
		// Make sure the board (if any) has been loaded by loadBoard().
1344
		if (!isset($board_info['profile']))
1345
			fatal_lang_error('no_board');
1346
1347
		$request = $smcFunc['db_query']('', '
1348
			SELECT permission, add_deny
1349
			FROM {db_prefix}board_permissions
1350
			WHERE (id_group IN ({array_int:member_groups})
1351
				' . $spider_restrict . ')
1352
				AND id_profile = {int:id_profile}',
1353
			array(
1354
				'member_groups' => $user_info['groups'],
1355
				'id_profile' => $board_info['profile'],
1356
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1357
			)
1358
		);
1359
		while ($row = $smcFunc['db_fetch_assoc']($request))
1360
		{
1361
			if (empty($row['add_deny']))
1362
				$removals[] = $row['permission'];
1363
			else
1364
				$user_info['permissions'][] = $row['permission'];
1365
		}
1366
		$smcFunc['db_free_result']($request);
1367
	}
1368
1369
	// Remove all the permissions they shouldn't have ;).
1370
	if (!empty($modSettings['permission_enable_deny']))
1371
		$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...
1372
1373
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1374
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1375
1376
	// Banned?  Watch, don't touch..
1377
	banPermissions();
1378
1379
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1380
	if (!$user_info['is_guest'])
1381
	{
1382
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1383
		{
1384
			require_once($sourcedir . '/Subs-Auth.php');
1385
			rebuildModCache();
1386
		}
1387
		else
1388
			$user_info['mod_cache'] = $_SESSION['mc'];
1389
1390
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1391
		// For example this drastically simplifies certain changes to the profile area.
1392
		$user_info['permissions'][] = 'is_not_guest';
1393
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1394
		$user_info['permissions'][] = 'profile_view_own';
1395
		if (in_array('profile_view', $user_info['permissions']))
1396
			$user_info['permissions'][] = 'profile_view_any';
1397
	}
1398
}
1399
1400
/**
1401
 * Loads an array of users' data by ID or member_name.
1402
 *
1403
 * @param array|string $users An array of users by id or name or a single username/id
1404
 * @param bool $is_name Whether $users contains names
1405
 * @param string $set What kind of data to load (normal, profile, minimal)
1406
 * @return array The ids of the members loaded
1407
 */
1408
function loadMemberData($users, $is_name = false, $set = 'normal')
1409
{
1410
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1411
	global $user_info, $cache_enable, $txt;
1412
1413
	// Can't just look for no users :P.
1414
	if (empty($users))
1415
		return array();
1416
1417
	// Pass the set value
1418
	$context['loadMemberContext_set'] = $set;
1419
1420
	// Make sure it's an array.
1421
	$users = !is_array($users) ? array($users) : array_unique($users);
1422
	$loaded_ids = array();
1423
1424
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1425
	{
1426
		$users = array_values($users);
1427
		for ($i = 0, $n = count($users); $i < $n; $i++)
1428
		{
1429
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1430
			if ($data == null)
1431
				continue;
1432
1433
			$loaded_ids[] = $data['id_member'];
1434
			$user_profile[$data['id_member']] = $data;
1435
			unset($users[$i]);
1436
		}
1437
	}
1438
1439
	// Used by default
1440
	$select_columns = '
1441
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type, a.width "attachment_width", a.height "attachment_height",
1442
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1443
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1444
			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,
1445
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1446
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1447
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1448
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1449
	$select_tables = '
1450
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1451
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1452
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1453
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1454
1455
	// We add or replace according the the set
1456
	switch ($set)
1457
	{
1458
		case 'normal':
1459
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1460
			break;
1461
		case 'profile':
1462
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1463
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1464
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1465
			break;
1466
		case 'minimal':
1467
			$select_columns = '
1468
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1469
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1470
			$select_tables = '';
1471
			break;
1472
		default:
1473
		{
1474
			loadLanguage('Errors');
1475
			trigger_error(sprintf($txt['invalid_member_data_set'], $set), E_USER_WARNING);
1476
		}
1477
	}
1478
1479
	// Allow mods to easily add to the selected member data
1480
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1481
1482
	if (!empty($users))
1483
	{
1484
		// Load the member's data.
1485
		$request = $smcFunc['db_query']('', '
1486
			SELECT' . $select_columns . '
1487
			FROM {db_prefix}members AS mem' . $select_tables . '
1488
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1489
			array(
1490
				'blank_string' => '',
1491
				'users' => $users,
1492
			)
1493
		);
1494
		$new_loaded_ids = array();
1495
		while ($row = $smcFunc['db_fetch_assoc']($request))
1496
		{
1497
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1498
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1499
1500
			// Take care of proxying avatar if required, do this here for maximum reach
1501
			if (!empty($row['avatar']))
1502
				$row['avatar'] = get_proxied_url($row['avatar']);
1503
1504
			// Keep track of the member's normal member group
1505
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1506
1507
			if (isset($row['member_ip']))
1508
				$row['member_ip'] = inet_dtop($row['member_ip']);
1509
			if (isset($row['member_ip2']))
1510
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1511
			$row['id_member'] = (int) $row['id_member'];
1512
			$new_loaded_ids[] = $row['id_member'];
1513
			$loaded_ids[] = $row['id_member'];
1514
			$row['options'] = array();
1515
			$user_profile[$row['id_member']] = $row;
1516
		}
1517
		$smcFunc['db_free_result']($request);
1518
	}
1519
1520
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1521
	{
1522
		$request = $smcFunc['db_query']('', '
1523
			SELECT id_member, variable, value
1524
			FROM {db_prefix}themes
1525
			WHERE id_member IN ({array_int:loaded_ids})',
1526
			array(
1527
				'loaded_ids' => $new_loaded_ids,
1528
			)
1529
		);
1530
		while ($row = $smcFunc['db_fetch_assoc']($request))
1531
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1532
		$smcFunc['db_free_result']($request);
1533
	}
1534
1535
	$additional_mods = array();
1536
1537
	// Are any of these users in groups assigned to moderate this board?
1538
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1539
	{
1540
		foreach ($loaded_ids as $a_member)
1541
		{
1542
			if (!empty($user_profile[$a_member]['additional_groups']))
1543
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1544
			else
1545
				$groups = array($user_profile[$a_member]['id_group']);
1546
1547
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1548
1549
			if (!empty($temp))
1550
			{
1551
				$additional_mods[] = $a_member;
1552
			}
1553
		}
1554
	}
1555
1556
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1557
	{
1558
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1559
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1560
	}
1561
1562
	// Are we loading any moderators?  If so, fix their group data...
1563
	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)
1564
	{
1565
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1566
		{
1567
			$request = $smcFunc['db_query']('', '
1568
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1569
				FROM {db_prefix}membergroups
1570
				WHERE id_group = {int:moderator_group}
1571
				LIMIT 1',
1572
				array(
1573
					'moderator_group' => 3,
1574
				)
1575
			);
1576
			$row = $smcFunc['db_fetch_assoc']($request);
1577
			$smcFunc['db_free_result']($request);
1578
1579
			cache_put_data('moderator_group_info', $row, 480);
1580
		}
1581
1582
		foreach ($temp_mods as $id)
1583
		{
1584
			// By popular demand, don't show admins or global moderators as moderators.
1585
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1586
				$user_profile[$id]['member_group'] = $row['member_group'];
1587
1588
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1589
			if (!empty($row['icons']))
1590
				$user_profile[$id]['icons'] = $row['icons'];
1591
			if (!empty($row['member_group_color']))
1592
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1593
		}
1594
	}
1595
1596
	return $loaded_ids;
1597
}
1598
1599
/**
1600
 * Loads the user's basic values... meant for template/theme usage.
1601
 *
1602
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1603
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1604
 * @return boolean|array  False if the data wasn't loaded or the loaded data.
1605
 * @throws Exception
1606
 */
1607
function loadMemberContext($user, $display_custom_fields = false)
1608
{
1609
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1610
	global $context, $modSettings, $settings, $smcFunc;
1611
	static $already_loaded_custom_fields = array();
1612
	static $loadedLanguages = array();
1613
1614
	// If this person's data is already loaded, skip it.
1615
	if (!empty($memberContext[$user]) && !empty($already_loaded_custom_fields[$user]) >= $display_custom_fields)
1616
		return $memberContext[$user];
1617
1618
	// We can't load guests or members not loaded by loadMemberData()!
1619
	if ($user == 0)
1620
		return false;
1621
	if (!isset($user_profile[$user]))
1622
	{
1623
		loadLanguage('Errors');
1624
		trigger_error(sprintf($txt['user_not_loaded'], $user), E_USER_WARNING);
1625
		return false;
1626
	}
1627
1628
	// Well, it's loaded now anyhow.
1629
	$profile = $user_profile[$user];
1630
1631
	// These minimal values are always loaded
1632
	$memberContext[$user] = array(
1633
		'username' => $profile['member_name'],
1634
		'name' => $profile['real_name'],
1635
		'id' => $profile['id_member'],
1636
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1637
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . sprintf($txt['view_profile_of_username'], $profile['real_name']) . '">' . $profile['real_name'] . '</a>',
1638
		'email' => $profile['email_address'],
1639
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1640
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1641
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1642
	);
1643
1644
	// If the set isn't minimal then load the monstrous array.
1645
	if ($context['loadMemberContext_set'] != 'minimal')
1646
	{
1647
		// Censor everything.
1648
		censorText($profile['signature']);
1649
		censorText($profile['personal_text']);
1650
1651
		// Set things up to be used before hand.
1652
		$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1653
		$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1654
1655
		$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1656
		$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1657
		// Setup the buddy status here (One whole in_array call saved :P)
1658
		$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1659
		$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1660
1661
		//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1662
		if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1663
			$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1664
		elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1665
			$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1666
		else //not set, bye bye
1667
			$group_icon_url = '';
1668
1669
		// Go the extra mile and load the user's native language name.
1670
		if (empty($loadedLanguages))
1671
			$loadedLanguages = getLanguages();
1672
1673
		// Figure out the new time offset.
1674
		if (!empty($profile['timezone']))
1675
		{
1676
			// Get the offsets from UTC for the server, then for the user.
1677
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1678
			$tz_user = new DateTimeZone($profile['timezone']);
1679
			$time_system = new DateTime('now', $tz_system);
1680
			$time_user = new DateTime('now', $tz_user);
1681
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1682
		}
1683
1684
		else
1685
		{
1686
			// !!! Compatibility.
1687
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1688
		}
1689
1690
		$memberContext[$user] += array(
1691
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1692
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1693
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . sprintf($txt['view_profile_of_username'], $profile['real_name']) . '" ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</a>',
1694
			'is_buddy' => $profile['buddy'],
1695
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1696
			'buddies' => $buddy_list,
1697
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1698
			'blurb' => $profile['personal_text'],
1699
			'website' => array(
1700
				'title' => $profile['website_title'],
1701
				'url' => $profile['website_url'],
1702
			),
1703
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1704
			'signature' => $profile['signature'],
1705
			'real_posts' => $profile['posts'],
1706
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1707
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1708
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1709
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1710
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1711
			'online' => array(
1712
				'is_online' => $profile['is_online'],
1713
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1714
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1715
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1716
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1717
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1718
			),
1719
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1720
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1721
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1722
			'options' => $profile['options'],
1723
			'is_guest' => false,
1724
			'primary_group' => $profile['primary_group'],
1725
			'group' => $profile['member_group'],
1726
			'group_color' => $profile['member_group_color'],
1727
			'group_id' => $profile['id_group'],
1728
			'post_group' => $profile['post_group'],
1729
			'post_group_color' => $profile['post_group_color'],
1730
			'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]),
1731
			'warning' => $profile['warning'],
1732
			'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' : (''))),
1733
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1734
			'custom_fields' => array(),
1735
		);
1736
	}
1737
1738
	// If the set isn't minimal then load their avatar as well.
1739
	if ($context['loadMemberContext_set'] != 'minimal')
1740
	{
1741
		$avatarData = set_avatar_data(array(
1742
			'filename' => $profile['filename'],
1743
			'avatar' => $profile['avatar'],
1744
			'email' => $profile['email_address'],
1745
		));
1746
1747
		if (!empty($avatarData['image']))
1748
			$memberContext[$user]['avatar'] = $avatarData;
1749
	}
1750
1751
	// Are we also loading the members custom fields into context?
1752
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1753
	{
1754
		$memberContext[$user]['custom_fields'] = array();
1755
1756
		if (!isset($context['display_fields']))
1757
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1758
1759
		foreach ($context['display_fields'] as $custom)
1760
		{
1761
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1762
				continue;
1763
1764
			$value = $profile['options'][$custom['col_name']];
1765
1766
			$fieldOptions = array();
1767
			$currentKey = 0;
1768
1769
			// Create a key => value array for multiple options fields
1770
			if (!empty($custom['options']))
1771
				foreach ($custom['options'] as $k => $v)
1772
				{
1773
					$fieldOptions[] = $v;
1774
					if (empty($currentKey))
1775
						$currentKey = $v == $value ? $k : 0;
1776
				}
1777
1778
			// BBC?
1779
			if ($custom['bbc'])
1780
				$value = parse_bbc($value);
1781
1782
			// ... or checkbox?
1783
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1784
				$value = $value ? $txt['yes'] : $txt['no'];
1785
1786
			// Enclosing the user input within some other text?
1787
			if (!empty($custom['enclose']))
1788
				$value = strtr($custom['enclose'], array(
1789
					'{SCRIPTURL}' => $scripturl,
1790
					'{IMAGES_URL}' => $settings['images_url'],
1791
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1792
					'{INPUT}' => $value,
1793
					'{KEY}' => $currentKey,
1794
				));
1795
1796
			$memberContext[$user]['custom_fields'][] = array(
1797
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1798
				'col_name' => $custom['col_name'],
1799
				'value' => un_htmlspecialchars($value),
1800
				'raw' => $profile['options'][$custom['col_name']],
1801
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1802
			);
1803
		}
1804
	}
1805
1806
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1807
1808
	$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...
1809
1810
	return $memberContext[$user];
1811
}
1812
1813
/**
1814
 * Loads the user's custom profile fields
1815
 *
1816
 * @param integer|array $users A single user ID or an array of user IDs
1817
 * @param string|array $params Either a string or an array of strings with profile field names
1818
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1819
 */
1820
function loadMemberCustomFields($users, $params)
1821
{
1822
	global $smcFunc, $txt, $scripturl, $settings;
1823
1824
	// Do not waste my time...
1825
	if (empty($users) || empty($params))
1826
		return false;
1827
1828
	// Make sure it's an array.
1829
	$users = !is_array($users) ? array($users) : array_unique($users);
1830
	$params = !is_array($params) ? array($params) : array_unique($params);
1831
	$return = array();
1832
1833
	$request = $smcFunc['db_query']('', '
1834
		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,
1835
		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
1836
		FROM {db_prefix}themes AS t
1837
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1838
		WHERE id_member IN ({array_int:loaded_ids})
1839
			AND variable IN ({array_string:params})
1840
		ORDER BY field_order',
1841
		array(
1842
			'loaded_ids' => $users,
1843
			'params' => $params,
1844
		)
1845
	);
1846
1847
	while ($row = $smcFunc['db_fetch_assoc']($request))
1848
	{
1849
		$fieldOptions = array();
1850
		$currentKey = 0;
1851
1852
		// Create a key => value array for multiple options fields
1853
		if (!empty($row['field_options']))
1854
			foreach (explode(',', $row['field_options']) as $k => $v)
1855
			{
1856
				$fieldOptions[] = $v;
1857
				if (empty($currentKey))
1858
					$currentKey = $v == $row['value'] ? $k : 0;
1859
			}
1860
1861
		// BBC?
1862
		if (!empty($row['bbc']))
1863
			$row['value'] = parse_bbc($row['value']);
1864
1865
		// ... or checkbox?
1866
		elseif (isset($row['type']) && $row['type'] == 'check')
1867
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1868
1869
		// Enclosing the user input within some other text?
1870
		if (!empty($row['enclose']))
1871
			$row['value'] = strtr($row['enclose'], array(
1872
				'{SCRIPTURL}' => $scripturl,
1873
				'{IMAGES_URL}' => $settings['images_url'],
1874
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1875
				'{INPUT}' => un_htmlspecialchars($row['value']),
1876
				'{KEY}' => $currentKey,
1877
			));
1878
1879
		// Send a simple array if there is just 1 param
1880
		if (count($params) == 1)
1881
			$return[$row['id_member']] = $row;
1882
1883
		// More than 1? knock yourself out...
1884
		else
1885
		{
1886
			if (!isset($return[$row['id_member']]))
1887
				$return[$row['id_member']] = array();
1888
1889
			$return[$row['id_member']][$row['variable']] = $row;
1890
		}
1891
	}
1892
1893
	$smcFunc['db_free_result']($request);
1894
1895
	return !empty($return) ? $return : false;
1896
}
1897
1898
/**
1899
 * Loads information about what browser the user is viewing with and places it in $context
1900
 *  - uses the class from {@link Class-BrowserDetect.php}
1901
 */
1902
function detectBrowser()
1903
{
1904
	// Load the current user's browser of choice
1905
	$detector = new browser_detector;
1906
	$detector->detectBrowser();
1907
}
1908
1909
/**
1910
 * Are we using this browser?
1911
 *
1912
 * Wrapper function for detectBrowser
1913
 *
1914
 * @param string $browser The browser we are checking for.
1915
 * @return bool Whether or not the current browser is what we're looking for
1916
 */
1917
function isBrowser($browser)
1918
{
1919
	global $context;
1920
1921
	// Don't know any browser!
1922
	if (empty($context['browser']))
1923
		detectBrowser();
1924
1925
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1926
}
1927
1928
/**
1929
 * Load a theme, by ID.
1930
 *
1931
 * @param int $id_theme The ID of the theme to load
1932
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1933
 */
1934
function loadTheme($id_theme = 0, $initialize = true)
1935
{
1936
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1937
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1938
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1939
1940
	if (empty($id_theme))
1941
	{
1942
		// The theme was specified by the board.
1943
		if (!empty($board_info['theme']))
1944
			$id_theme = $board_info['theme'];
1945
		// The theme is the forum's default.
1946
		else
1947
			$id_theme = $modSettings['theme_guests'];
1948
1949
		// Sometimes the user can choose their own theme.
1950
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1951
		{
1952
			// The theme was specified by REQUEST.
1953
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1954
			{
1955
				$id_theme = (int) $_REQUEST['theme'];
1956
				$_SESSION['id_theme'] = $id_theme;
1957
			}
1958
			// The theme was specified by REQUEST... previously.
1959
			elseif (!empty($_SESSION['id_theme']))
1960
				$id_theme = (int) $_SESSION['id_theme'];
1961
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1962
			elseif (!empty($user_info['theme']))
1963
				$id_theme = $user_info['theme'];
1964
		}
1965
1966
		// Verify the id_theme... no foul play.
1967
		// Always allow the board specific theme, if they are overriding.
1968
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1969
			$id_theme = $board_info['theme'];
1970
		elseif (!empty($modSettings['enableThemes']))
1971
		{
1972
			$themes = explode(',', $modSettings['enableThemes']);
1973
			if (!in_array($id_theme, $themes))
1974
				$id_theme = $modSettings['theme_guests'];
1975
			else
1976
				$id_theme = (int) $id_theme;
1977
		}
1978
	}
1979
1980
	// Allow mod authors the option to override the theme id for custom page themes
1981
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1982
1983
	// We already load the basic stuff?
1984
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1985
	{
1986
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1987
1988
		if (!empty($cache_enable) && $cache_enable >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
1989
		{
1990
			$themeData = $temp;
1991
			$flag = true;
1992
		}
1993
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1994
			$themeData = $temp + array($member => array());
1995
		else
1996
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1997
1998
		if (empty($flag))
1999
		{
2000
			// Load variables from the current or default theme, global or this user's.
2001
			$result = $smcFunc['db_query']('', '
2002
				SELECT variable, value, id_member, id_theme
2003
				FROM {db_prefix}themes
2004
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
2005
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
2006
				ORDER BY id_theme asc',
2007
				array(
2008
					'id_theme' => $id_theme,
2009
					'id_member' => $member,
2010
				)
2011
			);
2012
			// Pick between $settings and $options depending on whose data it is.
2013
			foreach ($smcFunc['db_fetch_all']($result) as $row)
2014
			{
2015
				// There are just things we shouldn't be able to change as members.
2016
				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')))
2017
					continue;
2018
2019
				// If this is the theme_dir of the default theme, store it.
2020
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
2021
					$themeData[0]['default_' . $row['variable']] = $row['value'];
2022
2023
				// If this isn't set yet, is a theme option, or is not the default theme..
2024
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
2025
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
2026
			}
2027
			$smcFunc['db_free_result']($result);
2028
2029
			if (!empty($themeData[-1]))
2030
				foreach ($themeData[-1] as $k => $v)
2031
				{
2032
					if (!isset($themeData[$member][$k]))
2033
						$themeData[$member][$k] = $v;
2034
				}
2035
2036
			if (!empty($cache_enable) && $cache_enable >= 2)
2037
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
2038
			// Only if we didn't already load that part of the cache...
2039
			elseif (!isset($temp))
2040
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
2041
		}
2042
2043
		$settings = $themeData[0];
2044
		$options = $themeData[$member];
2045
2046
		$settings['theme_id'] = $id_theme;
2047
2048
		$settings['actual_theme_url'] = $settings['theme_url'];
2049
		$settings['actual_images_url'] = $settings['images_url'];
2050
		$settings['actual_theme_dir'] = $settings['theme_dir'];
2051
2052
		$settings['template_dirs'] = array();
2053
		// This theme first.
2054
		$settings['template_dirs'][] = $settings['theme_dir'];
2055
2056
		// Based on theme (if there is one).
2057
		if (!empty($settings['base_theme_dir']))
2058
			$settings['template_dirs'][] = $settings['base_theme_dir'];
2059
2060
		// Lastly the default theme.
2061
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
2062
			$settings['template_dirs'][] = $settings['default_theme_dir'];
2063
	}
2064
2065
	if (!$initialize)
2066
		return;
2067
2068
	// Perhaps we've changed the agreement or privacy policy? Only redirect if:
2069
	// 1. They're not a guest or admin
2070
	// 2. This isn't called from SSI
2071
	// 3. This isn't an XML request
2072
	// 4. They're not trying to do any of the following actions:
2073
	// 4a. View or accept the agreement and/or policy
2074
	// 4b. Login or logout
2075
	// 4c. Get a feed (RSS, ATOM, etc.)
2076
	$agreement_actions = array(
2077
		'agreement' => true,
2078
		'acceptagreement' => true,
2079
		'login2' => true,
2080
		'logintfa' => true,
2081
		'logout' => true,
2082
		'pm' => array('sa' => array('popup')),
2083
		'profile' => array('area' => array('popup', 'alerts_popup')),
2084
		'xmlhttp' => true,
2085
		'.xml' => true,
2086
	);
2087
	if (empty($user_info['is_guest']) && empty($user_info['is_admin']) && SMF != 'SSI' && !isset($_REQUEST['xml']) && !is_filtered_request($agreement_actions, 'action'))
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2088
	{
2089
		require_once($sourcedir . '/Agreement.php');
2090
		$can_accept_agreement = !empty($modSettings['requireAgreement']) && canRequireAgreement();
2091
		$can_accept_privacy_policy = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy();
2092
2093
		if ($can_accept_agreement || $can_accept_privacy_policy)
2094
			redirectexit('action=agreement');
2095
	}
2096
2097
	// Check to see if we're forcing SSL
2098
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2099
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2100
	{
2101
		if (isset($_GET['sslRedirect']))
2102
		{
2103
			loadLanguage('Errors');
2104
			fatal_lang_error('login_ssl_required', false);
2105
		}
2106
2107
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2108
	}
2109
2110
	// Check to see if they're accessing it from the wrong place.
2111
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2112
	{
2113
		$detected_url = httpsOn() ? 'https://' : 'http://';
2114
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2115
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2116
		if ($temp != '/')
2117
			$detected_url .= $temp;
2118
	}
2119
	if (isset($detected_url) && $detected_url != $boardurl)
2120
	{
2121
		// Try #1 - check if it's in a list of alias addresses.
2122
		if (!empty($modSettings['forum_alias_urls']))
2123
		{
2124
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2125
2126
			foreach ($aliases as $alias)
2127
			{
2128
				// Rip off all the boring parts, spaces, etc.
2129
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2130
					$do_fix = true;
2131
			}
2132
		}
2133
2134
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2135
		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...
2136
		{
2137
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2138
			if (empty($_GET))
2139
				redirectexit('wwwRedirect');
2140
			else
2141
			{
2142
				$k = key($_GET);
2143
				$v = current($_GET);
2144
2145
				if ($k != 'wwwRedirect')
2146
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2147
			}
2148
		}
2149
2150
		// #3 is just a check for SSL...
2151
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2152
			$do_fix = true;
2153
2154
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2155
		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...
2156
		{
2157
			// Caching is good ;).
2158
			$oldurl = $boardurl;
2159
2160
			// Fix $boardurl and $scripturl.
2161
			$boardurl = $detected_url;
2162
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2163
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2164
2165
			// Fix the theme urls...
2166
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2167
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2168
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2169
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2170
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2171
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2172
2173
			// And just a few mod settings :).
2174
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2175
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2176
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2177
2178
			// Clean up after loadBoard().
2179
			if (isset($board_info['moderators']))
2180
			{
2181
				foreach ($board_info['moderators'] as $k => $dummy)
2182
				{
2183
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2184
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2185
				}
2186
			}
2187
			foreach ($context['linktree'] as $k => $dummy)
2188
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2189
		}
2190
	}
2191
	// Set up the contextual user array.
2192
	if (!empty($user_info))
2193
	{
2194
		$context['user'] = array(
2195
			'id' => $user_info['id'],
2196
			'is_logged' => !$user_info['is_guest'],
2197
			'is_guest' => &$user_info['is_guest'],
2198
			'is_admin' => &$user_info['is_admin'],
2199
			'is_mod' => &$user_info['is_mod'],
2200
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2201
			'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'])))),
2202
			'name' => $user_info['username'],
2203
			'language' => $user_info['language'],
2204
			'email' => $user_info['email'],
2205
			'ignoreusers' => $user_info['ignoreusers'],
2206
		);
2207
		if (!$context['user']['is_guest'])
2208
			$context['user']['name'] = $user_info['name'];
2209
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2210
			$context['user']['name'] = $txt['guest_title'];
2211
2212
		// Determine the current smiley set.
2213
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2214
		$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'];
2215
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2216
	}
2217
	else
2218
	{
2219
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2220
		$context['user'] = array(
2221
			'id' => -1,
2222
			'is_logged' => false,
2223
			'is_guest' => true,
2224
			'is_mod' => false,
2225
			'can_mod' => false,
2226
			'name' => $txt['guest_title'],
2227
			'language' => $language,
2228
			'email' => '',
2229
			'ignoreusers' => array(),
2230
		);
2231
		// Note we should stuff $user_info with some guest values also...
2232
		$user_info = array(
2233
			'id' => 0,
2234
			'is_guest' => true,
2235
			'is_admin' => false,
2236
			'is_mod' => false,
2237
			'username' => $txt['guest_title'],
2238
			'language' => $language,
2239
			'email' => '',
2240
			'smiley_set' => '',
2241
			'permissions' => array(),
2242
			'groups' => array(),
2243
			'ignoreusers' => array(),
2244
			'possibly_robot' => true,
2245
			'time_offset' => 0,
2246
			'time_format' => $modSettings['time_format'],
2247
		);
2248
	}
2249
2250
	// Some basic information...
2251
	if (!isset($context['html_headers']))
2252
		$context['html_headers'] = '';
2253
	if (!isset($context['javascript_files']))
2254
		$context['javascript_files'] = array();
2255
	if (!isset($context['css_files']))
2256
		$context['css_files'] = array();
2257
	if (!isset($context['css_header']))
2258
		$context['css_header'] = array();
2259
	if (!isset($context['javascript_inline']))
2260
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2261
	if (!isset($context['javascript_vars']))
2262
		$context['javascript_vars'] = array();
2263
2264
	$context['login_url'] = $scripturl . '?action=login2';
2265
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2266
	$context['session_var'] = $_SESSION['session_var'];
2267
	$context['session_id'] = $_SESSION['session_value'];
2268
	$context['forum_name'] = $mbname;
2269
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2270
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2271
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2272
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2273
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2274
	if (isset($modSettings['load_average']))
2275
		$context['load_average'] = $modSettings['load_average'];
2276
2277
	// Detect the browser. This is separated out because it's also used in attachment downloads
2278
	detectBrowser();
2279
2280
	// Set the top level linktree up.
2281
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2282
	if (empty($context['linktree']))
2283
		$context['linktree'] = array();
2284
	array_unshift($context['linktree'], array(
2285
		'url' => $scripturl,
2286
		'name' => $context['forum_name_html_safe']
2287
	));
2288
2289
	// This allows sticking some HTML on the page output - useful for controls.
2290
	$context['insert_after_template'] = '';
2291
2292
	if (!isset($txt))
2293
		$txt = array();
2294
2295
	$simpleActions = array(
2296
		'findmember',
2297
		'helpadmin',
2298
		'printpage',
2299
	);
2300
2301
	// Parent action => array of areas
2302
	$simpleAreas = array(
2303
		'profile' => array('popup', 'alerts_popup',),
2304
	);
2305
2306
	// Parent action => array of subactions
2307
	$simpleSubActions = array(
2308
		'pm' => array('popup',),
2309
		'signup' => array('usernamecheck'),
2310
	);
2311
2312
	// Extra params like ;preview ;js, etc.
2313
	$extraParams = array(
2314
		'preview',
2315
		'splitjs',
2316
	);
2317
2318
	// Actions that specifically uses XML output.
2319
	$xmlActions = array(
2320
		'quotefast',
2321
		'jsmodify',
2322
		'xmlhttp',
2323
		'post2',
2324
		'suggest',
2325
		'stats',
2326
		'notifytopic',
2327
		'notifyboard',
2328
	);
2329
2330
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2331
2332
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2333
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2334
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2335
2336
	// See if theres any extra param to check.
2337
	$requiresXML = false;
2338
	foreach ($extraParams as $key => $extra)
2339
		if (isset($_REQUEST[$extra]))
2340
			$requiresXML = true;
2341
2342
	// Output is fully XML, so no need for the index template.
2343
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2344
	{
2345
		loadLanguage('index+Modifications');
2346
		loadTemplate('Xml');
2347
		$context['template_layers'] = array();
2348
	}
2349
2350
	// These actions don't require the index template at all.
2351
	elseif (!empty($context['simple_action']))
2352
	{
2353
		loadLanguage('index+Modifications');
2354
		$context['template_layers'] = array();
2355
	}
2356
2357
	else
2358
	{
2359
		// Custom templates to load, or just default?
2360
		if (isset($settings['theme_templates']))
2361
			$templates = explode(',', $settings['theme_templates']);
2362
		else
2363
			$templates = array('index');
2364
2365
		// Load each template...
2366
		foreach ($templates as $template)
2367
			loadTemplate($template);
2368
2369
		// ...and attempt to load their associated language files.
2370
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2371
		loadLanguage($required_files, '', false);
2372
2373
		// Custom template layers?
2374
		if (isset($settings['theme_layers']))
2375
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2376
		else
2377
			$context['template_layers'] = array('html', 'body');
2378
	}
2379
2380
	// Initialize the theme.
2381
	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

2381
	loadSubTemplate('init', /** @scrutinizer ignore-type */ 'ignore');
Loading history...
2382
2383
	// Allow overriding the board wide time/number formats.
2384
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2385
		$user_info['time_format'] = $txt['time_format'];
2386
2387
	// Set the character set from the template.
2388
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2389
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2390
2391
	// Guests may still need a name.
2392
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2393
		$context['user']['name'] = $txt['guest_title'];
2394
2395
	// Any theme-related strings that need to be loaded?
2396
	if (!empty($settings['require_theme_strings']))
2397
		loadLanguage('ThemeStrings', '', false);
2398
2399
	// Make a special URL for the language.
2400
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2401
2402
	// And of course, let's load the default CSS file.
2403
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2404
2405
	// Here is my luvly Responsive CSS
2406
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2407
2408
	if ($context['right_to_left'])
2409
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2410
2411
	// We allow theme variants, because we're cool.
2412
	$context['theme_variant'] = '';
2413
	$context['theme_variant_url'] = '';
2414
	if (!empty($settings['theme_variants']))
2415
	{
2416
		// Overriding - for previews and that ilk.
2417
		if (!empty($_REQUEST['variant']))
2418
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2419
		// User selection?
2420
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2421
			$context['theme_variant'] = !empty($_SESSION['id_variant']) && in_array($_SESSION['id_variant'], $settings['theme_variants']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) && in_array($options['theme_variant'], $settings['theme_variants']) ? $options['theme_variant'] : '');
2422
		// If not a user variant, select the default.
2423
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2424
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2425
2426
		// Do this to keep things easier in the templates.
2427
		$context['theme_variant'] = '_' . $context['theme_variant'];
2428
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2429
2430
		if (!empty($context['theme_variant']))
2431
		{
2432
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2433
			if ($context['right_to_left'])
2434
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2435
		}
2436
	}
2437
2438
	// Let's be compatible with old themes!
2439
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2440
		$context['template_layers'] = array('main');
2441
2442
	$context['tabindex'] = 1;
2443
2444
	// Compatibility.
2445
	if (!isset($settings['theme_version']))
2446
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2447
2448
	// Default JS variables for use in every theme
2449
	$context['javascript_vars'] = array(
2450
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2451
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2452
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2453
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2454
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2455
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2456
		'smf_scripturl' => '"' . $scripturl . '"',
2457
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2458
		'smf_charset' => '"' . $context['character_set'] . '"',
2459
		'smf_session_id' => '"' . $context['session_id'] . '"',
2460
		'smf_session_var' => '"' . $context['session_var'] . '"',
2461
		'smf_member_id' => $context['user']['id'],
2462
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2463
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2464
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2465
		'smf_txt_expand' => JavaScriptEscape($txt['code_expand']),
2466
		'smf_txt_shrink' => JavaScriptEscape($txt['code_shrink']),
2467
		'smf_quote_expand' => !empty($modSettings['quote_expand']) ? $modSettings['quote_expand'] : 'false',
2468
		'allow_xhjr_credentials' => !empty($modSettings['allow_cors_credentials']) ? 'true' : 'false',
2469
	);
2470
2471
	// Add the JQuery library to the list of files to load.
2472
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2473
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true, 'seed' => false), 'smf_jquery');
2474
2475
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2476
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2477
2478
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2479
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true, 'seed' => false), 'smf_jquery');
2480
2481
	// Auto loading? template_javascript() will take care of the local half of this.
2482
	else
2483
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true, 'seed' => false), 'smf_jquery');
2484
2485
	// Queue our JQuery plugins!
2486
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2487
	if (!$user_info['is_guest'])
2488
	{
2489
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2490
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2491
	}
2492
2493
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2494
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2495
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2496
2497
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2498
	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())
2499
	{
2500
		if (isBrowser('possibly_robot'))
2501
		{
2502
			// @todo Maybe move this somewhere better?!
2503
			require_once($sourcedir . '/ScheduledTasks.php');
2504
2505
			// What to do, what to do?!
2506
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2507
				AutoTask();
2508
			else
2509
				ReduceMailQueue();
2510
		}
2511
		else
2512
		{
2513
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2514
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2515
2516
			addInlineJavaScript('
2517
		function smfAutoTask()
2518
		{
2519
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2520
		}
2521
		window.setTimeout("smfAutoTask();", 1);');
2522
		}
2523
	}
2524
2525
	// And we should probably trigger the cron too.
2526
	if (empty($modSettings['cron_is_real_cron']))
2527
	{
2528
		$ts = time();
2529
		$ts -= $ts % 15;
2530
		addInlineJavaScript('
2531
	function triggerCron()
2532
	{
2533
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2534
	}
2535
	window.setTimeout(triggerCron, 1);', true);
2536
	}
2537
2538
	// Filter out the restricted boards from the linktree
2539
	if (!$user_info['is_admin'] && !empty($board))
2540
	{
2541
		foreach ($context['linktree'] as $k => $element)
2542
		{
2543
			if (!empty($element['groups']) &&
2544
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2545
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2546
			{
2547
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2548
				$context['linktree'][$k]['extra_before'] = '<i>';
2549
				$context['linktree'][$k]['extra_after'] = '</i>';
2550
				unset($context['linktree'][$k]['url']);
2551
			}
2552
		}
2553
	}
2554
2555
	// Any files to include at this point?
2556
	if (!empty($modSettings['integrate_theme_include']))
2557
	{
2558
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2559
		foreach ($theme_includes as $include)
2560
		{
2561
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2562
			if (file_exists($include))
2563
				require_once($include);
2564
		}
2565
	}
2566
2567
	// Call load theme integration functions.
2568
	call_integration_hook('integrate_load_theme');
2569
2570
	// We are ready to go.
2571
	$context['theme_loaded'] = true;
2572
}
2573
2574
/**
2575
 * Load a template - if the theme doesn't include it, use the default.
2576
 * What this function does:
2577
 *  - loads a template file with the name template_name from the current, default, or base theme.
2578
 *  - detects a wrong default theme directory and tries to work around it.
2579
 *
2580
 * @uses template_include() to include the file.
2581
 * @param string $template_name The name of the template to load
2582
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2583
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2584
 * @return boolean Whether or not the template was loaded
2585
 */
2586
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2587
{
2588
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2589
2590
	// Do any style sheets first, cause we're easy with those.
2591
	if (!empty($style_sheets))
2592
	{
2593
		if (!is_array($style_sheets))
2594
			$style_sheets = array($style_sheets);
2595
2596
		foreach ($style_sheets as $sheet)
2597
			loadCSSFile($sheet . '.css', array(), $sheet);
2598
	}
2599
2600
	// No template to load?
2601
	if ($template_name === false)
0 ignored issues
show
introduced by
The condition $template_name === false is always false.
Loading history...
2602
		return true;
2603
2604
	$loaded = false;
2605
	foreach ($settings['template_dirs'] as $template_dir)
2606
	{
2607
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2608
		{
2609
			$loaded = true;
2610
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2611
			break;
2612
		}
2613
	}
2614
2615
	if ($loaded)
0 ignored issues
show
introduced by
The condition $loaded is always false.
Loading history...
2616
	{
2617
		if ($db_show_debug === true)
2618
			$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 2605. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2619
2620
		// If they have specified an initialization function for this template, go ahead and call it now.
2621
		if (function_exists('template_' . $template_name . '_init'))
2622
			call_user_func('template_' . $template_name . '_init');
2623
	}
2624
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2625
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2626
	{
2627
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2628
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2629
2630
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2631
		{
2632
			loadLanguage('Errors');
2633
			echo '
2634
<div class="alert errorbox">
2635
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2636
</div>';
2637
		}
2638
2639
		loadTemplate($template_name);
2640
	}
2641
	// Cause an error otherwise.
2642
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2643
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2644
	elseif ($fatal)
2645
		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...
2646
	else
2647
		return false;
2648
}
2649
2650
/**
2651
 * Load a sub-template.
2652
 * What it does:
2653
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2654
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2655
 *	for debugging purposes.
2656
 *
2657
 * @todo get rid of reading $_REQUEST directly
2658
 *
2659
 * @param string $sub_template_name The name of the sub-template to load
2660
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2661
 */
2662
function loadSubTemplate($sub_template_name, $fatal = false)
2663
{
2664
	global $context, $txt, $db_show_debug;
2665
2666
	if ($db_show_debug === true)
2667
		$context['debug']['sub_templates'][] = $sub_template_name;
2668
2669
	// Figure out what the template function is named.
2670
	$theme_function = 'template_' . $sub_template_name;
2671
	if (function_exists($theme_function))
2672
		$theme_function();
2673
	elseif ($fatal === false)
2674
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2675
	elseif ($fatal !== 'ignore')
0 ignored issues
show
introduced by
The condition $fatal !== 'ignore' is always true.
Loading history...
2676
		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...
2677
2678
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2679
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2680
	{
2681
		echo '
2682
<div class="noticebox">---- ', $sub_template_name, ' ends ----</div>';
2683
	}
2684
}
2685
2686
/**
2687
 * Add a CSS file for output later
2688
 *
2689
 * @param string $fileName The name of the file to load
2690
 * @param array $params An array of parameters
2691
 * Keys are the following:
2692
 * 	- ['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
2693
 * 	- ['default_theme'] (true/false): force use of default theme url
2694
 * 	- ['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
2695
 *  - ['validate'] (true/false): if true script will validate the local file exists
2696
 *  - ['rtl'] (string): additional file to load in RTL mode
2697
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2698
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2699
 *  - ['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
2700
 *  - ['attributes'] array extra attributes to add to the element
2701
 * @param string $id An ID to stick on the end of the filename for caching purposes
2702
 */
2703
function loadCSSFile($fileName, $params = array(), $id = '')
2704
{
2705
	global $settings, $context, $modSettings, $boardurl;
2706
2707
	if (empty($context['css_files_order']))
2708
		$context['css_files_order'] = array();
2709
2710
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ?
2711
		(array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') :
2712
		(is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2713
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2714
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2715
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2716
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2717
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2718
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2719
2720
	// Account for shorthand like admin.css?alp21 filenames
2721
	$id = (empty($id) ? strtr(str_replace('.css', '', basename($fileName)), '?', '_') : $id) . '_css';
2722
2723
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2724
2725
	// Is this a local file?
2726
	if (empty($params['external']))
2727
	{
2728
		// Are we validating the the file exists?
2729
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/css/' . $fileName)) === false)
2730
		{
2731
			// Maybe the default theme has it?
2732
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/css/' . $fileName) !== false))
2733
			{
2734
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName;
2735
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName;
2736
			}
2737
			else
2738
			{
2739
				$fileUrl = false;
2740
				$filePath = false;
2741
			}
2742
		}
2743
		else
2744
		{
2745
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName;
2746
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName;
2747
			$mtime = @filemtime($filePath);
2748
		}
2749
	}
2750
	// An external file doesn't have a filepath. Mock one for simplicity.
2751
	else
2752
	{
2753
		$fileUrl = $fileName;
2754
		$filePath = $fileName;
2755
2756
		// Always turn these off for external files.
2757
		$params['minimize'] = false;
2758
		$params['seed'] = false;
2759
	}
2760
2761
	$mtime = empty($mtime) ? 0 : $mtime;
2762
2763
	// Add it to the array for use in the template
2764
	if (!empty($fileName) && !empty($fileUrl))
2765
	{
2766
		// find a free number/position
2767
		while (isset($context['css_files_order'][$params['order_pos']]))
2768
			$params['order_pos']++;
2769
		$context['css_files_order'][$params['order_pos']] = $id;
2770
2771
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2772
	}
2773
2774
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2775
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2776
2777
	if ($mtime > $modSettings['browser_cache'])
2778
		updateSettings(array('browser_cache' => $mtime));
2779
}
2780
2781
/**
2782
 * Add a block of inline css code to be executed later
2783
 *
2784
 * - only use this if you have to, generally external css files are better, but for very small changes
2785
 *   or for scripts that require help from PHP/whatever, this can be useful.
2786
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2787
 *
2788
 * @param string $css Some css code
2789
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2790
 */
2791
function addInlineCss($css)
2792
{
2793
	global $context;
2794
2795
	// Gotta add something...
2796
	if (empty($css))
2797
		return false;
2798
2799
	$context['css_header'][] = $css;
2800
}
2801
2802
/**
2803
 * Add a Javascript file for output later
2804
 *
2805
 * @param string $fileName The name of the file to load
2806
 * @param array $params An array of parameter info
2807
 * Keys are the following:
2808
 * 	- ['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
2809
 * 	- ['default_theme'] (true/false): force use of default theme url
2810
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2811
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2812
 *	default theme if not found in the current theme
2813
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2814
 *  - ['validate'] (true/false): if true script will validate the local file exists
2815
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2816
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2817
 *  - ['attributes'] array extra attributes to add to the element
2818
 *
2819
 * @param string $id An ID to stick on the end of the filename
2820
 */
2821
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2822
{
2823
	global $settings, $context, $modSettings, $boardurl;
2824
2825
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ?
2826
		(array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') :
2827
		(is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2828
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2829
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2830
	$params['async'] = isset($params['async']) ? $params['async'] : false;
2831
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2832
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2833
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2834
2835
	// Account for shorthand like admin.js?alp21 filenames
2836
	$id = (empty($id) ? strtr(str_replace('.js', '', basename($fileName)), '?', '_') : $id) . '_js';
2837
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2838
2839
	// Is this a local file?
2840
	if (empty($params['external']))
2841
	{
2842
		// Are we validating it exists on disk?
2843
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/scripts/' . $fileName)) === false)
2844
		{
2845
			// Can't find it in this theme, how about the default?
2846
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/scripts/' . $fileName)) !== false)
2847
			{
2848
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName;
2849
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName;
2850
			}
2851
			else
2852
			{
2853
				$fileUrl = false;
2854
				$filePath = false;
2855
			}
2856
		}
2857
		else
2858
		{
2859
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName;
2860
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName;
2861
			$mtime = @filemtime($filePath);
2862
		}
2863
	}
2864
	// An external file doesn't have a filepath. Mock one for simplicity.
2865
	else
2866
	{
2867
		$fileUrl = $fileName;
2868
		$filePath = $fileName;
2869
2870
		// Always turn these off for external files.
2871
		$params['minimize'] = false;
2872
		$params['seed'] = false;
2873
	}
2874
2875
	$mtime = empty($mtime) ? 0 : $mtime;
2876
2877
	// Add it to the array for use in the template
2878
	if (!empty($fileName) && !empty($fileUrl))
2879
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2880
2881
	if ($mtime > $modSettings['browser_cache'])
2882
		updateSettings(array('browser_cache' => $mtime));
2883
}
2884
2885
/**
2886
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2887
 * Cleaner and easier (for modders) than to use the function below.
2888
 *
2889
 * @param string $key The key for this variable
2890
 * @param string $value The value
2891
 * @param bool $escape Whether or not to escape the value
2892
 */
2893
function addJavaScriptVar($key, $value, $escape = false)
2894
{
2895
	global $context;
2896
2897
	// Variable name must be a valid string.
2898
	if (!is_string($key) || $key === '' || is_numeric($key))
0 ignored issues
show
introduced by
The condition is_string($key) is always true.
Loading history...
2899
		return;
2900
2901
	// Take care of escaping the value for JavaScript?
2902
	if (!empty($escape))
2903
	{
2904
		if (is_null($value))
0 ignored issues
show
introduced by
The condition is_null($value) is always false.
Loading history...
2905
			$value = 'null';
2906
2907
		elseif (is_bool($value))
0 ignored issues
show
introduced by
The condition is_bool($value) is always false.
Loading history...
2908
			$value = var_export($value, true);
2909
2910
		elseif (is_scalar($value))
0 ignored issues
show
introduced by
The condition is_scalar($value) is always true.
Loading history...
2911
			$value = JavaScriptEscape($value);
2912
2913
		elseif (is_array($value))
2914
		{
2915
			$elements = array();
2916
2917
			// We only support one-dimensional arrays.
2918
			foreach ($value as $element)
2919
			{
2920
				if (is_null($element))
2921
					$elements[] = 'null';
2922
2923
				elseif (is_bool($element))
2924
					$elements[] = var_export($element, true);
2925
2926
				elseif (is_scalar($element))
2927
					$elements[] = JavaScriptEscape($element);
2928
			}
2929
2930
			$value = '[' . implode(', ',$elements) . ']';
2931
		}
2932
	}
2933
2934
	// At this point, value should contain suitably escaped JavaScript code.
2935
	// If it obviously doesn't, declare the var with an undefined value.
2936
	if (!is_string($value) && !is_numeric($value))
0 ignored issues
show
introduced by
The condition is_string($value) is always true.
Loading history...
2937
		$value = null;
2938
2939
	$context['javascript_vars'][$key] = $value;
2940
}
2941
2942
/**
2943
 * Add a block of inline Javascript code to be executed later
2944
 *
2945
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2946
 *   or for scripts that require help from PHP/whatever, this can be useful.
2947
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2948
 *
2949
 * @param string $javascript Some JS code
2950
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2951
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2952
 */
2953
function addInlineJavaScript($javascript, $defer = false)
2954
{
2955
	global $context;
2956
2957
	if (empty($javascript))
2958
		return false;
2959
2960
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2961
}
2962
2963
/**
2964
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2965
 *
2966
 * @param string $template_name The name of a template file
2967
 * @param string $lang A specific language to load this file from
2968
 * @param bool $fatal Whether to die with an error if it can't be loaded
2969
 * @param bool $force_reload Whether to load the file again if it's already loaded
2970
 * @return string The language actually loaded.
2971
 */
2972
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2973
{
2974
	global $user_info, $language, $settings, $context, $modSettings;
2975
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2976
	static $already_loaded = array();
2977
2978
	// Default to the user's language.
2979
	if ($lang == '')
2980
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2981
2982
	// Do we want the English version of language file as fallback?
2983
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2984
		loadLanguage($template_name, 'english', false);
2985
2986
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2987
		return $lang;
2988
2989
	// Make sure we have $settings - if not we're in trouble and need to find it!
2990
	if (empty($settings['default_theme_dir']))
2991
	{
2992
		require_once($sourcedir . '/ScheduledTasks.php');
2993
		loadEssentialThemeData();
2994
	}
2995
2996
	// What theme are we in?
2997
	$theme_name = basename($settings['theme_url']);
2998
	if (empty($theme_name))
2999
		$theme_name = 'unknown';
3000
3001
	// For each file open it up and write it out!
3002
	foreach (explode('+', $template_name) as $template)
3003
	{
3004
		// Obviously, the current theme is most important to check.
3005
		$attempts = array(
3006
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
3007
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
3008
		);
3009
3010
		// Do we have a base theme to worry about?
3011
		if (isset($settings['base_theme_dir']))
3012
		{
3013
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
3014
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
3015
		}
3016
3017
		// Fall back on the default theme if necessary.
3018
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
3019
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
3020
3021
		// Fall back on the English language if none of the preferred languages can be found.
3022
		if (!in_array('english', array($lang, $language)))
3023
		{
3024
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
3025
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
3026
		}
3027
3028
		// Try to find the language file.
3029
		$found = false;
3030
		foreach ($attempts as $k => $file)
3031
		{
3032
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
3033
			{
3034
				// Include it!
3035
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
3036
3037
				// Note that we found it.
3038
				$found = true;
3039
3040
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
3041
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
3042
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
3043
3044
				break;
3045
			}
3046
		}
3047
3048
		// That couldn't be found!  Log the error, but *try* to continue normally.
3049
		if (!$found && $fatal)
3050
		{
3051
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
3052
			break;
3053
		}
3054
3055
		// For the sake of backward compatibility
3056
		if (!empty($txt['emails']))
3057
		{
3058
			foreach ($txt['emails'] as $key => $value)
3059
			{
3060
				$txt[$key . '_subject'] = $value['subject'];
3061
				$txt[$key . '_body'] = $value['body'];
3062
			}
3063
			$txt['emails'] = array();
3064
		}
3065
		// For sake of backward compatibility: $birthdayEmails is supposed to be
3066
		// empty in a normal install. If it isn't it means the forum is using
3067
		// something "old" (it may be the translation, it may be a mod) and this
3068
		// code (like the piece above) takes care of converting it to the new format
3069
		if (!empty($birthdayEmails))
3070
		{
3071
			foreach ($birthdayEmails as $key => $value)
3072
			{
3073
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
3074
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
3075
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
3076
			}
3077
			$birthdayEmails = array();
3078
		}
3079
	}
3080
3081
	// Keep track of what we're up to soldier.
3082
	if ($db_show_debug === true)
3083
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
3084
3085
	// Remember what we have loaded, and in which language.
3086
	$already_loaded[$template_name] = $lang;
3087
3088
	// Return the language actually loaded.
3089
	return $lang;
3090
}
3091
3092
/**
3093
 * Get all parent boards (requires first parent as parameter)
3094
 * It finds all the parents of id_parent, and that board itself.
3095
 * Additionally, it detects the moderators of said boards.
3096
 *
3097
 * @param int $id_parent The ID of the parent board
3098
 * @return array An array of information about the boards found.
3099
 */
3100
function getBoardParents($id_parent)
3101
{
3102
	global $scripturl, $smcFunc;
3103
3104
	// First check if we have this cached already.
3105
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
3106
	{
3107
		$boards = array();
3108
		$original_parent = $id_parent;
3109
3110
		// Loop while the parent is non-zero.
3111
		while ($id_parent != 0)
3112
		{
3113
			$result = $smcFunc['db_query']('', '
3114
				SELECT
3115
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
3116
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
3117
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
3118
				FROM {db_prefix}boards AS b
3119
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
3120
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
3121
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
3122
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
3123
				WHERE b.id_board = {int:board_parent}',
3124
				array(
3125
					'board_parent' => $id_parent,
3126
				)
3127
			);
3128
			// In the EXTREMELY unlikely event this happens, give an error message.
3129
			if ($smcFunc['db_num_rows']($result) == 0)
3130
				fatal_lang_error('parent_not_found', 'critical');
3131
			while ($row = $smcFunc['db_fetch_assoc']($result))
3132
			{
3133
				if (!isset($boards[$row['id_board']]))
3134
				{
3135
					$id_parent = $row['id_parent'];
3136
					$boards[$row['id_board']] = array(
3137
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
3138
						'name' => $row['name'],
3139
						'level' => $row['child_level'],
3140
						'groups' => explode(',', $row['member_groups']),
3141
						'deny_groups' => explode(',', $row['deny_member_groups']),
3142
						'moderators' => array(),
3143
						'moderator_groups' => array()
3144
					);
3145
				}
3146
				// If a moderator exists for this board, add that moderator for all children too.
3147
				if (!empty($row['id_moderator']))
3148
					foreach ($boards as $id => $dummy)
3149
					{
3150
						$boards[$id]['moderators'][$row['id_moderator']] = array(
3151
							'id' => $row['id_moderator'],
3152
							'name' => $row['real_name'],
3153
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
3154
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
3155
						);
3156
					}
3157
3158
				// If a moderator group exists for this board, add that moderator group for all children too
3159
				if (!empty($row['id_moderator_group']))
3160
					foreach ($boards as $id => $dummy)
3161
					{
3162
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
3163
							'id' => $row['id_moderator_group'],
3164
							'name' => $row['group_name'],
3165
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
3166
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
3167
						);
3168
					}
3169
			}
3170
			$smcFunc['db_free_result']($result);
3171
		}
3172
3173
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
3174
	}
3175
3176
	return $boards;
3177
}
3178
3179
/**
3180
 * Attempt to reload our known languages.
3181
 * It will try to choose only utf8 or non-utf8 languages.
3182
 *
3183
 * @param bool $use_cache Whether or not to use the cache
3184
 * @return array An array of information about available languages
3185
 */
3186
function getLanguages($use_cache = true)
3187
{
3188
	global $context, $smcFunc, $settings, $modSettings, $cache_enable;
3189
3190
	// Either we don't use the cache, or its expired.
3191
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600)) == null)
3192
	{
3193
		// If we don't have our ucwords function defined yet, let's load the settings data.
3194
		if (empty($smcFunc['ucwords']))
3195
			reloadSettings();
3196
3197
		// If we don't have our theme information yet, let's get it.
3198
		if (empty($settings['default_theme_dir']))
3199
			loadTheme(0, false);
3200
3201
		// Default language directories to try.
3202
		$language_directories = array(
3203
			$settings['default_theme_dir'] . '/languages',
3204
		);
3205
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
3206
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
3207
3208
		// We possibly have a base theme directory.
3209
		if (!empty($settings['base_theme_dir']))
3210
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
3211
3212
		// Remove any duplicates.
3213
		$language_directories = array_unique($language_directories);
3214
3215
		foreach ($language_directories as $language_dir)
3216
		{
3217
			// Can't look in here... doesn't exist!
3218
			if (!file_exists($language_dir))
3219
				continue;
3220
3221
			$dir = dir($language_dir);
3222
			while ($entry = $dir->read())
3223
			{
3224
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
3225
				if (!preg_match('~^index\.((?:.(?!-utf8))+)\.php$~', $entry, $matches))
3226
					continue;
3227
3228
				$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
3229
3230
				if (($spos = strpos($langName, ' ')) !== false)
3231
					$langName = substr($langName, 0, ++$spos) . '(' . substr($langName, $spos) . ')';
3232
3233
				// Get the line we need.
3234
				$fp = @fopen($language_dir . '/' . $entry, 'r');
3235
3236
				// Yay!
3237
				if ($fp)
3238
				{
3239
					while (($line = fgets($fp)) !== false)
3240
					{
3241
						if (strpos($line, '$txt[\'native_name\']') === false)
3242
							continue;
3243
3244
						preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative);
3245
3246
						// Set the language's name.
3247
						if (!empty($matchNative) && !empty($matchNative[1]))
3248
						{
3249
							// Don't mislabel the language if the translator missed this one.
3250
							if ($langName !== 'English' && $matchNative[1] === 'English')
3251
								break;
3252
3253
							$langName = un_htmlspecialchars($matchNative[1]);
3254
							break;
3255
						}
3256
					}
3257
3258
					fclose($fp);
3259
				}
3260
3261
				// Build this language entry.
3262
				$context['languages'][$matches[1]] = array(
3263
					'name' => $langName,
3264
					'selected' => false,
3265
					'filename' => $matches[1],
3266
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
3267
				);
3268
			}
3269
			$dir->close();
3270
		}
3271
3272
		// Avoid confusion when we have more than one English variant installed.
3273
		// Honestly, our default English version should always have been called "English (US)"
3274
		if (substr_count(implode(' ', array_keys($context['languages'])), 'english') > 1 && $context['languages']['english']['name'] === 'English')
3275
			$context['languages']['english']['name'] = 'English (US)';
3276
3277
		// Let's cash in on this deal.
3278
		if (!empty($cache_enable))
3279
			cache_put_data('known_languages', $context['languages'], !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600);
3280
	}
3281
3282
	return $context['languages'];
3283
}
3284
3285
/**
3286
 * Replace all vulgar words with respective proper words. (substring or whole words..)
3287
 * What this function does:
3288
 *  - it censors the passed string.
3289
 *  - if the theme setting allow_no_censored is on, and the theme option
3290
 *	show_no_censored is enabled, does not censor, unless force is also set.
3291
 *  - it caches the list of censored words to reduce parsing.
3292
 *
3293
 * @param string &$text The text to censor
3294
 * @param bool $force Whether to censor the text regardless of settings
3295
 * @return string The censored text
3296
 */
3297
function censorText(&$text, $force = false)
3298
{
3299
	global $modSettings, $options, $txt;
3300
	static $censor_vulgar = null, $censor_proper;
3301
3302
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
3303
		return $text;
3304
3305
	// If they haven't yet been loaded, load them.
3306
	if ($censor_vulgar == null)
3307
	{
3308
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
3309
		$censor_proper = explode("\n", $modSettings['censor_proper']);
3310
3311
		// Quote them for use in regular expressions.
3312
		if (!empty($modSettings['censorWholeWord']))
3313
		{
3314
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3315
3316
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3317
			{
3318
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3319
3320
				// Use the faster \b if we can, or something more complex if we can't
3321
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3322
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3323
3324
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3325
			}
3326
		}
3327
	}
3328
3329
	// Censoring isn't so very complicated :P.
3330
	if (empty($modSettings['censorWholeWord']))
3331
	{
3332
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3333
		$text = $func($censor_vulgar, $censor_proper, $text);
3334
	}
3335
	else
3336
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3337
3338
	return $text;
3339
}
3340
3341
/**
3342
 * Load the template/language file using require
3343
 * 	- loads the template or language file specified by filename.
3344
 * 	- uses eval unless disableTemplateEval is enabled.
3345
 * 	- outputs a parse error if the file did not exist or contained errors.
3346
 * 	- attempts to detect the error and line, and show detailed information.
3347
 *
3348
 * @param string $filename The name of the file to include
3349
 * @param bool $once If true only includes the file once (like include_once)
3350
 */
3351
function template_include($filename, $once = false)
3352
{
3353
	global $context, $txt, $scripturl, $modSettings;
3354
	global $boardurl, $boarddir;
3355
	global $maintenance, $mtitle, $mmessage;
3356
	static $templates = array();
3357
3358
	// We want to be able to figure out any errors...
3359
	@ini_set('track_errors', '1');
3360
3361
	// Don't include the file more than once, if $once is true.
3362
	if ($once && in_array($filename, $templates))
3363
		return;
3364
	// Add this file to the include list, whether $once is true or not.
3365
	else
3366
		$templates[] = $filename;
3367
3368
	$file_found = file_exists($filename);
3369
3370
	if ($once && $file_found)
3371
		require_once($filename);
3372
	elseif ($file_found)
3373
		require($filename);
3374
3375
	if ($file_found !== true)
3376
	{
3377
		ob_end_clean();
3378
		if (!empty($modSettings['enableCompressedOutput']))
3379
			@ob_start('ob_gzhandler');
3380
		else
3381
			ob_start();
3382
3383
		if (isset($_GET['debug']))
3384
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3385
3386
		// Don't cache error pages!!
3387
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3388
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3389
		header('cache-control: no-cache');
3390
3391
		if (!isset($txt['template_parse_error']))
3392
		{
3393
			$txt['template_parse_error'] = 'Template Parse Error!';
3394
			$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>.';
3395
			$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>.';
3396
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3397
		}
3398
3399
		// First, let's get the doctype and language information out of the way.
3400
		echo '<!DOCTYPE html>
3401
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3402
	<head>';
3403
		if (isset($context['character_set']))
3404
			echo '
3405
		<meta charset="', $context['character_set'], '">';
3406
3407
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3408
			echo '
3409
		<title>', $mtitle, '</title>
3410
	</head>
3411
	<body>
3412
		<h3>', $mtitle, '</h3>
3413
		', $mmessage, '
3414
	</body>
3415
</html>';
3416
		elseif (!allowedTo('admin_forum'))
3417
			echo '
3418
		<title>', $txt['template_parse_error'], '</title>
3419
	</head>
3420
	<body>
3421
		<h3>', $txt['template_parse_error'], '</h3>
3422
		', $txt['template_parse_error_message'], '
3423
	</body>
3424
</html>';
3425
		else
3426
		{
3427
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3428
			$error_array = error_get_last();
3429
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3430
				$error = $error_array['message'];
3431
			if (empty($error))
3432
				$error = $txt['template_parse_errmsg'];
3433
3434
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3435
3436
			echo '
3437
		<title>', $txt['template_parse_error'], '</title>
3438
	</head>
3439
	<body>
3440
		<h3>', $txt['template_parse_error'], '</h3>
3441
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')), $boardurl, $scripturl);
3442
3443
			if (!empty($error))
3444
				echo '
3445
		<hr>
3446
3447
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3448
3449
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3450
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3451
			{
3452
				$data = file($filename);
3453
				$data2 = highlight_php_code(implode('', $data));
3454
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3455
3456
				// Fix the PHP code stuff...
3457
				if (!isBrowser('gecko'))
3458
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3459
				else
3460
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3461
3462
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3463
				$j = -1;
3464
				foreach ($data as $line)
3465
				{
3466
					$j++;
3467
3468
					if (substr_count($line, '<br>') == 0)
3469
						continue;
3470
3471
					$n = substr_count($line, '<br>');
3472
					for ($i = 0; $i < $n; $i++)
3473
					{
3474
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3475
						unset($data2[$j + $i + 1]);
3476
					}
3477
					$j += $n;
3478
				}
3479
				$data2 = array_values($data2);
3480
				array_unshift($data2, '');
3481
3482
				echo '
3483
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3484
3485
				// Figure out what the color coding was before...
3486
				$line = max($match[1] - 9, 1);
3487
				$last_line = '';
3488
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3489
					if (strpos($data2[$line2], '<') !== false)
3490
					{
3491
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3492
							$last_line = $color_match[1];
3493
						break;
3494
					}
3495
3496
				// Show the relevant lines...
3497
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3498
				{
3499
					if ($line == $match[1])
3500
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3501
3502
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3503
					if (isset($data2[$line]) && $data2[$line] != '')
3504
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3505
3506
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3507
					{
3508
						$last_line = $color_match[1];
3509
						echo '</', substr($last_line, 1, 4), '>';
3510
					}
3511
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3512
						$last_line = '';
3513
					elseif ($last_line != '' && $data2[$line] != '')
3514
						echo '</', substr($last_line, 1, 4), '>';
3515
3516
					if ($line == $match[1])
3517
						echo '</pre></div><pre style="margin: 0;">';
3518
					else
3519
						echo "\n";
3520
				}
3521
3522
				echo '</pre></div>';
3523
			}
3524
3525
			echo '
3526
	</body>
3527
</html>';
3528
		}
3529
3530
		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...
3531
	}
3532
}
3533
3534
/**
3535
 * Initialize a database connection.
3536
 */
3537
function loadDatabase()
3538
{
3539
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3540
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port, $db_mb4;
3541
3542
	// Figure out what type of database we are using.
3543
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3544
		$db_type = 'mysql';
3545
3546
	// Load the file for the database.
3547
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3548
3549
	$db_options = array();
3550
3551
	// Add in the port if needed
3552
	if (!empty($db_port))
3553
		$db_options['port'] = $db_port;
3554
3555
	if (!empty($db_mb4))
3556
		$db_options['db_mb4'] = $db_mb4;
3557
3558
	// 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.
3559
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3560
	{
3561
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3562
3563
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3564
	}
3565
3566
	// Either we aren't in SSI mode, or it failed.
3567
	if (empty($db_connection))
3568
	{
3569
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3570
3571
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3572
	}
3573
3574
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3575
	if (!$db_connection)
3576
		display_db_error();
3577
3578
	// If in SSI mode fix up the prefix.
3579
	if (SMF == 'SSI')
0 ignored issues
show
introduced by
The condition SMF == 'SSI' is always true.
Loading history...
3580
		db_fix_prefix($db_prefix, $db_name);
3581
}
3582
3583
/**
3584
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3585
 *
3586
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
3587
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3588
 * @return object|false A object of $cacheAPI, or False on failure.
3589
 */
3590
function loadCacheAccelerator($overrideCache = '', $fallbackSMF = true)
3591
{
3592
	global $cacheAPI, $cache_accelerator, $cache_enable;
3593
	global $sourcedir;
3594
3595
	// Is caching enabled?
3596
	if (empty($cache_enable) && empty($overrideCache))
3597
		return false;
3598
3599
	// Not overriding this and we have a cacheAPI, send it back.
3600
	if (empty($overrideCache) && is_object($cacheAPI))
3601
		return $cacheAPI;
3602
3603
	elseif (is_null($cacheAPI))
3604
		$cacheAPI = false;
3605
3606
	require_once($sourcedir . '/Cache/CacheApi.php');
3607
	require_once($sourcedir . '/Cache/CacheApiInterface.php');
3608
3609
	// What accelerator we are going to try.
3610
	$cache_class_name = !empty($cache_accelerator) ? $cache_accelerator : CacheApi::APIS_DEFAULT;
3611
	$fully_qualified_class_name = !empty($overrideCache) ? $overrideCache :
3612
		CacheApi::APIS_NAMESPACE . $cache_class_name;
3613
3614
	// Do some basic tests.
3615
	if (class_exists($fully_qualified_class_name))
3616
	{
3617
		/* @var CacheApiInterface $cache_api */
3618
		$cache_api = new $fully_qualified_class_name();
3619
3620
		// There are rules you know...
3621
		if (!($cache_api instanceof CacheApiInterface) || !($cache_api instanceof CacheApi))
0 ignored issues
show
introduced by
$cache_api is always a sub-type of SMF\Cache\CacheApiInterface.
Loading history...
3622
			return false;
3623
3624
		// No Support?  NEXT!
3625
		if (!$cache_api->isSupported())
3626
		{
3627
			// Can we save ourselves?
3628
			if (!empty($fallbackSMF) && $overrideCache == '' &&
3629
				$cache_class_name !== CacheApi::APIS_DEFAULT)
3630
				return loadCacheAccelerator(CacheApi::APIS_NAMESPACE . CacheApi::APIS_DEFAULT, false);
3631
3632
			return false;
3633
		}
3634
3635
		// Connect up to the accelerator.
3636
		$cache_api->connect();
3637
3638
		// Don't set this if we are overriding the cache.
3639
		if (empty($overrideCache))
3640
			$cacheAPI = $cache_api;
3641
3642
		return $cache_api;
3643
	}
3644
3645
	return false;
3646
}
3647
3648
/**
3649
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3650
 *
3651
 * @param string $key The key for this entry
3652
 * @param string $file The file associated with this entry
3653
 * @param string $function The function to call
3654
 * @param array $params Parameters to be passed to the specified function
3655
 * @param int $level The cache level
3656
 * @return string The cached data
3657
 */
3658
function cache_quick_get($key, $file, $function, $params, $level = 1)
3659
{
3660
	global $modSettings, $sourcedir, $cache_enable;
3661
3662
	if (function_exists('call_integration_hook'))
3663
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3664
3665
	/* Refresh the cache if either:
3666
		1. Caching is disabled.
3667
		2. The cache level isn't high enough.
3668
		3. The item has not been cached or the cached item expired.
3669
		4. The cached item has a custom expiration condition evaluating to true.
3670
		5. The expire time set in the cache item has passed (needed for Zend).
3671
	*/
3672
	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...
3673
	{
3674
		require_once($sourcedir . '/' . $file);
3675
		$cache_block = call_user_func_array($function, $params);
3676
3677
		if (!empty($cache_enable) && $cache_enable >= $level)
3678
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3679
	}
3680
3681
	// Some cached data may need a freshening up after retrieval.
3682
	if (!empty($cache_block['post_retri_eval']))
3683
		eval($cache_block['post_retri_eval']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
3684
3685
	if (function_exists('call_integration_hook'))
3686
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3687
3688
	return $cache_block['data'];
3689
}
3690
3691
/**
3692
 * Puts value in the cache under key for ttl seconds.
3693
 *
3694
 * - It may "miss" so shouldn't be depended on
3695
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3696
 * - It supports:
3697
 *	 memcache: https://php.net/memcache
3698
 *   APCu: https://php.net/book.apcu
3699
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3700
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3701
 *
3702
 * @param string $key A key for this value
3703
 * @param mixed $value The data to cache
3704
 * @param int $ttl How long (in seconds) the data should be cached for
3705
 */
3706
function cache_put_data($key, $value, $ttl = 120)
3707
{
3708
	global $smcFunc, $cache_enable, $cacheAPI;
3709
	global $cache_hits, $cache_count, $db_show_debug;
3710
3711
	if (empty($cache_enable) || empty($cacheAPI))
3712
		return;
3713
3714
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3715
	if (isset($db_show_debug) && $db_show_debug === true)
3716
	{
3717
		$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)));
3718
		$st = microtime(true);
3719
	}
3720
3721
	// The API will handle the rest.
3722
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3723
	$cacheAPI->putData($key, $value, $ttl);
3724
3725
	if (function_exists('call_integration_hook'))
3726
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3727
3728
	if (isset($db_show_debug) && $db_show_debug === true)
3729
		$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...
3730
}
3731
3732
/**
3733
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3734
 * - It may often "miss", so shouldn't be depended on.
3735
 * - It supports the same as cache_put_data().
3736
 *
3737
 * @param string $key The key for the value to retrieve
3738
 * @param int $ttl The maximum age of the cached data
3739
 * @return array|null The cached data or null if nothing was loaded
3740
 */
3741
function cache_get_data($key, $ttl = 120)
3742
{
3743
	global $smcFunc, $cache_enable, $cacheAPI;
3744
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
3745
3746
	if (empty($cache_enable) || empty($cacheAPI))
3747
		return null;
3748
3749
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3750
	if (isset($db_show_debug) && $db_show_debug === true)
3751
	{
3752
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3753
		$st = microtime(true);
3754
		$original_key = $key;
3755
	}
3756
3757
	// Ask the API to get the data.
3758
	$value = $cacheAPI->getData($key, $ttl);
3759
3760
	if (isset($db_show_debug) && $db_show_debug === true)
3761
	{
3762
		$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...
3763
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3764
3765
		if (empty($value))
3766
		{
3767
			if (!is_array($cache_misses))
3768
				$cache_misses = array();
3769
3770
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3771
			$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...
3772
		}
3773
	}
3774
3775
	if (function_exists('call_integration_hook') && isset($value))
3776
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3777
3778
	return empty($value) ? null : (isset($smcFunc['json_decode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3779
}
3780
3781
/**
3782
 * Empty out the cache in use as best it can
3783
 *
3784
 * It may only remove the files of a certain type (if the $type parameter is given)
3785
 * Type can be user, data or left blank
3786
 * 	- user clears out user data
3787
 *  - data clears out system / opcode data
3788
 *  - If no type is specified will perform a complete cache clearing
3789
 * For cache engines that do not distinguish on types, a full cache flush will be done
3790
 *
3791
 * @param string $type The cache type ('memcached', 'zend' or something else for SMF's file cache)
3792
 */
3793
function clean_cache($type = '')
3794
{
3795
	global $cacheAPI;
3796
3797
	// If we can't get to the API, can't do this.
3798
	if (empty($cacheAPI))
3799
		return;
3800
3801
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3802
	$cacheAPI->cleanCache($type);
3803
3804
	call_integration_hook('integrate_clean_cache');
3805
	clearstatcache();
3806
}
3807
3808
/**
3809
 * Helper function to set an array of data for an user's avatar.
3810
 *
3811
 * Makes assumptions based on the data provided, the following keys are required:
3812
 * - avatar The raw "avatar" column in members table
3813
 * - email The user's email. Used to get the gravatar info
3814
 * - filename The attachment filename
3815
 *
3816
 * @param array $data An array of raw info
3817
 * @return array An array of avatar data
3818
 */
3819
function set_avatar_data($data = array())
3820
{
3821
	global $modSettings, $smcFunc, $user_info;
3822
3823
	// Come on!
3824
	if (empty($data))
3825
		return array();
3826
3827
	// Set a nice default var.
3828
	$image = '';
3829
3830
	// Gravatar has been set as mandatory!
3831
	if (!empty($modSettings['gravatarEnabled']) && !empty($modSettings['gravatarOverride']))
3832
	{
3833
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3834
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3835
3836
		elseif (!empty($data['email']))
3837
			$image = get_gravatar_url($data['email']);
3838
	}
3839
3840
	// Look if the user has a gravatar field or has set an external url as avatar.
3841
	else
3842
	{
3843
		// So it's stored in the member table?
3844
		if (!empty($data['avatar']))
3845
		{
3846
			// Gravatar.
3847
			if (stristr($data['avatar'], 'gravatar://'))
3848
			{
3849
				if ($data['avatar'] == 'gravatar://')
3850
					$image = get_gravatar_url($data['email']);
3851
3852
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3853
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3854
			}
3855
3856
			// External url.
3857
			else
3858
				$image = parse_url($data['avatar'], PHP_URL_SCHEME) !== null ? get_proxied_url($data['avatar']) : $modSettings['avatar_url'] . '/' . $data['avatar'];
3859
		}
3860
3861
		// Perhaps this user has an attachment as avatar...
3862
		elseif (!empty($data['filename']))
3863
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3864
3865
		// Right... no avatar... use our default image.
3866
		else
3867
			$image = $modSettings['avatar_url'] . '/default.png';
3868
	}
3869
3870
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3871
3872
	// 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.
3873
	if (!empty($image))
3874
		return array(
3875
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3876
			'image' => '<img class="avatar" src="' . $image . '" alt="">',
3877
			'href' => $image,
3878
			'url' => $image,
3879
		);
3880
3881
	// Fallback to make life easier for everyone...
3882
	else
3883
		return array(
3884
			'name' => '',
3885
			'image' => '',
3886
			'href' => '',
3887
			'url' => '',
3888
		);
3889
}
3890
3891
/**
3892
 * Gets, and if necessary creates, the authentication secret to use for cookies, tokens, etc.
3893
 *
3894
 * Note: Never use the $auth_secret variable directly. Always call this function instead.
3895
 *
3896
 * @return string The authentication secret.
3897
 */
3898
function get_auth_secret()
3899
{
3900
	global $context, $auth_secret, $sourcedir, $boarddir, $smcFunc, $db_last_error, $txt;
3901
3902
	if (empty($auth_secret))
3903
	{
3904
		$auth_secret = bin2hex($smcFunc['random_bytes'](32));
3905
3906
		// It is important to store this in Settings.php, not the database.
3907
		require_once($sourcedir . '/Subs-Admin.php');
3908
3909
		// Did this fail?  If so, we should alert, log and set a static value.
3910
		if (!updateSettingsFile(array('auth_secret' => $auth_secret)))
3911
		{
3912
			$context['auth_secret_missing'] = true;
3913
			$auth_secret = hash_file('sha256', $boarddir . '/Settings.php');
3914
3915
			// Set the last error to now, but only every 15 minutes.  Don't need to flood the logs.
3916
			if (empty($db_last_error) || ($db_last_error + 60*15) <= time())
3917
			{
3918
				updateDbLastError(time());
3919
				loadLanguage('Errors');
3920
				log_error($txt['auth_secret_missing'], 'critical');
3921
			}
3922
		}
3923
	}
3924
3925
3926
	return $auth_secret;
3927
}
3928
3929
?>