Passed
Push — release-2.1 ( 7515da...71b454 )
by Jon
05:28 queued 12s
created

loadMemberCustomFields()   C

Complexity

Conditions 16

Size

Total Lines 78
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 1 Features 0
Metric Value
cc 16
eloc 39
c 1
b 1
f 0
nop 2
dl 0
loc 78
rs 5.5666

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines https://www.simplemachines.org
10
 * @copyright 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}' => tokenTxtReplace($value),
1793
					'{KEY}' => $currentKey,
1794
				));
1795
1796
			$memberContext[$user]['custom_fields'][] = array(
1797
				'title' => tokenTxtReplace(!empty($custom['title']) ? $custom['title'] : $custom['col_name']),
1798
				'col_name' => tokenTxtReplace($custom['col_name']),
1799
				'value' => un_htmlspecialchars(tokenTxtReplace($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 = (array) array_unique($users);
0 ignored issues
show
Bug introduced by
It seems like $users can also be of type integer; however, parameter $array of array_unique() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1829
	$users = (array) array_unique(/** @scrutinizer ignore-type */ $users);
Loading history...
1830
	$params = (array) array_unique($params);
0 ignored issues
show
Bug introduced by
It seems like $params can also be of type string; however, parameter $array of array_unique() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1830
	$params = (array) array_unique(/** @scrutinizer ignore-type */ $params);
Loading history...
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
		$row['field_name'] = tokenTxtReplace($row['field_name']);
1852
		$row['field_desc'] = tokenTxtReplace($row['field_desc']);
1853
1854
		// Create a key => value array for multiple options fields
1855
		if (!empty($row['field_options']))
1856
			foreach (explode(',', $row['field_options']) as $k => $v)
1857
			{
1858
				$fieldOptions[] = $v;
1859
				if (empty($currentKey))
1860
					$currentKey = $v == $row['value'] ? $k : 0;
1861
			}
1862
1863
		// BBC?
1864
		if (!empty($row['bbc']))
1865
			$row['value'] = parse_bbc($row['value']);
1866
1867
		// ... or checkbox?
1868
		elseif (isset($row['type']) && $row['type'] == 'check')
1869
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1870
1871
		// Enclosing the user input within some other text?
1872
		if (!empty($row['enclose']))
1873
			$row['value'] = strtr($row['enclose'], array(
1874
				'{SCRIPTURL}' => $scripturl,
1875
				'{IMAGES_URL}' => $settings['images_url'],
1876
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1877
				'{INPUT}' => un_htmlspecialchars($row['value']),
1878
				'{KEY}' => $currentKey,
1879
			));
1880
1881
		// Send a simple array if there is just 1 param
1882
		if (count($params) == 1)
1883
			$return[$row['id_member']] = $row;
1884
1885
		// More than 1? knock yourself out...
1886
		else
1887
		{
1888
			if (!isset($return[$row['id_member']]))
1889
				$return[$row['id_member']] = array();
1890
1891
			$return[$row['id_member']][$row['variable']] = $row;
1892
		}
1893
	}
1894
1895
	$smcFunc['db_free_result']($request);
1896
1897
	return !empty($return) ? $return : false;
1898
}
1899
1900
/**
1901
 * Loads information about what browser the user is viewing with and places it in $context
1902
 *  - uses the class from {@link Class-BrowserDetect.php}
1903
 */
1904
function detectBrowser()
1905
{
1906
	// Load the current user's browser of choice
1907
	$detector = new browser_detector;
1908
	$detector->detectBrowser();
1909
}
1910
1911
/**
1912
 * Are we using this browser?
1913
 *
1914
 * Wrapper function for detectBrowser
1915
 *
1916
 * @param string $browser The browser we are checking for.
1917
 * @return bool Whether or not the current browser is what we're looking for
1918
 */
1919
function isBrowser($browser)
1920
{
1921
	global $context;
1922
1923
	// Don't know any browser!
1924
	if (empty($context['browser']))
1925
		detectBrowser();
1926
1927
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1928
}
1929
1930
/**
1931
 * Load a theme, by ID.
1932
 *
1933
 * @param int $id_theme The ID of the theme to load
1934
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1935
 */
1936
function loadTheme($id_theme = 0, $initialize = true)
1937
{
1938
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1939
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1940
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1941
1942
	if (empty($id_theme))
1943
	{
1944
		// The theme was specified by the board.
1945
		if (!empty($board_info['theme']))
1946
			$id_theme = $board_info['theme'];
1947
		// The theme is the forum's default.
1948
		else
1949
			$id_theme = $modSettings['theme_guests'];
1950
1951
		// Sometimes the user can choose their own theme.
1952
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1953
		{
1954
			// The theme was specified by REQUEST.
1955
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1956
			{
1957
				$id_theme = (int) $_REQUEST['theme'];
1958
				$_SESSION['id_theme'] = $id_theme;
1959
			}
1960
			// The theme was specified by REQUEST... previously.
1961
			elseif (!empty($_SESSION['id_theme']))
1962
				$id_theme = (int) $_SESSION['id_theme'];
1963
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1964
			elseif (!empty($user_info['theme']))
1965
				$id_theme = $user_info['theme'];
1966
		}
1967
1968
		// Verify the id_theme... no foul play.
1969
		// Always allow the board specific theme, if they are overriding.
1970
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1971
			$id_theme = $board_info['theme'];
1972
		elseif (!empty($modSettings['enableThemes']))
1973
		{
1974
			$themes = explode(',', $modSettings['enableThemes']);
1975
			if (!in_array($id_theme, $themes))
1976
				$id_theme = $modSettings['theme_guests'];
1977
			else
1978
				$id_theme = (int) $id_theme;
1979
		}
1980
	}
1981
1982
	// Allow mod authors the option to override the theme id for custom page themes
1983
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1984
1985
	// We already load the basic stuff?
1986
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1987
	{
1988
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1989
1990
		if (!empty($cache_enable) && $cache_enable >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
1991
		{
1992
			$themeData = $temp;
1993
			$flag = true;
1994
		}
1995
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1996
			$themeData = $temp + array($member => array());
1997
		else
1998
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1999
2000
		if (empty($flag))
2001
		{
2002
			// Load variables from the current or default theme, global or this user's.
2003
			$result = $smcFunc['db_query']('', '
2004
				SELECT variable, value, id_member, id_theme
2005
				FROM {db_prefix}themes
2006
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
2007
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
2008
				ORDER BY id_theme asc',
2009
				array(
2010
					'id_theme' => $id_theme,
2011
					'id_member' => $member,
2012
				)
2013
			);
2014
			// Pick between $settings and $options depending on whose data it is.
2015
			foreach ($smcFunc['db_fetch_all']($result) as $row)
2016
			{
2017
				// There are just things we shouldn't be able to change as members.
2018
				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')))
2019
					continue;
2020
2021
				// If this is the theme_dir of the default theme, store it.
2022
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
2023
					$themeData[0]['default_' . $row['variable']] = $row['value'];
2024
2025
				// If this isn't set yet, is a theme option, or is not the default theme..
2026
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
2027
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
2028
			}
2029
			$smcFunc['db_free_result']($result);
2030
2031
			if (!empty($themeData[-1]))
2032
				foreach ($themeData[-1] as $k => $v)
2033
				{
2034
					if (!isset($themeData[$member][$k]))
2035
						$themeData[$member][$k] = $v;
2036
				}
2037
2038
			if (!empty($cache_enable) && $cache_enable >= 2)
2039
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
2040
			// Only if we didn't already load that part of the cache...
2041
			elseif (!isset($temp))
2042
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
2043
		}
2044
2045
		$settings = $themeData[0];
2046
		$options = $themeData[$member];
2047
2048
		$settings['theme_id'] = $id_theme;
2049
2050
		$settings['actual_theme_url'] = $settings['theme_url'];
2051
		$settings['actual_images_url'] = $settings['images_url'];
2052
		$settings['actual_theme_dir'] = $settings['theme_dir'];
2053
2054
		$settings['template_dirs'] = array();
2055
		// This theme first.
2056
		$settings['template_dirs'][] = $settings['theme_dir'];
2057
2058
		// Based on theme (if there is one).
2059
		if (!empty($settings['base_theme_dir']))
2060
			$settings['template_dirs'][] = $settings['base_theme_dir'];
2061
2062
		// Lastly the default theme.
2063
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
2064
			$settings['template_dirs'][] = $settings['default_theme_dir'];
2065
	}
2066
2067
	if (!$initialize)
2068
		return;
2069
2070
	// Perhaps we've changed the agreement or privacy policy? Only redirect if:
2071
	// 1. They're not a guest or admin
2072
	// 2. This isn't called from SSI
2073
	// 3. This isn't an XML request
2074
	// 4. They're not trying to do any of the following actions:
2075
	// 4a. View or accept the agreement and/or policy
2076
	// 4b. Login or logout
2077
	// 4c. Get a feed (RSS, ATOM, etc.)
2078
	$agreement_actions = array(
2079
		'agreement' => true,
2080
		'acceptagreement' => true,
2081
		'login2' => true,
2082
		'logintfa' => true,
2083
		'logout' => true,
2084
		'pm' => array('sa' => array('popup')),
2085
		'profile' => array('area' => array('popup', 'alerts_popup')),
2086
		'xmlhttp' => true,
2087
		'.xml' => true,
2088
	);
2089
	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...
2090
	{
2091
		require_once($sourcedir . '/Agreement.php');
2092
		$can_accept_agreement = !empty($modSettings['requireAgreement']) && canRequireAgreement();
2093
		$can_accept_privacy_policy = !empty($modSettings['requirePolicyAgreement']) && canRequirePrivacyPolicy();
2094
2095
		if ($can_accept_agreement || $can_accept_privacy_policy)
2096
			redirectexit('action=agreement');
2097
	}
2098
2099
	// Check to see if we're forcing SSL
2100
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2101
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
2102
	{
2103
		if (isset($_GET['sslRedirect']))
2104
		{
2105
			loadLanguage('Errors');
2106
			fatal_lang_error('login_ssl_required', false);
2107
		}
2108
2109
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2110
	}
2111
2112
	// Check to see if they're accessing it from the wrong place.
2113
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2114
	{
2115
		$detected_url = httpsOn() ? 'https://' : 'http://';
2116
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2117
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2118
		if ($temp != '/')
2119
			$detected_url .= $temp;
2120
	}
2121
	if (isset($detected_url) && $detected_url != $boardurl)
2122
	{
2123
		// Try #1 - check if it's in a list of alias addresses.
2124
		if (!empty($modSettings['forum_alias_urls']))
2125
		{
2126
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2127
2128
			foreach ($aliases as $alias)
2129
			{
2130
				// Rip off all the boring parts, spaces, etc.
2131
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2132
					$do_fix = true;
2133
			}
2134
		}
2135
2136
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2137
		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...
2138
		{
2139
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2140
			if (empty($_GET))
2141
				redirectexit('wwwRedirect');
2142
			else
2143
			{
2144
				$k = key($_GET);
2145
				$v = current($_GET);
2146
2147
				if ($k != 'wwwRedirect')
2148
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2149
			}
2150
		}
2151
2152
		// #3 is just a check for SSL...
2153
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2154
			$do_fix = true;
2155
2156
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2157
		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...
2158
		{
2159
			// Caching is good ;).
2160
			$oldurl = $boardurl;
2161
2162
			// Fix $boardurl and $scripturl.
2163
			$boardurl = $detected_url;
2164
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2165
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2166
2167
			// Fix the theme urls...
2168
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2169
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2170
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2171
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2172
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2173
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2174
2175
			// And just a few mod settings :).
2176
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2177
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2178
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2179
2180
			// Clean up after loadBoard().
2181
			if (isset($board_info['moderators']))
2182
			{
2183
				foreach ($board_info['moderators'] as $k => $dummy)
2184
				{
2185
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2186
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2187
				}
2188
			}
2189
			foreach ($context['linktree'] as $k => $dummy)
2190
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2191
		}
2192
	}
2193
	// Set up the contextual user array.
2194
	if (!empty($user_info))
2195
	{
2196
		$context['user'] = array(
2197
			'id' => $user_info['id'],
2198
			'is_logged' => !$user_info['is_guest'],
2199
			'is_guest' => &$user_info['is_guest'],
2200
			'is_admin' => &$user_info['is_admin'],
2201
			'is_mod' => &$user_info['is_mod'],
2202
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2203
			'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'])))),
2204
			'name' => $user_info['username'],
2205
			'language' => $user_info['language'],
2206
			'email' => $user_info['email'],
2207
			'ignoreusers' => $user_info['ignoreusers'],
2208
		);
2209
		if (!$context['user']['is_guest'])
2210
			$context['user']['name'] = $user_info['name'];
2211
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2212
			$context['user']['name'] = $txt['guest_title'];
2213
2214
		// Determine the current smiley set.
2215
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2216
		$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'];
2217
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2218
	}
2219
	else
2220
	{
2221
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2222
		$context['user'] = array(
2223
			'id' => -1,
2224
			'is_logged' => false,
2225
			'is_guest' => true,
2226
			'is_mod' => false,
2227
			'can_mod' => false,
2228
			'name' => $txt['guest_title'],
2229
			'language' => $language,
2230
			'email' => '',
2231
			'ignoreusers' => array(),
2232
		);
2233
		// Note we should stuff $user_info with some guest values also...
2234
		$user_info = array(
2235
			'id' => 0,
2236
			'is_guest' => true,
2237
			'is_admin' => false,
2238
			'is_mod' => false,
2239
			'username' => $txt['guest_title'],
2240
			'language' => $language,
2241
			'email' => '',
2242
			'smiley_set' => '',
2243
			'permissions' => array(),
2244
			'groups' => array(),
2245
			'ignoreusers' => array(),
2246
			'possibly_robot' => true,
2247
			'time_offset' => 0,
2248
			'time_format' => $modSettings['time_format'],
2249
		);
2250
	}
2251
2252
	// Some basic information...
2253
	if (!isset($context['html_headers']))
2254
		$context['html_headers'] = '';
2255
	if (!isset($context['javascript_files']))
2256
		$context['javascript_files'] = array();
2257
	if (!isset($context['css_files']))
2258
		$context['css_files'] = array();
2259
	if (!isset($context['css_header']))
2260
		$context['css_header'] = array();
2261
	if (!isset($context['javascript_inline']))
2262
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2263
	if (!isset($context['javascript_vars']))
2264
		$context['javascript_vars'] = array();
2265
2266
	$context['login_url'] = $scripturl . '?action=login2';
2267
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2268
	$context['session_var'] = $_SESSION['session_var'];
2269
	$context['session_id'] = $_SESSION['session_value'];
2270
	$context['forum_name'] = $mbname;
2271
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2272
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2273
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2274
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2275
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2276
	if (isset($modSettings['load_average']))
2277
		$context['load_average'] = $modSettings['load_average'];
2278
2279
	// Detect the browser. This is separated out because it's also used in attachment downloads
2280
	detectBrowser();
2281
2282
	// Set the top level linktree up.
2283
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2284
	if (empty($context['linktree']))
2285
		$context['linktree'] = array();
2286
	array_unshift($context['linktree'], array(
2287
		'url' => $scripturl,
2288
		'name' => $context['forum_name_html_safe']
2289
	));
2290
2291
	// This allows sticking some HTML on the page output - useful for controls.
2292
	$context['insert_after_template'] = '';
2293
2294
	if (!isset($txt))
2295
		$txt = array();
2296
2297
	$simpleActions = array(
2298
		'findmember',
2299
		'helpadmin',
2300
		'printpage',
2301
	);
2302
2303
	// Parent action => array of areas
2304
	$simpleAreas = array(
2305
		'profile' => array('popup', 'alerts_popup',),
2306
	);
2307
2308
	// Parent action => array of subactions
2309
	$simpleSubActions = array(
2310
		'pm' => array('popup',),
2311
		'signup' => array('usernamecheck'),
2312
	);
2313
2314
	// Extra params like ;preview ;js, etc.
2315
	$extraParams = array(
2316
		'preview',
2317
		'splitjs',
2318
	);
2319
2320
	// Actions that specifically uses XML output.
2321
	$xmlActions = array(
2322
		'quotefast',
2323
		'jsmodify',
2324
		'xmlhttp',
2325
		'post2',
2326
		'suggest',
2327
		'stats',
2328
		'notifytopic',
2329
		'notifyboard',
2330
	);
2331
2332
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2333
2334
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2335
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2336
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2337
2338
	// See if theres any extra param to check.
2339
	$requiresXML = false;
2340
	foreach ($extraParams as $key => $extra)
2341
		if (isset($_REQUEST[$extra]))
2342
			$requiresXML = true;
2343
2344
	// Output is fully XML, so no need for the index template.
2345
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2346
	{
2347
		loadLanguage('index+Modifications');
2348
		loadTemplate('Xml');
2349
		$context['template_layers'] = array();
2350
	}
2351
2352
	// These actions don't require the index template at all.
2353
	elseif (!empty($context['simple_action']))
2354
	{
2355
		loadLanguage('index+Modifications');
2356
		$context['template_layers'] = array();
2357
	}
2358
2359
	else
2360
	{
2361
		// Custom templates to load, or just default?
2362
		if (isset($settings['theme_templates']))
2363
			$templates = explode(',', $settings['theme_templates']);
2364
		else
2365
			$templates = array('index');
2366
2367
		// Load each template...
2368
		foreach ($templates as $template)
2369
			loadTemplate($template);
2370
2371
		// ...and attempt to load their associated language files.
2372
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2373
		loadLanguage($required_files, '', false);
2374
2375
		// Custom template layers?
2376
		if (isset($settings['theme_layers']))
2377
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2378
		else
2379
			$context['template_layers'] = array('html', 'body');
2380
	}
2381
2382
	// Initialize the theme.
2383
	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

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