Issues (1061)

Sources/Load.php (4 issues)

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

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

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

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

Loading history...
177
		{
178
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
179
180
			if (strlen($needle) === 1)
181
			{
182
				$result = array_search($needle, array_slice($haystack_arr, $offset));
183
				return is_int($result) ? $result + $offset : false;
184
			}
185
			else
186
			{
187
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
188
				$needle_size = count($needle_arr);
189
190
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
191
				while ((int) $result === $result)
192
				{
193
					$offset += $result;
194
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
195
						return $offset;
196
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
197
				}
198
				return false;
199
			}
200
		},
201
		'substr' => function($string, $start, $length = null) use ($utf8, $ent_check, $ent_list, $modSettings)
0 ignored issues
show
The import $modSettings is not used and could be removed.

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

Loading history...
202
		{
203
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
204
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
205
		},
206
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
207
		{
208
			if (!function_exists('mb_strtolower'))
209
			{
210
				require_once($sourcedir . '/Subs-Charset.php');
211
				return utf8_strtolower($string);
212
			}
213
214
			return mb_strtolower($string, 'UTF-8');
215
		} : 'strtolower',
216
		'strtoupper' => $utf8 ? function($string)
217
		{
218
			global $sourcedir;
219
220
			if (!function_exists('mb_strtolower'))
221
			{
222
				require_once($sourcedir . '/Subs-Charset.php');
223
				return utf8_strtoupper($string);
224
			}
225
226
			return mb_strtoupper($string, 'UTF-8');
227
		} : 'strtoupper',
228
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
229
		{
230
			$string = $ent_check($string);
231
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
232
			$string = $matches[0];
233
			while (strlen($string) > $length)
234
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
235
			return $string;
236
		},
237
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
238
		{
239
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
240
		} : 'ucfirst',
241
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
242
		{
243
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
244
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
245
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
246
			return implode('', $words);
247
		} : 'ucwords',
248
		'json_decode' => 'smf_json_decode',
249
		'json_encode' => 'json_encode',
250
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
251
		{
252
			global $sourcedir;
253
254
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
255
			if (!is_callable('random_int'))
256
				require_once($sourcedir . '/random_compat/random.php');
257
258
			return random_int($min, $max);
259
		},
260
		'random_bytes' => function($length = 64)
261
		{
262
			global $sourcedir;
263
264
			if (!is_callable('random_bytes'))
265
				require_once($sourcedir . '/random_compat/random.php');
266
267
			// Make sure length is valid
268
			$length = max(1, (int) $length);
269
270
			return random_bytes($length);
271
		},
272
	);
273
274
	// Setting the timezone is a requirement for some functions.
275
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
It seems like timezone_identifiers_list() can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

275
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
276
		date_default_timezone_set($modSettings['default_timezone']);
277
	else
278
	{
279
		// Get PHP's default timezone, if set
280
		$ini_tz = ini_get('date.timezone');
281
		if (!empty($ini_tz))
282
			$modSettings['default_timezone'] = $ini_tz;
283
		else
284
			$modSettings['default_timezone'] = '';
285
286
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
287
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
288
		{
289
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
290
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
291
		}
292
293
		date_default_timezone_set($modSettings['default_timezone']);
294
	}
295
296
	// Check the load averages?
297
	if (!empty($modSettings['loadavg_enable']))
298
	{
299
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
300
		{
301
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
302
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
303
				$modSettings['load_average'] = (float) $matches[1];
304
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
305
				$modSettings['load_average'] = (float) $matches[1];
306
			else
307
				unset($modSettings['load_average']);
308
309
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
310
				cache_put_data('loadavg', $modSettings['load_average'], 90);
311
		}
312
313
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
314
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
315
316
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
317
			display_loadavg_error();
318
	}
319
320
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
321
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
322
323
	// Here to justify the name of this function. :P
324
	// It should be added to the install and upgrade scripts.
325
	// But since the converters need to be updated also. This is easier.
326
	if (empty($modSettings['currentAttachmentUploadDir']))
327
	{
328
		updateSettings(array(
329
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
330
			'currentAttachmentUploadDir' => 1,
331
		));
332
	}
333
334
	// Respect PHP's limits.
335
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
336
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
337
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
338
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
339
340
	// Integration is cool.
341
	if (defined('SMF_INTEGRATION_SETTINGS'))
342
	{
343
		$integration_settings = $smcFunc['json_decode'](SMF_INTEGRATION_SETTINGS, true);
344
		foreach ($integration_settings as $hook => $function)
345
			add_integration_function($hook, $function, false);
346
	}
347
348
	// Any files to pre include?
349
	if (!empty($modSettings['integrate_pre_include']))
350
	{
351
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
352
		foreach ($pre_includes as $include)
353
		{
354
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
355
			if (file_exists($include))
356
				require_once($include);
357
		}
358
	}
359
360
	// This determines the server... not used in many places, except for login fixing.
361
	$context['server'] = array(
362
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
363
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
364
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
365
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
366
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
367
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
368
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
369
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
370
	);
371
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
372
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
373
374
	// Define a list of icons used across multiple places.
375
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
376
377
	// Define an array for custom profile fields placements.
378
	$context['cust_profile_fields_placement'] = array(
379
		'standard',
380
		'icons',
381
		'above_signature',
382
		'below_signature',
383
		'below_avatar',
384
		'above_member',
385
		'bottom_poster',
386
		'before_member',
387
		'after_member',
388
	);
389
390
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
391
	$context['meta_tags'] = array();
392
393
	// Define an array of allowed HTML tags.
394
	$context['allowed_html_tags'] = array(
395
		'<img>',
396
		'<div>',
397
	);
398
399
	// These are the only valid image types for SMF attachments, by default anyway.
400
	// Note: The values are for image mime types, not file extensions.
401
	$context['valid_image_types'] = array(
402
		IMAGETYPE_GIF => 'gif',
403
		IMAGETYPE_JPEG => 'jpeg',
404
		IMAGETYPE_PNG => 'png',
405
		IMAGETYPE_PSD => 'psd',
406
		IMAGETYPE_BMP => 'bmp',
407
		IMAGETYPE_TIFF_II => 'tiff',
408
		IMAGETYPE_TIFF_MM => 'tiff',
409
		IMAGETYPE_IFF => 'iff'
410
	);
411
412
	// Define a list of allowed tags for descriptions.
413
	$context['description_allowed_tags'] = array(
414
		'abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img',
415
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
416
		'sup', 'table', 'td', 'tr', 'u', 'url',
417
	);
418
419
	// Define a list of deprecated BBC tags
420
	// Even when enabled, they'll only work in old posts and not new ones
421
	$context['legacy_bbc'] = array(
422
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
423
		'green', 'move', 'red', 'shadow', 'tt', 'white',
424
	);
425
426
	// Define a list of BBC tags that require permissions to use
427
	$context['restricted_bbc'] = array(
428
		'html',
429
	);
430
431
	// Call pre load integration functions.
432
	call_integration_hook('integrate_pre_load');
433
}
434
435
/**
436
 * Load all the important user information.
437
 * What it does:
438
 * 	- sets up the $user_info array
439
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
440
 * 	- first checks for cookie or integration validation.
441
 * 	- uses the current session if no integration function or cookie is found.
442
 * 	- checks password length, if member is activated and the login span isn't over.
443
 * 		- if validation fails for the user, $id_member is set to 0.
444
 * 		- updates the last visit time when needed.
445
 */
446
function loadUserSettings()
447
{
448
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
449
	global $cookiename, $user_info, $language, $context, $cache_enable;
450
451
	require_once($sourcedir . '/Subs-Auth.php');
452
453
	// Check first the integration, then the cookie, and last the session.
454
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
455
	{
456
		$id_member = 0;
457
		foreach ($integration_ids as $integration_id)
458
		{
459
			$integration_id = (int) $integration_id;
460
			if ($integration_id > 0)
461
			{
462
				$id_member = $integration_id;
463
				$already_verified = true;
464
				break;
465
			}
466
		}
467
	}
468
	else
469
		$id_member = 0;
470
471
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
472
	{
473
		// First try 2.1 json-format cookie
474
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
475
476
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
477
		if (empty($cookie_data))
478
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
479
480
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
481
482
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
483
484
		// Make sure the cookie is set to the correct domain and path
485
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
486
			setLoginCookie((int) $login_span - time(), $id_member);
487
	}
488
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
489
	{
490
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
491
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
492
493
		if (empty($cookie_data))
494
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
495
496
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
497
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
498
	}
499
500
	// Only load this stuff if the user isn't a guest.
501
	if ($id_member != 0)
502
	{
503
		// Is the member data cached?
504
		if (empty($cache_enable) || $cache_enable < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
505
		{
506
			$request = $smcFunc['db_query']('', '
507
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
508
				FROM {db_prefix}members AS mem
509
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
510
				WHERE mem.id_member = {int:id_member}
511
				LIMIT 1',
512
				array(
513
					'id_member' => $id_member,
514
				)
515
			);
516
			$user_settings = $smcFunc['db_fetch_assoc']($request);
517
			$smcFunc['db_free_result']($request);
518
519
			if (!empty($user_settings['avatar']))
520
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
521
522
			if (!empty($cache_enable) && $cache_enable >= 2)
523
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
524
		}
525
526
		// Did we find 'im?  If not, junk it.
527
		if (!empty($user_settings))
528
		{
529
			// As much as the password should be right, we can assume the integration set things up.
530
			if (!empty($already_verified) && $already_verified === true)
531
				$check = true;
532
			// SHA-512 hash should be 128 characters long.
533
			elseif (strlen($password) == 128)
534
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
535
			else
536
				$check = false;
537
538
			// Wrong password or not activated - either way, you're going nowhere.
539
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
540
		}
541
		else
542
			$id_member = 0;
543
544
		// Check if we are forcing TFA
545
		$force_tfasetup = !empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']) && SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != '.xml');
546
547
		// Don't force TFA on popups
548
		if ($force_tfasetup)
549
		{
550
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
551
				$force_tfasetup = false;
552
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
553
				$force_tfasetup = false;
554
555
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
556
		}
557
558
		// If we no longer have the member maybe they're being all hackey, stop brute force!
559
		if (!$id_member)
560
		{
561
			require_once($sourcedir . '/LogInOut.php');
562
			validatePasswordFlood(
563
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
564
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
565
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
566
				$id_member != 0
567
			);
568
		}
569
		// Validate for Two Factor Authentication
570
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
571
		{
572
			$tfacookie = $cookiename . '_tfa';
573
			$tfasecret = null;
574
575
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
576
577
			if (empty($verified) || !in_array(true, $verified))
578
			{
579
				if (!empty($_COOKIE[$tfacookie]))
580
				{
581
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
582
583
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
584
585
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
586
						$tfasecret = null;
587
				}
588
589
				// They didn't finish logging in before coming here? Then they're no one to us.
590
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
591
				{
592
					setLoginCookie(-3600, $id_member);
593
					$id_member = 0;
594
					$user_settings = array();
595
				}
596
			}
597
		}
598
		// When authenticating their two factor code, make sure to reset their ID for security
599
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
600
		{
601
			$id_member = 0;
602
			$context['tfa_member'] = $user_settings;
603
			$user_settings = array();
604
		}
605
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
606
		elseif ($force_tfasetup)
607
		{
608
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
609
			{
610
				//Build an array of ALL user membergroups.
611
				$full_groups = array($user_settings['id_group']);
612
				if (!empty($user_settings['additional_groups']))
613
				{
614
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
615
					$full_groups = array_unique($full_groups); //duplicates, maybe?
616
				}
617
618
				//Find out if any group requires 2FA
619
				$request = $smcFunc['db_query']('', '
620
					SELECT COUNT(id_group) AS total
621
					FROM {db_prefix}membergroups
622
					WHERE tfa_required = {int:tfa_required}
623
						AND id_group IN ({array_int:full_groups})',
624
					array(
625
						'tfa_required' => 1,
626
						'full_groups' => $full_groups,
627
					)
628
				);
629
				$row = $smcFunc['db_fetch_assoc']($request);
630
				$smcFunc['db_free_result']($request);
631
			}
632
			else
633
				$row['total'] = 1; //simplifies logics in the next "if"
634
635
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
636
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
637
638
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
639
				redirectexit('action=profile;area=tfasetup;forced');
640
		}
641
	}
642
643
	// Found 'im, let's set up the variables.
644
	if ($id_member != 0)
645
	{
646
		// Let's not update the last visit time in these cases...
647
		// 1. SSI doesn't count as visiting the forum.
648
		// 2. RSS feeds and XMLHTTP requests don't count either.
649
		// 3. If it was set within this session, no need to set it again.
650
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
651
		// 5. We're still logging in or authenticating
652
		if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))) && empty($_SESSION['id_msg_last_visit']) && (empty($cache_enable) || ($_SESSION['id_msg_last_visit'] = cache_get_data('user_last_visit-' . $id_member, 5 * 3600)) === null))
653
		{
654
			// @todo can this be cached?
655
			// Do a quick query to make sure this isn't a mistake.
656
			$result = $smcFunc['db_query']('', '
657
				SELECT poster_time
658
				FROM {db_prefix}messages
659
				WHERE id_msg = {int:id_msg}
660
				LIMIT 1',
661
				array(
662
					'id_msg' => $user_settings['id_msg_last_visit'],
663
				)
664
			);
665
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
666
			$smcFunc['db_free_result']($result);
667
668
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
669
670
			// If it was *at least* five hours ago...
671
			if ($visitTime < time() - 5 * 3600)
672
			{
673
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $_SERVER['REMOTE_ADDR'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']));
674
				$user_settings['last_login'] = time();
675
676
				if (!empty($cache_enable) && $cache_enable >= 2)
677
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
678
679
				if (!empty($cache_enable))
680
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
681
			}
682
		}
683
		elseif (empty($_SESSION['id_msg_last_visit']))
684
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
685
686
		$username = $user_settings['member_name'];
687
688
		if (empty($user_settings['additional_groups']))
689
			$user_info = array(
690
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
691
			);
692
693
		else
694
			$user_info = array(
695
				'groups' => array_merge(
696
					array($user_settings['id_group'], $user_settings['id_post_group']),
697
					explode(',', $user_settings['additional_groups'])
698
				)
699
			);
700
701
		// Because history has proven that it is possible for groups to go bad - clean up in case.
702
		$user_info['groups'] = array_map('intval', $user_info['groups']);
703
704
		// This is a logged in user, so definitely not a spider.
705
		$user_info['possibly_robot'] = false;
706
707
		// Figure out the new time offset.
708
		if (!empty($user_settings['timezone']))
709
		{
710
			// Get the offsets from UTC for the server, then for the user.
711
			$tz_system = new DateTimeZone(@date_default_timezone_get());
712
			$tz_user = new DateTimeZone($user_settings['timezone']);
713
			$time_system = new DateTime('now', $tz_system);
714
			$time_user = new DateTime('now', $tz_user);
715
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
716
		}
717
		else
718
		{
719
			// !!! Compatibility.
720
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
721
		}
722
	}
723
	// If the user is a guest, initialize all the critical user settings.
724
	else
725
	{
726
		// This is what a guest's variables should be.
727
		$username = '';
728
		$user_info = array('groups' => array(-1));
729
		$user_settings = array();
730
731
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
732
			$_COOKIE[$cookiename] = '';
733
734
		// Expire the 2FA cookie
735
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
736
		{
737
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
738
739
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
740
741
			if (time() > $exp)
742
			{
743
				$_COOKIE[$cookiename . '_tfa'] = '';
744
				setTFACookie(-3600, 0, '');
745
			}
746
		}
747
748
		// Create a login token if it doesn't exist yet.
749
		if (!isset($_SESSION['token']['post-login']))
750
			createToken('login');
751
		else
752
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
753
754
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
755
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
756
		{
757
			require_once($sourcedir . '/ManageSearchEngines.php');
758
			$user_info['possibly_robot'] = SpiderCheck();
759
		}
760
		elseif (!empty($modSettings['spider_mode']))
761
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
762
		// If we haven't turned on proper spider hunts then have a guess!
763
		else
764
		{
765
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
766
			$user_info['possibly_robot'] = (strpos($_SERVER['HTTP_USER_AGENT'], 'Mozilla') === false && strpos($_SERVER['HTTP_USER_AGENT'], 'Opera') === false) || strpos($ci_user_agent, 'googlebot') !== false || strpos($ci_user_agent, 'slurp') !== false || strpos($ci_user_agent, 'crawl') !== false || strpos($ci_user_agent, 'bingbot') !== false || strpos($ci_user_agent, 'bingpreview') !== false || strpos($ci_user_agent, 'adidxbot') !== false || strpos($ci_user_agent, 'msnbot') !== false;
767
		}
768
769
		// We don't know the offset...
770
		$user_info['time_offset'] = 0;
771
	}
772
773
	// Login Cookie times. Format: time => txt
774
	$context['login_cookie_times'] = array(
775
		3153600 => 'always_logged_in',
776
		60 => 'one_hour',
777
		1440 => 'one_day',
778
		10080 => 'one_week',
779
		43200 => 'one_month',
780
	);
781
782
	// Set up the $user_info array.
783
	$user_info += array(
784
		'id' => $id_member,
785
		'username' => $username,
786
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
787
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
788
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
789
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
790
		'is_guest' => $id_member == 0,
791
		'is_admin' => in_array(1, $user_info['groups']),
792
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
793
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
794
		'ip' => $_SERVER['REMOTE_ADDR'],
795
		'ip2' => $_SERVER['BAN_CHECK_IP'],
796
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
797
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
798
		'avatar' => array(
799
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
800
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
801
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
802
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
803
		),
804
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
805
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
806
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
807
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
808
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
809
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
810
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
811
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
812
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
813
		'permissions' => array(),
814
	);
815
	$user_info['groups'] = array_unique($user_info['groups']);
816
	$user_info['can_manage_boards'] = !empty($user_info['is_admin']) || (!empty($modSettings['board_manager_groups']) && count(array_intersect($user_info['groups'], explode(',', $modSettings['board_manager_groups']))) > 0);
817
818
	// Make sure that the last item in the ignore boards array is valid. If the list was too long it could have an ending comma that could cause problems.
819
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
820
		unset($user_info['ignoreboards'][$tmp]);
821
822
	// Allow the user to change their language.
823
	if (!empty($modSettings['userLanguage']))
824
	{
825
		$languages = getLanguages();
826
827
		// Is it valid?
828
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
829
		{
830
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
831
832
			// Make it permanent for members.
833
			if (!empty($user_info['id']))
834
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
835
			else
836
				$_SESSION['language'] = $user_info['language'];
837
		}
838
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
839
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
840
	}
841
842
	$temp = build_query_board($user_info['id']);
843
	$user_info['query_see_board'] = $temp['query_see_board'];
844
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
845
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
846
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
847
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
848
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
849
850
	call_integration_hook('integrate_user_info');
851
}
852
853
/**
854
 * Load minimal user settings from members table.
855
 * Intended for use by background tasks that need to populate $user_info.
856
 *
857
 * @param int|array $user_ids The users IDs to get the data for.
858
 * @return array
859
 * @throws Exception
860
 */
861
function loadMinUserSettings($user_ids = array())
862
{
863
	global $smcFunc, $modSettings, $language;
864
	static $user_settings_min = array();
865
866
	$user_ids = (array) $user_ids;
867
868
	// Already loaded?
869
	if (!empty($user_ids))
870
		$user_ids = array_diff($user_ids, array_keys($user_settings_min));
871
872
	if (empty($user_ids))
873
		return $user_settings_min;
874
875
	$columns_to_load = array(
876
		'id_member',
877
		'time_offset',
878
		'additional_groups',
879
		'id_group',
880
		'id_post_group',
881
		'lngfile',
882
		'smiley_set',
883
		'timezone',
884
	);
885
886
	call_integration_hook('integrate_load_min_user_settings_columns', array(&$columns_to_load));
887
888
	$request = $smcFunc['db_query']('', '
889
		SELECT {raw:columns}
890
		FROM {db_prefix}members
891
		WHERE id_member IN ({array_int:user_ids})',
892
		array(
893
			'user_ids' => array_map('intval', array_unique($user_ids)),
894
			'columns' => implode(', ', $columns_to_load)
895
		)
896
	);
897
898
	while ($row = $smcFunc['db_fetch_assoc']($request))
899
	{
900
		$user_settings_min[$row['id_member']] = array(
901
			'id' => $row['id_member'],
902
			'language' => (empty($row['lngfile']) || empty($modSettings['userLanguage'])) ? $language : $row['lngfile'],
903
			'is_guest' => false,
904
			'time_format' => empty($row['time_format']) ? $modSettings['time_format'] : $row['time_format'],
905
			'smiley_set' => empty($row['smiley_set']) ? $modSettings['smiley_sets_default'] : $row['smiley_set'],
906
		);
907
908
		if (empty($row['additional_groups']))
909
			$user_settings_min[$row['id_member']]['groups'] = array($row['id_group'], $row['id_post_group']);
910
911
		else
912
			$user_settings_min[$row['id_member']]['groups'] = array_merge(
913
				array($row['id_group'], $row['id_post_group']),
914
				explode(',', $row['additional_groups'])
915
			);
916
917
		$user_settings_min[$row['id_member']]['is_admin'] = in_array(1, $user_settings_min[$row['id_member']]['groups']);
918
919
		if (!empty($row['timezone']))
920
		{
921
			$tz_system = new \DateTimeZone(@date_default_timezone_get());
922
			$tz_user = new \DateTimeZone($row['timezone']);
923
			$time_system = new \DateTime('now', $tz_system);
924
			$time_user = new \DateTime('now', $tz_user);
925
			$user_settings_min[$row['id_member']]['time_offset'] = ($tz_user->getOffset($time_user) -
926
					$tz_system->getOffset($time_system)) / 3600;
927
		}
928
929
		else
930
			$user_settings_min[$row['id_member']]['time_offset'] = empty($row['time_offset']) ? 0 : $row['time_offset'];
931
	}
932
933
	$smcFunc['db_free_result']($request);
934
935
	call_integration_hook('integrate_load_min_user_settings', array(&$user_settings_min));
936
937
	return $user_settings_min;
938
}
939
940
941
/**
942
 * Check for moderators and see if they have access to the board.
943
 * What it does:
944
 * - sets up the $board_info array for current board information.
945
 * - if cache is enabled, the $board_info array is stored in cache.
946
 * - redirects to appropriate post if only message id is requested.
947
 * - is only used when inside a topic or board.
948
 * - determines the local moderators for the board.
949
 * - adds group id 3 if the user is a local moderator for the board they are in.
950
 * - prevents access if user is not in proper group nor a local moderator of the board.
951
 */
952
function loadBoard()
953
{
954
	global $txt, $scripturl, $context, $modSettings;
955
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
956
957
	// Assume they are not a moderator.
958
	$user_info['is_mod'] = false;
959
	$context['user']['is_mod'] = &$user_info['is_mod'];
960
961
	// Start the linktree off empty..
962
	$context['linktree'] = array();
963
964
	// Have they by chance specified a message id but nothing else?
965
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
966
	{
967
		// Make sure the message id is really an int.
968
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
969
970
		// Looking through the message table can be slow, so try using the cache first.
971
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
972
		{
973
			$request = $smcFunc['db_query']('', '
974
				SELECT id_topic
975
				FROM {db_prefix}messages
976
				WHERE id_msg = {int:id_msg}
977
				LIMIT 1',
978
				array(
979
					'id_msg' => $_REQUEST['msg'],
980
				)
981
			);
982
983
			// So did it find anything?
984
			if ($smcFunc['db_num_rows']($request))
985
			{
986
				list ($topic) = $smcFunc['db_fetch_row']($request);
987
				$smcFunc['db_free_result']($request);
988
				// Save save save.
989
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
990
			}
991
		}
992
993
		// Remember redirection is the key to avoiding fallout from your bosses.
994
		if (!empty($topic))
995
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
996
		else
997
		{
998
			loadPermissions();
999
			loadTheme();
1000
			fatal_lang_error('topic_gone', false);
1001
		}
1002
	}
1003
1004
	// Load this board only if it is specified.
1005
	if (empty($board) && empty($topic))
1006
	{
1007
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
1008
		return;
1009
	}
1010
1011
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1012
	{
1013
		// @todo SLOW?
1014
		if (!empty($topic))
1015
			$temp = cache_get_data('topic_board-' . $topic, 120);
1016
		else
1017
			$temp = cache_get_data('board-' . $board, 120);
1018
1019
		if (!empty($temp))
1020
		{
1021
			$board_info = $temp;
1022
			$board = $board_info['id'];
1023
		}
1024
	}
1025
1026
	if (empty($temp))
1027
	{
1028
		$custom_column_selects = [];
1029
		$custom_column_parameters = [
1030
			'current_topic' => $topic,
1031
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
1032
		];
1033
1034
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
1035
1036
		$request = $smcFunc['db_query']('load_board_info', '
1037
			SELECT
1038
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
1039
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
1040
				COALESCE(mem.id_member, 0) AS id_moderator,
1041
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
1042
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
1043
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
1044
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
1045
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
1046
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
1047
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1048
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
1049
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
1050
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
1051
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1052
			WHERE b.id_board = {raw:board_link}',
1053
			$custom_column_parameters
1054
		);
1055
1056
		// If there aren't any, skip.
1057
		if ($smcFunc['db_num_rows']($request) > 0)
1058
		{
1059
			$row = $smcFunc['db_fetch_assoc']($request);
1060
1061
			// Set the current board.
1062
			if (!empty($row['id_board']))
1063
				$board = $row['id_board'];
1064
1065
			// Basic operating information. (globals... :/)
1066
			$board_info = array(
1067
				'id' => $board,
1068
				'moderators' => array(),
1069
				'moderator_groups' => array(),
1070
				'cat' => array(
1071
					'id' => $row['id_cat'],
1072
					'name' => $row['cname']
1073
				),
1074
				'name' => $row['bname'],
1075
				'description' => $row['description'],
1076
				'num_topics' => $row['num_topics'],
1077
				'unapproved_topics' => $row['unapproved_topics'],
1078
				'unapproved_posts' => $row['unapproved_posts'],
1079
				'unapproved_user_topics' => 0,
1080
				'parent_boards' => getBoardParents($row['id_parent']),
1081
				'parent' => $row['id_parent'],
1082
				'child_level' => $row['child_level'],
1083
				'theme' => $row['id_theme'],
1084
				'override_theme' => !empty($row['override_theme']),
1085
				'profile' => $row['id_profile'],
1086
				'redirect' => $row['redirect'],
1087
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
1088
				'posts_count' => empty($row['count_posts']),
1089
				'cur_topic_approved' => empty($topic) || $row['approved'],
1090
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1091
			);
1092
1093
			// Load the membergroups allowed, and check permissions.
1094
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1095
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1096
1097
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1098
1099
			if (!empty($modSettings['board_manager_groups']))
1100
			{
1101
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1102
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1103
			}
1104
1105
			do
1106
			{
1107
				if (!empty($row['id_moderator']))
1108
					$board_info['moderators'][$row['id_moderator']] = array(
1109
						'id' => $row['id_moderator'],
1110
						'name' => $row['real_name'],
1111
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1112
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1113
					);
1114
1115
				if (!empty($row['id_moderator_group']))
1116
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1117
						'id' => $row['id_moderator_group'],
1118
						'name' => $row['group_name'],
1119
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1120
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1121
					);
1122
			}
1123
			while ($row = $smcFunc['db_fetch_assoc']($request));
1124
1125
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1126
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1127
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1128
			{
1129
				// Free the previous result
1130
				$smcFunc['db_free_result']($request);
1131
1132
				// @todo why is this using id_topic?
1133
				// @todo Can this get cached?
1134
				$request = $smcFunc['db_query']('', '
1135
					SELECT COUNT(id_topic)
1136
					FROM {db_prefix}topics
1137
					WHERE id_member_started={int:id_member}
1138
						AND approved = {int:unapproved}
1139
						AND id_board = {int:board}',
1140
					array(
1141
						'id_member' => $user_info['id'],
1142
						'unapproved' => 0,
1143
						'board' => $board,
1144
					)
1145
				);
1146
1147
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1148
			}
1149
1150
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1151
			{
1152
				// @todo SLOW?
1153
				if (!empty($topic))
1154
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1155
				cache_put_data('board-' . $board, $board_info, 120);
1156
			}
1157
		}
1158
		else
1159
		{
1160
			// Otherwise the topic is invalid, there are no moderators, etc.
1161
			$board_info = array(
1162
				'moderators' => array(),
1163
				'moderator_groups' => array(),
1164
				'error' => 'exist'
1165
			);
1166
			$topic = null;
1167
			$board = 0;
1168
		}
1169
		$smcFunc['db_free_result']($request);
1170
	}
1171
1172
	if (!empty($topic))
1173
		$_GET['board'] = (int) $board;
1174
1175
	if (!empty($board))
1176
	{
1177
		// Get this into an array of keys for array_intersect
1178
		$moderator_groups = array_keys($board_info['moderator_groups']);
1179
1180
		// Now check if the user is a moderator.
1181
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1182
1183
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1184
			$board_info['error'] = 'access';
1185
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1186
			$board_info['error'] = 'access';
1187
1188
		// Build up the linktree.
1189
		$context['linktree'] = array_merge(
1190
			$context['linktree'],
1191
			array(array(
1192
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1193
				'name' => $board_info['cat']['name']
1194
			)),
1195
			array_reverse($board_info['parent_boards']),
1196
			array(array(
1197
				'url' => $scripturl . '?board=' . $board . '.0',
1198
				'name' => $board_info['name']
1199
			))
1200
		);
1201
	}
1202
1203
	// Set the template contextual information.
1204
	$context['user']['is_mod'] = &$user_info['is_mod'];
1205
	$context['current_topic'] = $topic;
1206
	$context['current_board'] = $board;
1207
1208
	// No posting in redirection boards!
1209
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1210
		$board_info['error'] = 'post_in_redirect';
1211
1212
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1213
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1214
	{
1215
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1216
		loadPermissions();
1217
		loadTheme();
1218
1219
		$_GET['board'] = '';
1220
		$_GET['topic'] = '';
1221
1222
		// The linktree should not give the game away mate!
1223
		$context['linktree'] = array(
1224
			array(
1225
				'url' => $scripturl,
1226
				'name' => $context['forum_name_html_safe']
1227
			)
1228
		);
1229
1230
		// If it's a prefetching agent or we're requesting an attachment.
1231
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1232
		{
1233
			ob_end_clean();
1234
			send_http_status(403);
1235
			die;
1236
		}
1237
		elseif ($board_info['error'] == 'post_in_redirect')
1238
		{
1239
			// Slightly different error message here...
1240
			fatal_lang_error('cannot_post_redirect', false);
1241
		}
1242
		elseif ($user_info['is_guest'])
1243
		{
1244
			loadLanguage('Errors');
1245
			is_not_guest($txt['topic_gone']);
1246
		}
1247
		else
1248
			fatal_lang_error('topic_gone', false);
1249
	}
1250
1251
	if ($user_info['is_mod'])
1252
		$user_info['groups'][] = 3;
1253
}
1254
1255
/**
1256
 * Load this user's permissions.
1257
 */
1258
function loadPermissions()
1259
{
1260
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1261
1262
	if ($user_info['is_admin'])
1263
	{
1264
		banPermissions();
1265
		return;
1266
	}
1267
1268
	if (!empty($cache_enable))
1269
	{
1270
		$cache_groups = $user_info['groups'];
1271
		asort($cache_groups);
1272
		$cache_groups = implode(',', $cache_groups);
1273
		// If it's a spider then cache it different.
1274
		if ($user_info['possibly_robot'])
1275
			$cache_groups .= '-spider';
1276
1277
		if ($cache_enable >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1278
		{
1279
			list ($user_info['permissions']) = $temp;
1280
			banPermissions();
1281
1282
			return;
1283
		}
1284
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1285
			list ($user_info['permissions'], $removals) = $temp;
1286
	}
1287
1288
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1289
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1290
1291
	if (empty($user_info['permissions']))
1292
	{
1293
		// Get the general permissions.
1294
		$request = $smcFunc['db_query']('', '
1295
			SELECT permission, add_deny
1296
			FROM {db_prefix}permissions
1297
			WHERE id_group IN ({array_int:member_groups})
1298
				' . $spider_restrict,
1299
			array(
1300
				'member_groups' => $user_info['groups'],
1301
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1302
			)
1303
		);
1304
		$removals = array();
1305
		while ($row = $smcFunc['db_fetch_assoc']($request))
1306
		{
1307
			if (empty($row['add_deny']))
1308
				$removals[] = $row['permission'];
1309
			else
1310
				$user_info['permissions'][] = $row['permission'];
1311
		}
1312
		$smcFunc['db_free_result']($request);
1313
1314
		if (isset($cache_groups))
1315
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1316
	}
1317
1318
	// Get the board permissions.
1319
	if (!empty($board))
1320
	{
1321
		// Make sure the board (if any) has been loaded by loadBoard().
1322
		if (!isset($board_info['profile']))
1323
			fatal_lang_error('no_board');
1324
1325
		$request = $smcFunc['db_query']('', '
1326
			SELECT permission, add_deny
1327
			FROM {db_prefix}board_permissions
1328
			WHERE (id_group IN ({array_int:member_groups})
1329
				' . $spider_restrict . ')
1330
				AND id_profile = {int:id_profile}',
1331
			array(
1332
				'member_groups' => $user_info['groups'],
1333
				'id_profile' => $board_info['profile'],
1334
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1335
			)
1336
		);
1337
		while ($row = $smcFunc['db_fetch_assoc']($request))
1338
		{
1339
			if (empty($row['add_deny']))
1340
				$removals[] = $row['permission'];
1341
			else
1342
				$user_info['permissions'][] = $row['permission'];
1343
		}
1344
		$smcFunc['db_free_result']($request);
1345
	}
1346
1347
	// Remove all the permissions they shouldn't have ;).
1348
	if (!empty($modSettings['permission_enable_deny']))
1349
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
1350
1351
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1352
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1353
1354
	// Banned?  Watch, don't touch..
1355
	banPermissions();
1356
1357
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1358
	if (!$user_info['is_guest'])
1359
	{
1360
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1361
		{
1362
			require_once($sourcedir . '/Subs-Auth.php');
1363
			rebuildModCache();
1364
		}
1365
		else
1366
			$user_info['mod_cache'] = $_SESSION['mc'];
1367
1368
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1369
		// For example this drastically simplifies certain changes to the profile area.
1370
		$user_info['permissions'][] = 'is_not_guest';
1371
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1372
		$user_info['permissions'][] = 'profile_view_own';
1373
		if (in_array('profile_view', $user_info['permissions']))
1374
			$user_info['permissions'][] = 'profile_view_any';
1375
	}
1376
}
1377
1378
/**
1379
 * Loads an array of users' data by ID or member_name.
1380
 *
1381
 * @param array|string $users An array of users by id or name or a single username/id
1382
 * @param bool $is_name Whether $users contains names
1383
 * @param string $set What kind of data to load (normal, profile, minimal)
1384
 * @return array The ids of the members loaded
1385
 */
1386
function loadMemberData($users, $is_name = false, $set = 'normal')
1387
{
1388
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1389
	global $user_info, $cache_enable;
1390
1391
	// Can't just look for no users :P.
1392
	if (empty($users))
1393
		return array();
1394
1395
	// Pass the set value
1396
	$context['loadMemberContext_set'] = $set;
1397
1398
	// Make sure it's an array.
1399
	$users = !is_array($users) ? array($users) : array_unique($users);
1400
	$loaded_ids = array();
1401
1402
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1403
	{
1404
		$users = array_values($users);
1405
		for ($i = 0, $n = count($users); $i < $n; $i++)
1406
		{
1407
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1408
			if ($data == null)
1409
				continue;
1410
1411
			$loaded_ids[] = $data['id_member'];
1412
			$user_profile[$data['id_member']] = $data;
1413
			unset($users[$i]);
1414
		}
1415
	}
1416
1417
	// Used by default
1418
	$select_columns = '
1419
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1420
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1421
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1422
			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,
1423
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1424
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1425
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1426
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1427
	$select_tables = '
1428
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1429
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1430
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1431
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1432
1433
	// We add or replace according the the set
1434
	switch ($set)
1435
	{
1436
		case 'normal':
1437
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1438
			break;
1439
		case 'profile':
1440
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1441
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1442
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1443
			break;
1444
		case 'minimal':
1445
			$select_columns = '
1446
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1447
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1448
			$select_tables = '';
1449
			break;
1450
		default:
1451
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1452
	}
1453
1454
	// Allow mods to easily add to the selected member data
1455
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1456
1457
	if (!empty($users))
1458
	{
1459
		// Load the member's data.
1460
		$request = $smcFunc['db_query']('', '
1461
			SELECT' . $select_columns . '
1462
			FROM {db_prefix}members AS mem' . $select_tables . '
1463
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1464
			array(
1465
				'blank_string' => '',
1466
				'users' => $users,
1467
			)
1468
		);
1469
		$new_loaded_ids = array();
1470
		while ($row = $smcFunc['db_fetch_assoc']($request))
1471
		{
1472
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1473
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1474
1475
			// Take care of proxying avatar if required, do this here for maximum reach
1476
			if (!empty($row['avatar']))
1477
				$row['avatar'] = get_proxied_url($row['avatar']);
1478
1479
			// Keep track of the member's normal member group
1480
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1481
1482
			if (isset($row['member_ip']))
1483
				$row['member_ip'] = inet_dtop($row['member_ip']);
1484
			if (isset($row['member_ip2']))
1485
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1486
			$row['id_member'] = (int) $row['id_member'];
1487
			$new_loaded_ids[] = $row['id_member'];
1488
			$loaded_ids[] = $row['id_member'];
1489
			$row['options'] = array();
1490
			$user_profile[$row['id_member']] = $row;
1491
		}
1492
		$smcFunc['db_free_result']($request);
1493
	}
1494
1495
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1496
	{
1497
		$request = $smcFunc['db_query']('', '
1498
			SELECT id_member, variable, value
1499
			FROM {db_prefix}themes
1500
			WHERE id_member IN ({array_int:loaded_ids})',
1501
			array(
1502
				'loaded_ids' => $new_loaded_ids,
1503
			)
1504
		);
1505
		while ($row = $smcFunc['db_fetch_assoc']($request))
1506
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1507
		$smcFunc['db_free_result']($request);
1508
	}
1509
1510
	$additional_mods = array();
1511
1512
	// Are any of these users in groups assigned to moderate this board?
1513
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1514
	{
1515
		foreach ($loaded_ids as $a_member)
1516
		{
1517
			if (!empty($user_profile[$a_member]['additional_groups']))
1518
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1519
			else
1520
				$groups = array($user_profile[$a_member]['id_group']);
1521
1522
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1523
1524
			if (!empty($temp))
1525
			{
1526
				$additional_mods[] = $a_member;
1527
			}
1528
		}
1529
	}
1530
1531
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1532
	{
1533
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1534
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1535
	}
1536
1537
	// Are we loading any moderators?  If so, fix their group data...
1538
	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)
1539
	{
1540
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1541
		{
1542
			$request = $smcFunc['db_query']('', '
1543
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1544
				FROM {db_prefix}membergroups
1545
				WHERE id_group = {int:moderator_group}
1546
				LIMIT 1',
1547
				array(
1548
					'moderator_group' => 3,
1549
				)
1550
			);
1551
			$row = $smcFunc['db_fetch_assoc']($request);
1552
			$smcFunc['db_free_result']($request);
1553
1554
			cache_put_data('moderator_group_info', $row, 480);
1555
		}
1556
1557
		foreach ($temp_mods as $id)
1558
		{
1559
			// By popular demand, don't show admins or global moderators as moderators.
1560
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1561
				$user_profile[$id]['member_group'] = $row['member_group'];
1562
1563
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1564
			if (!empty($row['icons']))
1565
				$user_profile[$id]['icons'] = $row['icons'];
1566
			if (!empty($row['member_group_color']))
1567
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1568
		}
1569
	}
1570
1571
	return $loaded_ids;
1572
}
1573
1574
/**
1575
 * Loads the user's basic values... meant for template/theme usage.
1576
 *
1577
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1578
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1579
 * @return boolean|array  False if the data wasn't loaded or the loaded data.
1580
 * @throws Exception
1581
 */
1582
function loadMemberContext($user, $display_custom_fields = false)
1583
{
1584
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1585
	global $context, $modSettings, $settings, $smcFunc;
1586
	static $dataLoaded = array();
1587
	static $loadedLanguages = array();
1588
1589
	// If this person's data is already loaded, skip it.
1590
	if (isset($dataLoaded[$user]))
1591
		return $dataLoaded[$user];
1592
1593
	// We can't load guests or members not loaded by loadMemberData()!
1594
	if ($user == 0)
1595
		return false;
1596
	if (!isset($user_profile[$user]))
1597
	{
1598
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1599
		return false;
1600
	}
1601
1602
	// Well, it's loaded now anyhow.
1603
	$dataLoaded[$user] = true;
1604
	$profile = $user_profile[$user];
1605
1606
	// Censor everything.
1607
	censorText($profile['signature']);
1608
	censorText($profile['personal_text']);
1609
1610
	// Set things up to be used before hand.
1611
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1612
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1613
1614
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1615
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1616
	// Setup the buddy status here (One whole in_array call saved :P)
1617
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1618
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1619
1620
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1621
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1622
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1623
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1624
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1625
	else //not set, bye bye
1626
		$group_icon_url = '';
1627
1628
	// These minimal values are always loaded
1629
	$memberContext[$user] = array(
1630
		'username' => $profile['member_name'],
1631
		'name' => $profile['real_name'],
1632
		'id' => $profile['id_member'],
1633
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1634
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($modSettings['onlineEnable']) ? 'class="pm_icon"' : '') . '>' . $profile['real_name'] . '</a>',
1635
		'email' => $profile['email_address'],
1636
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1637
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1638
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1639
	);
1640
1641
	// If the set isn't minimal then load the monstrous array.
1642
	if ($context['loadMemberContext_set'] != 'minimal')
1643
	{
1644
		// Go the extra mile and load the user's native language name.
1645
		if (empty($loadedLanguages))
1646
			$loadedLanguages = getLanguages();
1647
1648
		// Figure out the new time offset.
1649
		if (!empty($profile['timezone']))
1650
		{
1651
			// Get the offsets from UTC for the server, then for the user.
1652
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1653
			$tz_user = new DateTimeZone($profile['timezone']);
1654
			$time_system = new DateTime('now', $tz_system);
1655
			$time_user = new DateTime('now', $tz_user);
1656
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1657
		}
1658
1659
		else
1660
		{
1661
			// !!! Compatibility.
1662
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1663
		}
1664
1665
		$memberContext[$user] += array(
1666
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1667
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1668
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</a>',
1669
			'is_buddy' => $profile['buddy'],
1670
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1671
			'buddies' => $buddy_list,
1672
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1673
			'blurb' => $profile['personal_text'],
1674
			'website' => array(
1675
				'title' => $profile['website_title'],
1676
				'url' => $profile['website_url'],
1677
			),
1678
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1679
			'signature' => $profile['signature'],
1680
			'real_posts' => $profile['posts'],
1681
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1682
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1683
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1684
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1685
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1686
			'online' => array(
1687
				'is_online' => $profile['is_online'],
1688
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1689
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1690
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1691
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1692
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1693
			),
1694
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1695
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1696
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1697
			'options' => $profile['options'],
1698
			'is_guest' => false,
1699
			'primary_group' => $profile['primary_group'],
1700
			'group' => $profile['member_group'],
1701
			'group_color' => $profile['member_group_color'],
1702
			'group_id' => $profile['id_group'],
1703
			'post_group' => $profile['post_group'],
1704
			'post_group_color' => $profile['post_group_color'],
1705
			'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]),
1706
			'warning' => $profile['warning'],
1707
			'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' : (''))),
1708
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1709
			'custom_fields' => array(),
1710
		);
1711
	}
1712
1713
	// If the set isn't minimal then load their avatar as well.
1714
	if ($context['loadMemberContext_set'] != 'minimal')
1715
	{
1716
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1717
		{
1718
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1719
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1720
			else
1721
				$image = get_gravatar_url($profile['email_address']);
1722
		}
1723
		else
1724
		{
1725
			// So it's stored in the member table?
1726
			if (!empty($profile['avatar']))
1727
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1728
1729
			elseif (!empty($profile['filename']))
1730
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1731
1732
			// Right... no avatar...use the default one
1733
			else
1734
				$image = $modSettings['avatar_url'] . '/default.png';
1735
		}
1736
1737
		if (!empty($image))
1738
			$memberContext[$user]['avatar'] = array(
1739
				'name' => $profile['avatar'],
1740
				'image' => '<img class="avatar" src="' . $image . '" alt="">',
1741
				'href' => $image,
1742
				'url' => $image,
1743
			);
1744
	}
1745
1746
	// Are we also loading the members custom fields into context?
1747
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1748
	{
1749
		$memberContext[$user]['custom_fields'] = array();
1750
1751
		if (!isset($context['display_fields']))
1752
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1753
1754
		foreach ($context['display_fields'] as $custom)
1755
		{
1756
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1757
				continue;
1758
1759
			$value = $profile['options'][$custom['col_name']];
1760
1761
			$fieldOptions = array();
1762
			$currentKey = 0;
1763
1764
			// Create a key => value array for multiple options fields
1765
			if (!empty($custom['options']))
1766
				foreach ($custom['options'] as $k => $v)
1767
				{
1768
					$fieldOptions[] = $v;
1769
					if (empty($currentKey))
1770
						$currentKey = $v == $value ? $k : 0;
1771
				}
1772
1773
			// BBC?
1774
			if ($custom['bbc'])
1775
				$value = parse_bbc($value);
1776
1777
			// ... or checkbox?
1778
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1779
				$value = $value ? $txt['yes'] : $txt['no'];
1780
1781
			// Enclosing the user input within some other text?
1782
			if (!empty($custom['enclose']))
1783
				$value = strtr($custom['enclose'], array(
1784
					'{SCRIPTURL}' => $scripturl,
1785
					'{IMAGES_URL}' => $settings['images_url'],
1786
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1787
					'{INPUT}' => $value,
1788
					'{KEY}' => $currentKey,
1789
				));
1790
1791
			$memberContext[$user]['custom_fields'][] = array(
1792
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1793
				'col_name' => $custom['col_name'],
1794
				'value' => un_htmlspecialchars($value),
1795
				'raw' => $profile['options'][$custom['col_name']],
1796
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1797
			);
1798
		}
1799
	}
1800
1801
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1802
1803
	return $memberContext[$user];
1804
}
1805
1806
/**
1807
 * Loads the user's custom profile fields
1808
 *
1809
 * @param integer|array $users A single user ID or an array of user IDs
1810
 * @param string|array $params Either a string or an array of strings with profile field names
1811
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1812
 */
1813
function loadMemberCustomFields($users, $params)
1814
{
1815
	global $smcFunc, $txt, $scripturl, $settings;
1816
1817
	// Do not waste my time...
1818
	if (empty($users) || empty($params))
1819
		return false;
1820
1821
	// Make sure it's an array.
1822
	$users = !is_array($users) ? array($users) : array_unique($users);
1823
	$params = !is_array($params) ? array($params) : array_unique($params);
1824
	$return = array();
1825
1826
	$request = $smcFunc['db_query']('', '
1827
		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,
1828
		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
1829
		FROM {db_prefix}themes AS t
1830
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1831
		WHERE id_member IN ({array_int:loaded_ids})
1832
			AND variable IN ({array_string:params})
1833
		ORDER BY field_order',
1834
		array(
1835
			'loaded_ids' => $users,
1836
			'params' => $params,
1837
		)
1838
	);
1839
1840
	while ($row = $smcFunc['db_fetch_assoc']($request))
1841
	{
1842
		$fieldOptions = array();
1843
		$currentKey = 0;
1844
1845
		// Create a key => value array for multiple options fields
1846
		if (!empty($row['field_options']))
1847
			foreach (explode(',', $row['field_options']) as $k => $v)
1848
			{
1849
				$fieldOptions[] = $v;
1850
				if (empty($currentKey))
1851
					$currentKey = $v == $row['value'] ? $k : 0;
1852
			}
1853
1854
		// BBC?
1855
		if (!empty($row['bbc']))
1856
			$row['value'] = parse_bbc($row['value']);
1857
1858
		// ... or checkbox?
1859
		elseif (isset($row['type']) && $row['type'] == 'check')
1860
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1861
1862
		// Enclosing the user input within some other text?
1863
		if (!empty($row['enclose']))
1864
			$row['value'] = strtr($row['enclose'], array(
1865
				'{SCRIPTURL}' => $scripturl,
1866
				'{IMAGES_URL}' => $settings['images_url'],
1867
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1868
				'{INPUT}' => un_htmlspecialchars($row['value']),
1869
				'{KEY}' => $currentKey,
1870
			));
1871
1872
		// Send a simple array if there is just 1 param
1873
		if (count($params) == 1)
1874
			$return[$row['id_member']] = $row;
1875
1876
		// More than 1? knock yourself out...
1877
		else
1878
		{
1879
			if (!isset($return[$row['id_member']]))
1880
				$return[$row['id_member']] = array();
1881
1882
			$return[$row['id_member']][$row['variable']] = $row;
1883
		}
1884
	}
1885
1886
	$smcFunc['db_free_result']($request);
1887
1888
	return !empty($return) ? $return : false;
1889
}
1890
1891
/**
1892
 * Loads information about what browser the user is viewing with and places it in $context
1893
 *  - uses the class from {@link Class-BrowserDetect.php}
1894
 */
1895
function detectBrowser()
1896
{
1897
	// Load the current user's browser of choice
1898
	$detector = new browser_detector;
1899
	$detector->detectBrowser();
1900
}
1901
1902
/**
1903
 * Are we using this browser?
1904
 *
1905
 * Wrapper function for detectBrowser
1906
 *
1907
 * @param string $browser The browser we are checking for.
1908
 * @return bool Whether or not the current browser is what we're looking for
1909
 */
1910
function isBrowser($browser)
1911
{
1912
	global $context;
1913
1914
	// Don't know any browser!
1915
	if (empty($context['browser']))
1916
		detectBrowser();
1917
1918
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1919
}
1920
1921
/**
1922
 * Load a theme, by ID.
1923
 *
1924
 * @param int $id_theme The ID of the theme to load
1925
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1926
 */
1927
function loadTheme($id_theme = 0, $initialize = true)
1928
{
1929
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1930
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1931
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1932
1933
	if (empty($id_theme))
1934
	{
1935
		// The theme was specified by the board.
1936
		if (!empty($board_info['theme']))
1937
			$id_theme = $board_info['theme'];
1938
		// The theme is the forum's default.
1939
		else
1940
			$id_theme = $modSettings['theme_guests'];
1941
1942
		// Sometimes the user can choose their own theme.
1943
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1944
		{
1945
			// The theme was specified by REQUEST.
1946
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1947
			{
1948
				$id_theme = (int) $_REQUEST['theme'];
1949
				$_SESSION['id_theme'] = $id_theme;
1950
			}
1951
			// The theme was specified by REQUEST... previously.
1952
			elseif (!empty($_SESSION['id_theme']))
1953
				$id_theme = (int) $_SESSION['id_theme'];
1954
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1955
			elseif (!empty($user_info['theme']))
1956
				$id_theme = $user_info['theme'];
1957
		}
1958
1959
		// Verify the id_theme... no foul play.
1960
		// Always allow the board specific theme, if they are overriding.
1961
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1962
			$id_theme = $board_info['theme'];
1963
		elseif (!empty($modSettings['enableThemes']))
1964
		{
1965
			$themes = explode(',', $modSettings['enableThemes']);
1966
			if (!in_array($id_theme, $themes))
1967
				$id_theme = $modSettings['theme_guests'];
1968
			else
1969
				$id_theme = (int) $id_theme;
1970
		}
1971
	}
1972
1973
	// Allow mod authors the option to override the theme id for custom page themes
1974
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1975
1976
	// We already load the basic stuff?
1977
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1978
	{
1979
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1980
1981
		if (!empty($cache_enable) && $cache_enable >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
1982
		{
1983
			$themeData = $temp;
1984
			$flag = true;
1985
		}
1986
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1987
			$themeData = $temp + array($member => array());
1988
		else
1989
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1990
1991
		if (empty($flag))
1992
		{
1993
			// Load variables from the current or default theme, global or this user's.
1994
			$result = $smcFunc['db_query']('', '
1995
				SELECT variable, value, id_member, id_theme
1996
				FROM {db_prefix}themes
1997
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1998
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
1999
				ORDER BY id_theme asc',
2000
				array(
2001
					'id_theme' => $id_theme,
2002
					'id_member' => $member,
2003
				)
2004
			);
2005
			// Pick between $settings and $options depending on whose data it is.
2006
			foreach ($smcFunc['db_fetch_all']($result) as $row)
2007
			{
2008
				// There are just things we shouldn't be able to change as members.
2009
				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')))
2010
					continue;
2011
2012
				// If this is the theme_dir of the default theme, store it.
2013
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
2014
					$themeData[0]['default_' . $row['variable']] = $row['value'];
2015
2016
				// If this isn't set yet, is a theme option, or is not the default theme..
2017
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
2018
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
2019
			}
2020
			$smcFunc['db_free_result']($result);
2021
2022
			if (!empty($themeData[-1]))
2023
				foreach ($themeData[-1] as $k => $v)
2024
				{
2025
					if (!isset($themeData[$member][$k]))
2026
						$themeData[$member][$k] = $v;
2027
				}
2028
2029
			if (!empty($cache_enable) && $cache_enable >= 2)
2030
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
2031
			// Only if we didn't already load that part of the cache...
2032
			elseif (!isset($temp))
2033
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
2034
		}
2035
2036
		$settings = $themeData[0];
2037
		$options = $themeData[$member];
2038
2039
		$settings['theme_id'] = $id_theme;
2040
2041
		$settings['actual_theme_url'] = $settings['theme_url'];
2042
		$settings['actual_images_url'] = $settings['images_url'];
2043
		$settings['actual_theme_dir'] = $settings['theme_dir'];
2044
2045
		$settings['template_dirs'] = array();
2046
		// This theme first.
2047
		$settings['template_dirs'][] = $settings['theme_dir'];
2048
2049
		// Based on theme (if there is one).
2050
		if (!empty($settings['base_theme_dir']))
2051
			$settings['template_dirs'][] = $settings['base_theme_dir'];
2052
2053
		// Lastly the default theme.
2054
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
2055
			$settings['template_dirs'][] = $settings['default_theme_dir'];
2056
	}
2057
2058
	if (!$initialize)
2059
		return;
2060
2061
	// Check to see if we're forcing SSL
2062
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
2063
		!httpsOn() && SMF != 'SSI')
2064
	{
2065
		if (isset($_GET['sslRedirect']))
2066
		{
2067
			loadLanguage('Errors');
2068
			fatal_lang_error('login_ssl_required', false);
2069
		}
2070
2071
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
2072
	}
2073
2074
	// Check to see if they're accessing it from the wrong place.
2075
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
2076
	{
2077
		$detected_url = httpsOn() ? 'https://' : 'http://';
2078
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
2079
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
2080
		if ($temp != '/')
2081
			$detected_url .= $temp;
2082
	}
2083
	if (isset($detected_url) && $detected_url != $boardurl)
2084
	{
2085
		// Try #1 - check if it's in a list of alias addresses.
2086
		if (!empty($modSettings['forum_alias_urls']))
2087
		{
2088
			$aliases = explode(',', $modSettings['forum_alias_urls']);
2089
2090
			foreach ($aliases as $alias)
2091
			{
2092
				// Rip off all the boring parts, spaces, etc.
2093
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2094
					$do_fix = true;
2095
			}
2096
		}
2097
2098
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2099
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
2100
		{
2101
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2102
			if (empty($_GET))
2103
				redirectexit('wwwRedirect');
2104
			else
2105
			{
2106
				$k = key($_GET);
2107
				$v = current($_GET);
2108
2109
				if ($k != 'wwwRedirect')
2110
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2111
			}
2112
		}
2113
2114
		// #3 is just a check for SSL...
2115
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2116
			$do_fix = true;
2117
2118
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2119
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
2120
		{
2121
			// Caching is good ;).
2122
			$oldurl = $boardurl;
2123
2124
			// Fix $boardurl and $scripturl.
2125
			$boardurl = $detected_url;
2126
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2127
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2128
2129
			// Fix the theme urls...
2130
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2131
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2132
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2133
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2134
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2135
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2136
2137
			// And just a few mod settings :).
2138
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2139
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2140
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2141
2142
			// Clean up after loadBoard().
2143
			if (isset($board_info['moderators']))
2144
			{
2145
				foreach ($board_info['moderators'] as $k => $dummy)
2146
				{
2147
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2148
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2149
				}
2150
			}
2151
			foreach ($context['linktree'] as $k => $dummy)
2152
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2153
		}
2154
	}
2155
	// Set up the contextual user array.
2156
	if (!empty($user_info))
2157
	{
2158
		$context['user'] = array(
2159
			'id' => $user_info['id'],
2160
			'is_logged' => !$user_info['is_guest'],
2161
			'is_guest' => &$user_info['is_guest'],
2162
			'is_admin' => &$user_info['is_admin'],
2163
			'is_mod' => &$user_info['is_mod'],
2164
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2165
			'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'])))),
2166
			'name' => $user_info['username'],
2167
			'language' => $user_info['language'],
2168
			'email' => $user_info['email'],
2169
			'ignoreusers' => $user_info['ignoreusers'],
2170
		);
2171
		if (!$context['user']['is_guest'])
2172
			$context['user']['name'] = $user_info['name'];
2173
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2174
			$context['user']['name'] = $txt['guest_title'];
2175
2176
		// Determine the current smiley set.
2177
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2178
		$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'];
2179
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2180
	}
2181
	else
2182
	{
2183
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2184
		$context['user'] = array(
2185
			'id' => -1,
2186
			'is_logged' => false,
2187
			'is_guest' => true,
2188
			'is_mod' => false,
2189
			'can_mod' => false,
2190
			'name' => $txt['guest_title'],
2191
			'language' => $language,
2192
			'email' => '',
2193
			'ignoreusers' => array(),
2194
		);
2195
		// Note we should stuff $user_info with some guest values also...
2196
		$user_info = array(
2197
			'id' => 0,
2198
			'is_guest' => true,
2199
			'is_admin' => false,
2200
			'is_mod' => false,
2201
			'username' => $txt['guest_title'],
2202
			'language' => $language,
2203
			'email' => '',
2204
			'smiley_set' => '',
2205
			'permissions' => array(),
2206
			'groups' => array(),
2207
			'ignoreusers' => array(),
2208
			'possibly_robot' => true,
2209
			'time_offset' => 0,
2210
			'time_format' => $modSettings['time_format'],
2211
		);
2212
	}
2213
2214
	// Some basic information...
2215
	if (!isset($context['html_headers']))
2216
		$context['html_headers'] = '';
2217
	if (!isset($context['javascript_files']))
2218
		$context['javascript_files'] = array();
2219
	if (!isset($context['css_files']))
2220
		$context['css_files'] = array();
2221
	if (!isset($context['css_header']))
2222
		$context['css_header'] = array();
2223
	if (!isset($context['javascript_inline']))
2224
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2225
	if (!isset($context['javascript_vars']))
2226
		$context['javascript_vars'] = array();
2227
2228
	$context['login_url'] = $scripturl . '?action=login2';
2229
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2230
	$context['session_var'] = $_SESSION['session_var'];
2231
	$context['session_id'] = $_SESSION['session_value'];
2232
	$context['forum_name'] = $mbname;
2233
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2234
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2235
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2236
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2237
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2238
	if (isset($modSettings['load_average']))
2239
		$context['load_average'] = $modSettings['load_average'];
2240
2241
	// Detect the browser. This is separated out because it's also used in attachment downloads
2242
	detectBrowser();
2243
2244
	// Set the top level linktree up.
2245
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2246
	if (empty($context['linktree']))
2247
		$context['linktree'] = array();
2248
	array_unshift($context['linktree'], array(
2249
		'url' => $scripturl,
2250
		'name' => $context['forum_name_html_safe']
2251
	));
2252
2253
	// This allows sticking some HTML on the page output - useful for controls.
2254
	$context['insert_after_template'] = '';
2255
2256
	if (!isset($txt))
2257
		$txt = array();
2258
2259
	$simpleActions = array(
2260
		'findmember',
2261
		'helpadmin',
2262
		'printpage',
2263
		'spellcheck',
2264
	);
2265
2266
	// Parent action => array of areas
2267
	$simpleAreas = array(
2268
		'profile' => array('popup', 'alerts_popup',),
2269
	);
2270
2271
	// Parent action => array of subactions
2272
	$simpleSubActions = array(
2273
		'pm' => array('popup',),
2274
		'signup' => array('usernamecheck'),
2275
	);
2276
2277
	// Extra params like ;preview ;js, etc.
2278
	$extraParams = array(
2279
		'preview',
2280
		'splitjs',
2281
	);
2282
2283
	// Actions that specifically uses XML output.
2284
	$xmlActions = array(
2285
		'quotefast',
2286
		'jsmodify',
2287
		'xmlhttp',
2288
		'post2',
2289
		'suggest',
2290
		'stats',
2291
		'notifytopic',
2292
		'notifyboard',
2293
	);
2294
2295
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2296
2297
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2298
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2299
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2300
2301
	// See if theres any extra param to check.
2302
	$requiresXML = false;
2303
	foreach ($extraParams as $key => $extra)
2304
		if (isset($_REQUEST[$extra]))
2305
			$requiresXML = true;
2306
2307
	// Output is fully XML, so no need for the index template.
2308
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2309
	{
2310
		loadLanguage('index+Modifications');
2311
		loadTemplate('Xml');
2312
		$context['template_layers'] = array();
2313
	}
2314
2315
	// These actions don't require the index template at all.
2316
	elseif (!empty($context['simple_action']))
2317
	{
2318
		loadLanguage('index+Modifications');
2319
		$context['template_layers'] = array();
2320
	}
2321
2322
	else
2323
	{
2324
		// Custom templates to load, or just default?
2325
		if (isset($settings['theme_templates']))
2326
			$templates = explode(',', $settings['theme_templates']);
2327
		else
2328
			$templates = array('index');
2329
2330
		// Load each template...
2331
		foreach ($templates as $template)
2332
			loadTemplate($template);
2333
2334
		// ...and attempt to load their associated language files.
2335
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2336
		loadLanguage($required_files, '', false);
2337
2338
		// Custom template layers?
2339
		if (isset($settings['theme_layers']))
2340
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2341
		else
2342
			$context['template_layers'] = array('html', 'body');
2343
	}
2344
2345
	// Initialize the theme.
2346
	loadSubTemplate('init', 'ignore');
2347
2348
	// Allow overriding the board wide time/number formats.
2349
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2350
		$user_info['time_format'] = $txt['time_format'];
2351
2352
	// Set the character set from the template.
2353
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2354
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2355
2356
	// Guests may still need a name.
2357
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2358
		$context['user']['name'] = $txt['guest_title'];
2359
2360
	// Any theme-related strings that need to be loaded?
2361
	if (!empty($settings['require_theme_strings']))
2362
		loadLanguage('ThemeStrings', '', false);
2363
2364
	// Make a special URL for the language.
2365
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2366
2367
	// And of course, let's load the default CSS file.
2368
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2369
2370
	// Here is my luvly Responsive CSS
2371
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2372
2373
	if ($context['right_to_left'])
2374
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2375
2376
	// We allow theme variants, because we're cool.
2377
	$context['theme_variant'] = '';
2378
	$context['theme_variant_url'] = '';
2379
	if (!empty($settings['theme_variants']))
2380
	{
2381
		// Overriding - for previews and that ilk.
2382
		if (!empty($_REQUEST['variant']))
2383
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2384
		// User selection?
2385
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2386
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2387
		// If not a user variant, select the default.
2388
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2389
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2390
2391
		// Do this to keep things easier in the templates.
2392
		$context['theme_variant'] = '_' . $context['theme_variant'];
2393
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2394
2395
		if (!empty($context['theme_variant']))
2396
		{
2397
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2398
			if ($context['right_to_left'])
2399
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2400
		}
2401
	}
2402
2403
	// Let's be compatible with old themes!
2404
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2405
		$context['template_layers'] = array('main');
2406
2407
	$context['tabindex'] = 1;
2408
2409
	// Compatibility.
2410
	if (!isset($settings['theme_version']))
2411
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2412
2413
	// Default JS variables for use in every theme
2414
	$context['javascript_vars'] = array(
2415
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2416
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2417
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2418
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2419
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2420
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2421
		'smf_scripturl' => '"' . $scripturl . '"',
2422
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2423
		'smf_charset' => '"' . $context['character_set'] . '"',
2424
		'smf_session_id' => '"' . $context['session_id'] . '"',
2425
		'smf_session_var' => '"' . $context['session_var'] . '"',
2426
		'smf_member_id' => $context['user']['id'],
2427
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2428
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2429
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2430
		'smf_txt_expand' => JavaScriptEscape($txt['code_expand']),
2431
		'smf_txt_shrink' => JavaScriptEscape($txt['code_shrink']),
2432
		'smf_quote_expand' => !empty($modSettings['quote_expand']) ? $modSettings['quote_expand'] : 'false',
2433
	);
2434
2435
	// Add the JQuery library to the list of files to load.
2436
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2437
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2438
2439
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2440
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2441
2442
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2443
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2444
2445
	// Auto loading? template_javascript() will take care of the local half of this.
2446
	else
2447
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2448
2449
	// Queue our JQuery plugins!
2450
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2451
	if (!$user_info['is_guest'])
2452
	{
2453
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2454
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2455
	}
2456
2457
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2458
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2459
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2460
2461
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2462
	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())
2463
	{
2464
		if (isBrowser('possibly_robot'))
2465
		{
2466
			// @todo Maybe move this somewhere better?!
2467
			require_once($sourcedir . '/ScheduledTasks.php');
2468
2469
			// What to do, what to do?!
2470
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2471
				AutoTask();
2472
			else
2473
				ReduceMailQueue();
2474
		}
2475
		else
2476
		{
2477
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2478
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2479
2480
			addInlineJavaScript('
2481
		function smfAutoTask()
2482
		{
2483
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2484
		}
2485
		window.setTimeout("smfAutoTask();", 1);');
2486
		}
2487
	}
2488
2489
	// And we should probably trigger the cron too.
2490
	if (empty($modSettings['cron_is_real_cron']))
2491
	{
2492
		$ts = time();
2493
		$ts -= $ts % 15;
2494
		addInlineJavaScript('
2495
	function triggerCron()
2496
	{
2497
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2498
	}
2499
	window.setTimeout(triggerCron, 1);', true);
2500
	}
2501
2502
	// Filter out the restricted boards from the linktree
2503
	if (!$user_info['is_admin'] && !empty($board))
2504
	{
2505
		foreach ($context['linktree'] as $k => $element)
2506
		{
2507
			if (!empty($element['groups']) &&
2508
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2509
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2510
			{
2511
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2512
				$context['linktree'][$k]['extra_before'] = '<i>';
2513
				$context['linktree'][$k]['extra_after'] = '</i>';
2514
				unset($context['linktree'][$k]['url']);
2515
			}
2516
		}
2517
	}
2518
2519
	// Any files to include at this point?
2520
	if (!empty($modSettings['integrate_theme_include']))
2521
	{
2522
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2523
		foreach ($theme_includes as $include)
2524
		{
2525
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2526
			if (file_exists($include))
2527
				require_once($include);
2528
		}
2529
	}
2530
2531
	// Call load theme integration functions.
2532
	call_integration_hook('integrate_load_theme');
2533
2534
	// We are ready to go.
2535
	$context['theme_loaded'] = true;
2536
}
2537
2538
/**
2539
 * Load a template - if the theme doesn't include it, use the default.
2540
 * What this function does:
2541
 *  - loads a template file with the name template_name from the current, default, or base theme.
2542
 *  - detects a wrong default theme directory and tries to work around it.
2543
 *
2544
 * @uses template_include() to include the file.
2545
 * @param string $template_name The name of the template to load
2546
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2547
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2548
 * @return boolean Whether or not the template was loaded
2549
 */
2550
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2551
{
2552
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2553
2554
	// Do any style sheets first, cause we're easy with those.
2555
	if (!empty($style_sheets))
2556
	{
2557
		if (!is_array($style_sheets))
2558
			$style_sheets = array($style_sheets);
2559
2560
		foreach ($style_sheets as $sheet)
2561
			loadCSSFile($sheet . '.css', array(), $sheet);
2562
	}
2563
2564
	// No template to load?
2565
	if ($template_name === false)
2566
		return true;
2567
2568
	$loaded = false;
2569
	foreach ($settings['template_dirs'] as $template_dir)
2570
	{
2571
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2572
		{
2573
			$loaded = true;
2574
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2575
			break;
2576
		}
2577
	}
2578
2579
	if ($loaded)
2580
	{
2581
		if ($db_show_debug === true)
2582
			$context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')';
2583
2584
		// If they have specified an initialization function for this template, go ahead and call it now.
2585
		if (function_exists('template_' . $template_name . '_init'))
2586
			call_user_func('template_' . $template_name . '_init');
2587
	}
2588
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2589
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2590
	{
2591
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2592
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2593
2594
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2595
		{
2596
			loadLanguage('Errors');
2597
			echo '
2598
<div class="alert errorbox">
2599
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2600
</div>';
2601
		}
2602
2603
		loadTemplate($template_name);
2604
	}
2605
	// Cause an error otherwise.
2606
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2607
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2608
	elseif ($fatal)
2609
		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'));
2610
	else
2611
		return false;
2612
}
2613
2614
/**
2615
 * Load a sub-template.
2616
 * What it does:
2617
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2618
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2619
 *	for debugging purposes.
2620
 *
2621
 * @todo get rid of reading $_REQUEST directly
2622
 *
2623
 * @param string $sub_template_name The name of the sub-template to load
2624
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2625
 */
2626
function loadSubTemplate($sub_template_name, $fatal = false)
2627
{
2628
	global $context, $txt, $db_show_debug;
2629
2630
	if ($db_show_debug === true)
2631
		$context['debug']['sub_templates'][] = $sub_template_name;
2632
2633
	// Figure out what the template function is named.
2634
	$theme_function = 'template_' . $sub_template_name;
2635
	if (function_exists($theme_function))
2636
		$theme_function();
2637
	elseif ($fatal === false)
2638
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2639
	elseif ($fatal !== 'ignore')
2640
		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'));
2641
2642
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2643
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2644
	{
2645
		echo '
2646
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2647
	}
2648
}
2649
2650
/**
2651
 * Add a CSS file for output later
2652
 *
2653
 * @param string $fileName The name of the file to load
2654
 * @param array $params An array of parameters
2655
 * Keys are the following:
2656
 * 	- ['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
2657
 * 	- ['default_theme'] (true/false): force use of default theme url
2658
 * 	- ['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
2659
 *  - ['validate'] (true/false): if true script will validate the local file exists
2660
 *  - ['rtl'] (string): additional file to load in RTL mode
2661
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2662
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2663
 *  - ['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
2664
 *  - ['attributes'] array extra attributes to add to the element
2665
 * @param string $id An ID to stick on the end of the filename for caching purposes
2666
 */
2667
function loadCSSFile($fileName, $params = array(), $id = '')
2668
{
2669
	global $settings, $context, $modSettings;
2670
2671
	if (empty($context['css_files_order']))
2672
		$context['css_files_order'] = array();
2673
2674
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') : (is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2675
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2676
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2677
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2678
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2679
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2680
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2681
2682
	// If this is an external file, automatically set this to false.
2683
	if (!empty($params['external']))
2684
		$params['minimize'] = false;
2685
2686
	// Account for shorthand like admin.css?alp21 filenames
2687
	$id = empty($id) ? strtr(str_replace('.css', '', basename($fileName)), '?', '_') : $id;
2688
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2689
2690
	// Is this a local file?
2691
	if (empty($params['external']))
2692
	{
2693
		// Are we validating the the file exists?
2694
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/css/' . $fileName)) === false)
2695
		{
2696
			// Maybe the default theme has it?
2697
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/css/' . $fileName) !== false))
2698
			{
2699
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName;
2700
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName;
2701
			}
2702
2703
			else
2704
			{
2705
				$fileUrl = false;
2706
				$filePath = false;
2707
			}
2708
		}
2709
2710
		else
2711
		{
2712
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName;
2713
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName;
2714
			$mtime = @filemtime($filePath);
2715
		}
2716
	}
2717
2718
	// An external file doesn't have a filepath. Mock one for simplicity.
2719
	else
2720
	{
2721
		$fileUrl = $fileName;
2722
		$filePath = $fileName;
2723
	}
2724
2725
	$mtime = empty($mtime) ? 0 : $mtime;
2726
2727
	// Add it to the array for use in the template
2728
	if (!empty($fileName))
2729
	{
2730
		// find a free number/position
2731
		while (isset($context['css_files_order'][$params['order_pos']]))
2732
			$params['order_pos']++;
2733
		$context['css_files_order'][$params['order_pos']] = $id;
2734
2735
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2736
	}
2737
2738
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2739
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2740
2741
	if ($mtime > $modSettings['browser_cache'])
2742
		updateSettings(array('browser_cache' => $mtime));
2743
}
2744
2745
/**
2746
 * Add a block of inline css code to be executed later
2747
 *
2748
 * - only use this if you have to, generally external css files are better, but for very small changes
2749
 *   or for scripts that require help from PHP/whatever, this can be useful.
2750
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2751
 *
2752
 * @param string $css Some css code
2753
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2754
 */
2755
function addInlineCss($css)
2756
{
2757
	global $context;
2758
2759
	// Gotta add something...
2760
	if (empty($css))
2761
		return false;
2762
2763
	$context['css_header'][] = $css;
2764
}
2765
2766
/**
2767
 * Add a Javascript file for output later
2768
 *
2769
 * @param string $fileName The name of the file to load
2770
 * @param array $params An array of parameter info
2771
 * Keys are the following:
2772
 * 	- ['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
2773
 * 	- ['default_theme'] (true/false): force use of default theme url
2774
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2775
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2776
 *	default theme if not found in the current theme
2777
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2778
 *  - ['validate'] (true/false): if true script will validate the local file exists
2779
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2780
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2781
 *  - ['attributes'] array extra attributes to add to the element
2782
 *
2783
 * @param string $id An ID to stick on the end of the filename
2784
 */
2785
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2786
{
2787
	global $settings, $context, $modSettings;
2788
2789
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $context) ? $context['browser_cache'] : '') : (is_string($params['seed']) ? '?' . ltrim($params['seed'], '?') : '');
2790
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2791
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2792
	$params['async'] = isset($params['async']) ? $params['async'] : false;
2793
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2794
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2795
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2796
2797
	// If this is an external file, automatically set this to false.
2798
	if (!empty($params['external']))
2799
		$params['minimize'] = false;
2800
2801
	// Account for shorthand like admin.js?alp21 filenames
2802
	$id = empty($id) ? strtr(str_replace('.js', '', basename($fileName)), '?', '_') : $id;
2803
	$fileName = str_replace(pathinfo($fileName, PATHINFO_EXTENSION), strtok(pathinfo($fileName, PATHINFO_EXTENSION), '?'), $fileName);
2804
2805
	// Is this a local file?
2806
	if (empty($params['external']))
2807
	{
2808
		// Are we validating it exists on disk?
2809
		if (!empty($params['validate']) && ($mtime = @filemtime($settings[$themeRef . '_dir'] . '/scripts/' . $fileName)) === false)
2810
		{
2811
			// Can't find it in this theme, how about the default?
2812
			if ($themeRef === 'theme' && !$params['force_current'] && ($mtime = @filemtime($settings['default_theme_dir'] . '/scripts/' . $fileName)) !== false)
2813
			{
2814
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName;
2815
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName;
2816
			}
2817
2818
			else
2819
			{
2820
				$fileUrl = false;
2821
				$filePath = false;
2822
			}
2823
		}
2824
2825
		else
2826
		{
2827
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName;
2828
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName;
2829
			$mtime = @filemtime($filePath);
2830
		}
2831
	}
2832
2833
	// An external file doesn't have a filepath. Mock one for simplicity.
2834
	else
2835
	{
2836
		$fileUrl = $fileName;
2837
		$filePath = $fileName;
2838
	}
2839
2840
	$mtime = empty($mtime) ? 0 : $mtime;
2841
2842
	// Add it to the array for use in the template
2843
	if (!empty($fileName))
2844
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params, 'mtime' => $mtime);
2845
2846
	if ($mtime > $modSettings['browser_cache'])
2847
		updateSettings(array('browser_cache' => $mtime));
2848
}
2849
2850
/**
2851
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2852
 * Cleaner and easier (for modders) than to use the function below.
2853
 *
2854
 * @param string $key The key for this variable
2855
 * @param string $value The value
2856
 * @param bool $escape Whether or not to escape the value
2857
 */
2858
function addJavaScriptVar($key, $value, $escape = false)
2859
{
2860
	global $context;
2861
2862
	if (!empty($key) && (!empty($value) || $value === '0'))
2863
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2864
}
2865
2866
/**
2867
 * Add a block of inline Javascript code to be executed later
2868
 *
2869
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2870
 *   or for scripts that require help from PHP/whatever, this can be useful.
2871
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2872
 *
2873
 * @param string $javascript Some JS code
2874
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2875
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2876
 */
2877
function addInlineJavaScript($javascript, $defer = false)
2878
{
2879
	global $context;
2880
2881
	if (empty($javascript))
2882
		return false;
2883
2884
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2885
}
2886
2887
/**
2888
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2889
 *
2890
 * @param string $template_name The name of a template file
2891
 * @param string $lang A specific language to load this file from
2892
 * @param bool $fatal Whether to die with an error if it can't be loaded
2893
 * @param bool $force_reload Whether to load the file again if it's already loaded
2894
 * @return string The language actually loaded.
2895
 */
2896
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2897
{
2898
	global $user_info, $language, $settings, $context, $modSettings;
2899
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2900
	static $already_loaded = array();
2901
2902
	// Default to the user's language.
2903
	if ($lang == '')
2904
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2905
2906
	// Do we want the English version of language file as fallback?
2907
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2908
		loadLanguage($template_name, 'english', false);
2909
2910
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2911
		return $lang;
2912
2913
	// Make sure we have $settings - if not we're in trouble and need to find it!
2914
	if (empty($settings['default_theme_dir']))
2915
	{
2916
		require_once($sourcedir . '/ScheduledTasks.php');
2917
		loadEssentialThemeData();
2918
	}
2919
2920
	// What theme are we in?
2921
	$theme_name = basename($settings['theme_url']);
2922
	if (empty($theme_name))
2923
		$theme_name = 'unknown';
2924
2925
	// For each file open it up and write it out!
2926
	foreach (explode('+', $template_name) as $template)
2927
	{
2928
		// Obviously, the current theme is most important to check.
2929
		$attempts = array(
2930
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2931
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2932
		);
2933
2934
		// Do we have a base theme to worry about?
2935
		if (isset($settings['base_theme_dir']))
2936
		{
2937
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2938
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2939
		}
2940
2941
		// Fall back on the default theme if necessary.
2942
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2943
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2944
2945
		// Fall back on the English language if none of the preferred languages can be found.
2946
		if (!in_array('english', array($lang, $language)))
2947
		{
2948
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2949
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2950
		}
2951
2952
		// Try to find the language file.
2953
		$found = false;
2954
		foreach ($attempts as $k => $file)
2955
		{
2956
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2957
			{
2958
				// Include it!
2959
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2960
2961
				// Note that we found it.
2962
				$found = true;
2963
2964
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2965
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2966
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2967
2968
				break;
2969
			}
2970
		}
2971
2972
		// That couldn't be found!  Log the error, but *try* to continue normally.
2973
		if (!$found && $fatal)
2974
		{
2975
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2976
			break;
2977
		}
2978
2979
		// For the sake of backward compatibility
2980
		if (!empty($txt['emails']))
2981
		{
2982
			foreach ($txt['emails'] as $key => $value)
2983
			{
2984
				$txt[$key . '_subject'] = $value['subject'];
2985
				$txt[$key . '_body'] = $value['body'];
2986
			}
2987
			$txt['emails'] = array();
2988
		}
2989
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2990
		// empty in a normal install. If it isn't it means the forum is using
2991
		// something "old" (it may be the translation, it may be a mod) and this
2992
		// code (like the piece above) takes care of converting it to the new format
2993
		if (!empty($birthdayEmails))
2994
		{
2995
			foreach ($birthdayEmails as $key => $value)
2996
			{
2997
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2998
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2999
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
3000
			}
3001
			$birthdayEmails = array();
3002
		}
3003
	}
3004
3005
	// Keep track of what we're up to soldier.
3006
	if ($db_show_debug === true)
3007
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
3008
3009
	// Remember what we have loaded, and in which language.
3010
	$already_loaded[$template_name] = $lang;
3011
3012
	// Return the language actually loaded.
3013
	return $lang;
3014
}
3015
3016
/**
3017
 * Get all parent boards (requires first parent as parameter)
3018
 * It finds all the parents of id_parent, and that board itself.
3019
 * Additionally, it detects the moderators of said boards.
3020
 *
3021
 * @param int $id_parent The ID of the parent board
3022
 * @return array An array of information about the boards found.
3023
 */
3024
function getBoardParents($id_parent)
3025
{
3026
	global $scripturl, $smcFunc;
3027
3028
	// First check if we have this cached already.
3029
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
3030
	{
3031
		$boards = array();
3032
		$original_parent = $id_parent;
3033
3034
		// Loop while the parent is non-zero.
3035
		while ($id_parent != 0)
3036
		{
3037
			$result = $smcFunc['db_query']('', '
3038
				SELECT
3039
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
3040
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
3041
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
3042
				FROM {db_prefix}boards AS b
3043
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
3044
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
3045
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
3046
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
3047
				WHERE b.id_board = {int:board_parent}',
3048
				array(
3049
					'board_parent' => $id_parent,
3050
				)
3051
			);
3052
			// In the EXTREMELY unlikely event this happens, give an error message.
3053
			if ($smcFunc['db_num_rows']($result) == 0)
3054
				fatal_lang_error('parent_not_found', 'critical');
3055
			while ($row = $smcFunc['db_fetch_assoc']($result))
3056
			{
3057
				if (!isset($boards[$row['id_board']]))
3058
				{
3059
					$id_parent = $row['id_parent'];
3060
					$boards[$row['id_board']] = array(
3061
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
3062
						'name' => $row['name'],
3063
						'level' => $row['child_level'],
3064
						'groups' => explode(',', $row['member_groups']),
3065
						'deny_groups' => explode(',', $row['deny_member_groups']),
3066
						'moderators' => array(),
3067
						'moderator_groups' => array()
3068
					);
3069
				}
3070
				// If a moderator exists for this board, add that moderator for all children too.
3071
				if (!empty($row['id_moderator']))
3072
					foreach ($boards as $id => $dummy)
3073
					{
3074
						$boards[$id]['moderators'][$row['id_moderator']] = array(
3075
							'id' => $row['id_moderator'],
3076
							'name' => $row['real_name'],
3077
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
3078
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
3079
						);
3080
					}
3081
3082
				// If a moderator group exists for this board, add that moderator group for all children too
3083
				if (!empty($row['id_moderator_group']))
3084
					foreach ($boards as $id => $dummy)
3085
					{
3086
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
3087
							'id' => $row['id_moderator_group'],
3088
							'name' => $row['group_name'],
3089
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
3090
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
3091
						);
3092
					}
3093
			}
3094
			$smcFunc['db_free_result']($result);
3095
		}
3096
3097
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
3098
	}
3099
3100
	return $boards;
3101
}
3102
3103
/**
3104
 * Attempt to reload our known languages.
3105
 * It will try to choose only utf8 or non-utf8 languages.
3106
 *
3107
 * @param bool $use_cache Whether or not to use the cache
3108
 * @return array An array of information about available languages
3109
 */
3110
function getLanguages($use_cache = true)
3111
{
3112
	global $context, $smcFunc, $settings, $modSettings, $cache_enable;
3113
3114
	// Either we don't use the cache, or its expired.
3115
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600)) == null)
3116
	{
3117
		// If we don't have our ucwords function defined yet, let's load the settings data.
3118
		if (empty($smcFunc['ucwords']))
3119
			reloadSettings();
3120
3121
		// If we don't have our theme information yet, let's get it.
3122
		if (empty($settings['default_theme_dir']))
3123
			loadTheme(0, false);
3124
3125
		// Default language directories to try.
3126
		$language_directories = array(
3127
			$settings['default_theme_dir'] . '/languages',
3128
		);
3129
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
3130
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
3131
3132
		// We possibly have a base theme directory.
3133
		if (!empty($settings['base_theme_dir']))
3134
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
3135
3136
		// Remove any duplicates.
3137
		$language_directories = array_unique($language_directories);
3138
3139
		foreach ($language_directories as $language_dir)
3140
		{
3141
			// Can't look in here... doesn't exist!
3142
			if (!file_exists($language_dir))
3143
				continue;
3144
3145
			$dir = dir($language_dir);
3146
			while ($entry = $dir->read())
3147
			{
3148
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
3149
				if (!preg_match('~^index\.((?:.(?!-utf8))+)\.php$~', $entry, $matches))
3150
					continue;
3151
3152
				$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
3153
3154
				if (($spos = strpos($langName, ' ')) !== false)
3155
					$langName = substr($langName, 0, ++$spos) . '(' . substr($langName, $spos) . ')';
3156
3157
				// Get the line we need.
3158
				$fp = @fopen($language_dir . '/' . $entry, 'r');
3159
3160
				// Yay!
3161
				if ($fp)
3162
				{
3163
					while (($line = fgets($fp)) !== false)
3164
					{
3165
						if (strpos($line, '$txt[\'native_name\']') === false)
3166
							continue;
3167
3168
						preg_match('~\$txt\[\'native_name\'\]\s*=\s*\'([^\']+)\';~', $line, $matchNative);
3169
3170
						// Set the language's name.
3171
						if (!empty($matchNative) && !empty($matchNative[1]))
3172
						{
3173
							// Don't mislabel the language if the translator missed this one.
3174
							if ($langName !== 'English' && $matchNative[1] === 'English')
3175
								break;
3176
3177
							$langName = un_htmlspecialchars($matchNative[1]);
3178
							break;
3179
						}
3180
					}
3181
3182
					fclose($fp);
3183
				}
3184
3185
				// Build this language entry.
3186
				$context['languages'][$matches[1]] = array(
3187
					'name' => $langName,
3188
					'selected' => false,
3189
					'filename' => $matches[1],
3190
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
3191
				);
3192
			}
3193
			$dir->close();
3194
		}
3195
3196
		// Avoid confusion when we have more than one English variant installed.
3197
		// Honestly, our default English version should always have been called "English (US)"
3198
		if (substr_count(implode(' ', array_keys($context['languages'])), 'english') > 1 && $context['languages']['english']['name'] === 'English')
3199
			$context['languages']['english']['name'] = 'English (US)';
3200
3201
		// Let's cash in on this deal.
3202
		if (!empty($cache_enable))
3203
			cache_put_data('known_languages', $context['languages'], !empty($cache_enable) && $cache_enable < 1 ? 86400 : 3600);
3204
	}
3205
3206
	return $context['languages'];
3207
}
3208
3209
/**
3210
 * Replace all vulgar words with respective proper words. (substring or whole words..)
3211
 * What this function does:
3212
 *  - it censors the passed string.
3213
 *  - if the theme setting allow_no_censored is on, and the theme option
3214
 *	show_no_censored is enabled, does not censor, unless force is also set.
3215
 *  - it caches the list of censored words to reduce parsing.
3216
 *
3217
 * @param string &$text The text to censor
3218
 * @param bool $force Whether to censor the text regardless of settings
3219
 * @return string The censored text
3220
 */
3221
function censorText(&$text, $force = false)
3222
{
3223
	global $modSettings, $options, $txt;
3224
	static $censor_vulgar = null, $censor_proper;
3225
3226
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
3227
		return $text;
3228
3229
	// If they haven't yet been loaded, load them.
3230
	if ($censor_vulgar == null)
3231
	{
3232
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
3233
		$censor_proper = explode("\n", $modSettings['censor_proper']);
3234
3235
		// Quote them for use in regular expressions.
3236
		if (!empty($modSettings['censorWholeWord']))
3237
		{
3238
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3239
3240
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3241
			{
3242
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3243
3244
				// Use the faster \b if we can, or something more complex if we can't
3245
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3246
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3247
3248
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3249
			}
3250
		}
3251
	}
3252
3253
	// Censoring isn't so very complicated :P.
3254
	if (empty($modSettings['censorWholeWord']))
3255
	{
3256
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3257
		$text = $func($censor_vulgar, $censor_proper, $text);
3258
	}
3259
	else
3260
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3261
3262
	return $text;
3263
}
3264
3265
/**
3266
 * Load the template/language file using require
3267
 * 	- loads the template or language file specified by filename.
3268
 * 	- uses eval unless disableTemplateEval is enabled.
3269
 * 	- outputs a parse error if the file did not exist or contained errors.
3270
 * 	- attempts to detect the error and line, and show detailed information.
3271
 *
3272
 * @param string $filename The name of the file to include
3273
 * @param bool $once If true only includes the file once (like include_once)
3274
 */
3275
function template_include($filename, $once = false)
3276
{
3277
	global $context, $txt, $scripturl, $modSettings;
3278
	global $boardurl, $boarddir;
3279
	global $maintenance, $mtitle, $mmessage;
3280
	static $templates = array();
3281
3282
	// We want to be able to figure out any errors...
3283
	@ini_set('track_errors', '1');
3284
3285
	// Don't include the file more than once, if $once is true.
3286
	if ($once && in_array($filename, $templates))
3287
		return;
3288
	// Add this file to the include list, whether $once is true or not.
3289
	else
3290
		$templates[] = $filename;
3291
3292
	$file_found = file_exists($filename);
3293
3294
	if ($once && $file_found)
3295
		require_once($filename);
3296
	elseif ($file_found)
3297
		require($filename);
3298
3299
	if ($file_found !== true)
3300
	{
3301
		ob_end_clean();
3302
		if (!empty($modSettings['enableCompressedOutput']))
3303
			@ob_start('ob_gzhandler');
3304
		else
3305
			ob_start();
3306
3307
		if (isset($_GET['debug']))
3308
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3309
3310
		// Don't cache error pages!!
3311
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3312
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3313
		header('cache-control: no-cache');
3314
3315
		if (!isset($txt['template_parse_error']))
3316
		{
3317
			$txt['template_parse_error'] = 'Template Parse Error!';
3318
			$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>.';
3319
			$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="' . $boardurl . '%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="' . $scripturl . '?theme=1">use the default theme</a>.';
3320
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3321
		}
3322
3323
		// First, let's get the doctype and language information out of the way.
3324
		echo '<!DOCTYPE html>
3325
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3326
	<head>';
3327
		if (isset($context['character_set']))
3328
			echo '
3329
		<meta charset="', $context['character_set'], '">';
3330
3331
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3332
			echo '
3333
		<title>', $mtitle, '</title>
3334
	</head>
3335
	<body>
3336
		<h3>', $mtitle, '</h3>
3337
		', $mmessage, '
3338
	</body>
3339
</html>';
3340
		elseif (!allowedTo('admin_forum'))
3341
			echo '
3342
		<title>', $txt['template_parse_error'], '</title>
3343
	</head>
3344
	<body>
3345
		<h3>', $txt['template_parse_error'], '</h3>
3346
		', $txt['template_parse_error_message'], '
3347
	</body>
3348
</html>';
3349
		else
3350
		{
3351
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3352
			$error_array = error_get_last();
3353
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3354
				$error = $error_array['message'];
3355
			if (empty($error))
3356
				$error = $txt['template_parse_errmsg'];
3357
3358
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3359
3360
			echo '
3361
		<title>', $txt['template_parse_error'], '</title>
3362
	</head>
3363
	<body>
3364
		<h3>', $txt['template_parse_error'], '</h3>
3365
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3366
3367
			if (!empty($error))
3368
				echo '
3369
		<hr>
3370
3371
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3372
3373
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3374
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3375
			{
3376
				$data = file($filename);
3377
				$data2 = highlight_php_code(implode('', $data));
3378
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3379
3380
				// Fix the PHP code stuff...
3381
				if (!isBrowser('gecko'))
3382
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3383
				else
3384
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3385
3386
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3387
				$j = -1;
3388
				foreach ($data as $line)
3389
				{
3390
					$j++;
3391
3392
					if (substr_count($line, '<br>') == 0)
3393
						continue;
3394
3395
					$n = substr_count($line, '<br>');
3396
					for ($i = 0; $i < $n; $i++)
3397
					{
3398
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3399
						unset($data2[$j + $i + 1]);
3400
					}
3401
					$j += $n;
3402
				}
3403
				$data2 = array_values($data2);
3404
				array_unshift($data2, '');
3405
3406
				echo '
3407
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3408
3409
				// Figure out what the color coding was before...
3410
				$line = max($match[1] - 9, 1);
3411
				$last_line = '';
3412
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3413
					if (strpos($data2[$line2], '<') !== false)
3414
					{
3415
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3416
							$last_line = $color_match[1];
3417
						break;
3418
					}
3419
3420
				// Show the relevant lines...
3421
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3422
				{
3423
					if ($line == $match[1])
3424
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3425
3426
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3427
					if (isset($data2[$line]) && $data2[$line] != '')
3428
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3429
3430
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3431
					{
3432
						$last_line = $color_match[1];
3433
						echo '</', substr($last_line, 1, 4), '>';
3434
					}
3435
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3436
						$last_line = '';
3437
					elseif ($last_line != '' && $data2[$line] != '')
3438
						echo '</', substr($last_line, 1, 4), '>';
3439
3440
					if ($line == $match[1])
3441
						echo '</pre></div><pre style="margin: 0;">';
3442
					else
3443
						echo "\n";
3444
				}
3445
3446
				echo '</pre></div>';
3447
			}
3448
3449
			echo '
3450
	</body>
3451
</html>';
3452
		}
3453
3454
		die;
3455
	}
3456
}
3457
3458
/**
3459
 * Initialize a database connection.
3460
 */
3461
function loadDatabase()
3462
{
3463
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3464
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port, $db_mb4;
3465
3466
	// Figure out what type of database we are using.
3467
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3468
		$db_type = 'mysql';
3469
3470
	// Load the file for the database.
3471
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3472
3473
	$db_options = array();
3474
3475
	// Add in the port if needed
3476
	if (!empty($db_port))
3477
		$db_options['port'] = $db_port;
3478
3479
	if (!empty($db_mb4))
3480
		$db_options['db_mb4'] = $db_mb4;
3481
3482
	// 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.
3483
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3484
	{
3485
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3486
3487
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3488
	}
3489
3490
	// Either we aren't in SSI mode, or it failed.
3491
	if (empty($db_connection))
3492
	{
3493
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3494
3495
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3496
	}
3497
3498
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3499
	if (!$db_connection)
3500
		display_db_error();
3501
3502
	// If in SSI mode fix up the prefix.
3503
	if (SMF == 'SSI')
3504
		db_fix_prefix($db_prefix, $db_name);
3505
}
3506
3507
/**
3508
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3509
 *
3510
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
3511
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3512
 * @return object|false A object of $cacheAPI, or False on failure.
3513
 */
3514
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3515
{
3516
	global $sourcedir, $cacheAPI, $cache_accelerator, $cache_enable;
3517
3518
	// is caching enabled?
3519
	if (empty($cache_enable) && empty($overrideCache))
3520
		return false;
3521
3522
	// Not overriding this and we have a cacheAPI, send it back.
3523
	if (empty($overrideCache) && is_object($cacheAPI))
3524
		return $cacheAPI;
3525
	elseif (is_null($cacheAPI))
3526
		$cacheAPI = false;
3527
3528
	// Make sure our class is in session.
3529
	require_once($sourcedir . '/Class-CacheAPI.php');
3530
3531
	// What accelerator we are going to try.
3532
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : (!empty($cache_accelerator) ? $cache_accelerator : 'smf');
3533
	$tryAccelerator = strtolower($tryAccelerator);
3534
3535
	// Do some basic tests.
3536
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3537
	{
3538
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3539
3540
		$cache_class_name = $tryAccelerator . '_cache';
3541
		$testAPI = new $cache_class_name();
3542
3543
		// No Support?  NEXT!
3544
		if (!$testAPI->isSupported())
3545
		{
3546
			// Can we save ourselves?
3547
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3548
				return loadCacheAccelerator(null, false);
3549
			return false;
3550
		}
3551
3552
		// Connect up to the accelerator.
3553
		$testAPI->connect();
3554
3555
		// Don't set this if we are overriding the cache.
3556
		if (is_null($overrideCache))
3557
		{
3558
			$cacheAPI = $testAPI;
3559
			return $cacheAPI;
3560
		}
3561
		else
3562
			return $testAPI;
3563
	}
3564
}
3565
3566
/**
3567
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3568
 *
3569
 * @param string $key The key for this entry
3570
 * @param string $file The file associated with this entry
3571
 * @param string $function The function to call
3572
 * @param array $params Parameters to be passed to the specified function
3573
 * @param int $level The cache level
3574
 * @return string The cached data
3575
 */
3576
function cache_quick_get($key, $file, $function, $params, $level = 1)
3577
{
3578
	global $modSettings, $sourcedir, $cache_enable;
3579
3580
	// @todo Why are we doing this if caching is disabled?
3581
3582
	if (function_exists('call_integration_hook'))
3583
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3584
3585
	/* Refresh the cache if either:
3586
		1. Caching is disabled.
3587
		2. The cache level isn't high enough.
3588
		3. The item has not been cached or the cached item expired.
3589
		4. The cached item has a custom expiration condition evaluating to true.
3590
		5. The expire time set in the cache item has passed (needed for Zend).
3591
	*/
3592
	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()))
3593
	{
3594
		require_once($sourcedir . '/' . $file);
3595
		$cache_block = call_user_func_array($function, $params);
3596
3597
		if (!empty($cache_enable) && $cache_enable >= $level)
3598
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3599
	}
3600
3601
	// Some cached data may need a freshening up after retrieval.
3602
	if (!empty($cache_block['post_retri_eval']))
3603
		eval($cache_block['post_retri_eval']);
3604
3605
	if (function_exists('call_integration_hook'))
3606
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3607
3608
	return $cache_block['data'];
3609
}
3610
3611
/**
3612
 * Puts value in the cache under key for ttl seconds.
3613
 *
3614
 * - It may "miss" so shouldn't be depended on
3615
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3616
 * - It supports:
3617
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3618
 *	 memcache: https://php.net/memcache
3619
 *	 APC: https://php.net/apc
3620
 *   APCu: https://php.net/book.apcu
3621
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3622
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3623
 *
3624
 * @param string $key A key for this value
3625
 * @param mixed $value The data to cache
3626
 * @param int $ttl How long (in seconds) the data should be cached for
3627
 */
3628
function cache_put_data($key, $value, $ttl = 120)
3629
{
3630
	global $smcFunc, $cache_enable, $cacheAPI;
3631
	global $cache_hits, $cache_count, $db_show_debug;
3632
3633
	if (empty($cache_enable) || empty($cacheAPI))
3634
		return;
3635
3636
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3637
	if (isset($db_show_debug) && $db_show_debug === true)
3638
	{
3639
		$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)));
3640
		$st = microtime(true);
3641
	}
3642
3643
	// The API will handle the rest.
3644
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3645
	$cacheAPI->putData($key, $value, $ttl);
3646
3647
	if (function_exists('call_integration_hook'))
3648
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3649
3650
	if (isset($db_show_debug) && $db_show_debug === true)
3651
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
3652
}
3653
3654
/**
3655
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3656
 * - It may often "miss", so shouldn't be depended on.
3657
 * - It supports the same as cache_put_data().
3658
 *
3659
 * @param string $key The key for the value to retrieve
3660
 * @param int $ttl The maximum age of the cached data
3661
 * @return string|null The cached data or null if nothing was loaded
3662
 */
3663
function cache_get_data($key, $ttl = 120)
3664
{
3665
	global $smcFunc, $cache_enable, $cacheAPI;
3666
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
3667
3668
	if (empty($cache_enable) || empty($cacheAPI))
3669
		return;
3670
3671
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3672
	if (isset($db_show_debug) && $db_show_debug === true)
3673
	{
3674
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3675
		$st = microtime(true);
3676
		$original_key = $key;
3677
	}
3678
3679
	// Ask the API to get the data.
3680
	$value = $cacheAPI->getData($key, $ttl);
3681
3682
	if (isset($db_show_debug) && $db_show_debug === true)
3683
	{
3684
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
3685
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3686
3687
		if (empty($value))
3688
		{
3689
			if (!is_array($cache_misses))
3690
				$cache_misses = array();
3691
3692
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3693
			$cache_misses[$cache_count_misses] = array('k' => $original_key, 'd' => 'get');
3694
		}
3695
	}
3696
3697
	if (function_exists('call_integration_hook') && isset($value))
3698
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3699
3700
	return empty($value) ? null : (isset($smcFunc['json_decode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3701
}
3702
3703
/**
3704
 * Empty out the cache in use as best it can
3705
 *
3706
 * It may only remove the files of a certain type (if the $type parameter is given)
3707
 * Type can be user, data or left blank
3708
 * 	- user clears out user data
3709
 *  - data clears out system / opcode data
3710
 *  - If no type is specified will perform a complete cache clearing
3711
 * For cache engines that do not distinguish on types, a full cache flush will be done
3712
 *
3713
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3714
 */
3715
function clean_cache($type = '')
3716
{
3717
	global $cacheAPI;
3718
3719
	// If we can't get to the API, can't do this.
3720
	if (empty($cacheAPI))
3721
		return;
3722
3723
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3724
	$cacheAPI->cleanCache($type);
3725
3726
	call_integration_hook('integrate_clean_cache');
3727
	clearstatcache();
3728
}
3729
3730
/**
3731
 * Helper function to set an array of data for an user's avatar.
3732
 *
3733
 * Makes assumptions based on the data provided, the following keys are required:
3734
 * - avatar The raw "avatar" column in members table
3735
 * - email The user's email. Used to get the gravatar info
3736
 * - filename The attachment filename
3737
 *
3738
 * @param array $data An array of raw info
3739
 * @return array An array of avatar data
3740
 */
3741
function set_avatar_data($data = array())
3742
{
3743
	global $modSettings, $smcFunc, $user_info;
3744
3745
	// Come on!
3746
	if (empty($data))
3747
		return array();
3748
3749
	// Set a nice default var.
3750
	$image = '';
3751
3752
	// Gravatar has been set as mandatory!
3753
	if (!empty($modSettings['gravatarOverride']))
3754
	{
3755
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3756
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3757
3758
		elseif (!empty($data['email']))
3759
			$image = get_gravatar_url($data['email']);
3760
	}
3761
3762
	// Look if the user has a gravatar field or has set an external url as avatar.
3763
	else
3764
	{
3765
		// So it's stored in the member table?
3766
		if (!empty($data['avatar']))
3767
		{
3768
			// Gravatar.
3769
			if (stristr($data['avatar'], 'gravatar://'))
3770
			{
3771
				if ($data['avatar'] == 'gravatar://')
3772
					$image = get_gravatar_url($data['email']);
3773
3774
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3775
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3776
			}
3777
3778
			// External url.
3779
			else
3780
				$image = parse_url($data['avatar'], PHP_URL_SCHEME) !== null ? get_proxied_url($data['avatar']) : $modSettings['avatar_url'] . '/' . $data['avatar'];
3781
		}
3782
3783
		// Perhaps this user has an attachment as avatar...
3784
		elseif (!empty($data['filename']))
3785
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3786
3787
		// Right... no avatar... use our default image.
3788
		else
3789
			$image = $modSettings['avatar_url'] . '/default.png';
3790
	}
3791
3792
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3793
3794
	// 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.
3795
	if (!empty($image))
3796
		return array(
3797
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3798
			'image' => '<img class="avatar" src="' . $image . '" alt="">',
3799
			'href' => $image,
3800
			'url' => $image,
3801
		);
3802
3803
	// Fallback to make life easier for everyone...
3804
	else
3805
		return array(
3806
			'name' => '',
3807
			'image' => '',
3808
			'href' => '',
3809
			'url' => '',
3810
		);
3811
}
3812
3813
/**
3814
 * Gets, and if necessary creates, the authentication secret to use for cookies, tokens, etc.
3815
 *
3816
 * Note: Never use the $auth_secret variable directly. Always call this function instead.
3817
 *
3818
 * @return string The authentication secret.
3819
 */
3820
function get_auth_secret()
3821
{
3822
	global $auth_secret, $sourcedir, $smcFunc;
3823
3824
	if (empty($auth_secret))
3825
	{
3826
		$auth_secret = bin2hex($smcFunc['random_bytes'](32));
3827
3828
		// It is important to store this in Settings.php, not the database.
3829
		require_once($sourcedir . '/Subs-Admin.php');
3830
		updateSettingsFile(array('auth_secret' => $auth_secret));
3831
	}
3832
3833
	return $auth_secret;
3834
}
3835
3836
?>