Issues (1014)

Sources/Load.php (1 issue)

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