Failed Conditions
Push — release-2.1 ( 8d1977...8da17b )
by Rick
06:19
created

Sources/Load.php (16 issues)

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

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

Loading history...
88
	}
89
90
	$modSettings['cache_enable'] = $cache_enable;
91
92
	// Used to force browsers to download fresh CSS and JavaScript when necessary
93
	$modSettings['browser_cache'] = !empty($modSettings['browser_cache']) ? (int) $modSettings['browser_cache'] : 0;
94
	$context['browser_cache'] = '?' . preg_replace('~\W~', '', strtolower(SMF_FULL_VERSION)) . '_' . $modSettings['browser_cache'];
95
96
	// Disable image proxy if we don't have SSL enabled
97
	if (empty($modSettings['force_ssl']))
98
		$image_proxy_enabled = false;
99
100
	// UTF-8 ?
101
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
102
	$context['utf8'] = $utf8;
103
104
	// Set a list of common functions.
105
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
106
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
107
		{
108
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
109
			return $string;
110
		} : function($string)
111
		{
112
			return $string;
113
		};
114
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
115
	{
116
		if (!$utf8 || $smcFunc['db_mb4'])
117
			return $string;
118
119
		$i = 0;
120
		$len = strlen($string);
121
		$new_string = '';
122
		while ($i < $len)
123
		{
124
			$ord = ord($string[$i]);
125
			if ($ord < 128)
126
			{
127
				$new_string .= $string[$i];
128
				$i++;
129
			}
130
			elseif ($ord < 224)
131
			{
132
				$new_string .= $string[$i] . $string[$i + 1];
133
				$i += 2;
134
			}
135
			elseif ($ord < 240)
136
			{
137
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
138
				$i += 3;
139
			}
140
			elseif ($ord < 248)
141
			{
142
				// Magic happens.
143
				$val = (ord($string[$i]) & 0x07) << 18;
144
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
145
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
146
				$val += (ord($string[$i + 3]) & 0x3F);
147
				$new_string .= '&#' . $val . ';';
148
				$i += 4;
149
			}
150
		}
151
		return $new_string;
152
	};
153
154
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
155
	$smcFunc += array(
156
		'entity_fix' => function($string)
157
		{
158
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
159
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
160
		},
161
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
162
		{
163
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
164
		},
165
		'htmltrim' => function($string) use ($utf8, $ent_check)
166
		{
167
			// Preg_replace space characters depend on the character set in use
168
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
169
170
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
171
		},
172
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
173
		{
174
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
175
		},
176
		'strpos' => function($haystack, $needle, $offset = 0) use ($utf8, $ent_check, $ent_list, $modSettings)
177
		{
178
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
179
180
			if (strlen($needle) === 1)
181
			{
182
				$result = array_search($needle, array_slice($haystack_arr, $offset));
183
				return is_int($result) ? $result + $offset : false;
184
			}
185
			else
186
			{
187
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
188
				$needle_size = count($needle_arr);
189
190
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
191
				while ((int) $result === $result)
192
				{
193
					$offset += $result;
194
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
195
						return $offset;
196
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
197
				}
198
				return false;
199
			}
200
		},
201
		'substr' => function($string, $start, $length = null) use ($utf8, $ent_check, $ent_list, $modSettings)
202
		{
203
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
204
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
205
		},
206
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
207
		{
208
			if (!function_exists('mb_strtolower'))
209
			{
210
				require_once($sourcedir . '/Subs-Charset.php');
211
				return utf8_strtolower($string);
212
			}
213
214
			return mb_strtolower($string, 'UTF-8');
215
		} : 'strtolower',
216
		'strtoupper' => $utf8 ? function($string)
217
		{
218
			global $sourcedir;
219
220
			if (!function_exists('mb_strtolower'))
221
			{
222
				require_once($sourcedir . '/Subs-Charset.php');
223
				return utf8_strtoupper($string);
224
			}
225
226
			return mb_strtoupper($string, 'UTF-8');
227
		} : 'strtoupper',
228
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
229
		{
230
			$string = $ent_check($string);
231
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
232
			$string = $matches[0];
233
			while (strlen($string) > $length)
234
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
235
			return $string;
236
		},
237
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
238
		{
239
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
240
		} : 'ucfirst',
241
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
242
		{
243
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
244
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
245
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
246
			return implode('', $words);
247
		} : 'ucwords',
248
		'json_decode' => 'smf_json_decode',
249
		'json_encode' => 'json_encode',
250
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
251
		{
252
			global $sourcedir;
253
254
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
255
			if (!is_callable('random_int'))
256
				require_once($sourcedir . '/random_compat/random.php');
257
258
			return random_int($min, $max);
259
		},
260
		'random_bytes' => function($length = 64)
261
		{
262
			global $sourcedir;
263
264
			if (!is_callable('random_bytes'))
265
				require_once($sourcedir . '/random_compat/random.php');
266
267
			// Make sure length is valid
268
			$length = max(1, (int) $length);
269
270
			return random_bytes($length);
271
		},
272
	);
273
274
	// Setting the timezone is a requirement for some functions.
275
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
276
		date_default_timezone_set($modSettings['default_timezone']);
277
	else
278
	{
279
		// Get PHP's default timezone, if set
280
		$ini_tz = ini_get('date.timezone');
281
		if (!empty($ini_tz))
282
			$modSettings['default_timezone'] = $ini_tz;
283
		else
284
			$modSettings['default_timezone'] = '';
285
286
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
287
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
288
		{
289
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
290
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
291
		}
292
293
		date_default_timezone_set($modSettings['default_timezone']);
294
	}
295
296
	// Check the load averages?
297
	if (!empty($modSettings['loadavg_enable']))
298
	{
299
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
0 ignored issues
show
It seems like you are loosely comparing $modSettings['load_avera...get_data('loadavg', 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
300
		{
301
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
302
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
303
				$modSettings['load_average'] = (float) $matches[1];
304
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
305
				$modSettings['load_average'] = (float) $matches[1];
306
			else
307
				unset($modSettings['load_average']);
308
309
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
310
				cache_put_data('loadavg', $modSettings['load_average'], 90);
311
		}
312
313
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
314
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
315
316
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
317
			display_loadavg_error();
318
	}
319
320
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
321
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
322
323
	// Here to justify the name of this function. :P
324
	// It should be added to the install and upgrade scripts.
325
	// But since the converters need to be updated also. This is easier.
326
	if (empty($modSettings['currentAttachmentUploadDir']))
327
	{
328
		updateSettings(array(
329
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
330
			'currentAttachmentUploadDir' => 1,
331
		));
332
	}
333
334
	// Respect PHP's limits.
335
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
336
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
337
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
338
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
339
340
	// Integration is cool.
341
	if (defined('SMF_INTEGRATION_SETTINGS'))
342
	{
343
		$integration_settings = $smcFunc['json_decode'](SMF_INTEGRATION_SETTINGS, true);
0 ignored issues
show
The constant SMF_INTEGRATION_SETTINGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
344
		foreach ($integration_settings as $hook => $function)
345
			add_integration_function($hook, $function, false);
346
	}
347
348
	// Any files to pre include?
349
	if (!empty($modSettings['integrate_pre_include']))
350
	{
351
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
352
		foreach ($pre_includes as $include)
353
		{
354
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
355
			if (file_exists($include))
356
				require_once($include);
357
		}
358
	}
359
360
	// This determines the server... not used in many places, except for login fixing.
361
	$context['server'] = array(
362
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
363
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
364
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
365
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
366
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
367
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
368
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
369
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
370
	);
371
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
372
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
373
374
	// Define a list of icons used across multiple places.
375
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
376
377
	// Define an array for custom profile fields placements.
378
	$context['cust_profile_fields_placement'] = array(
379
		'standard',
380
		'icons',
381
		'above_signature',
382
		'below_signature',
383
		'below_avatar',
384
		'above_member',
385
		'bottom_poster',
386
		'before_member',
387
		'after_member',
388
	);
389
390
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
391
	$context['meta_tags'] = array();
392
393
	// Define an array of allowed HTML tags.
394
	$context['allowed_html_tags'] = array(
395
		'<img>',
396
		'<div>',
397
	);
398
399
	// These are the only valid image types for SMF attachments, by default anyway.
400
	// Note: The values are for image mime types, not file extensions.
401
	$context['valid_image_types'] = array(
402
		IMAGETYPE_GIF => 'gif',
403
		IMAGETYPE_JPEG => 'jpeg',
404
		IMAGETYPE_PNG => 'png',
405
		IMAGETYPE_PSD => 'psd',
406
		IMAGETYPE_BMP => 'bmp',
407
		IMAGETYPE_TIFF_II => 'tiff',
408
		IMAGETYPE_TIFF_MM => 'tiff',
409
		IMAGETYPE_IFF => 'iff'
410
	);
411
412
	// Define a list of allowed tags for descriptions.
413
	$context['description_allowed_tags'] = array(
414
		'abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img',
415
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
416
		'sup', 'table', 'td', 'tr', 'u', 'url',
417
	);
418
419
	// Define a list of deprecated BBC tags
420
	// Even when enabled, they'll only work in old posts and not new ones
421
	$context['legacy_bbc'] = array(
422
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
423
		'green', 'move', 'red', 'shadow', 'tt', 'white',
424
	);
425
426
	// Define a list of BBC tags that require permissions to use
427
	$context['restricted_bbc'] = array(
428
		'html',
429
	);
430
431
	// Call pre load integration functions.
432
	call_integration_hook('integrate_pre_load');
433
}
434
435
/**
436
 * Load all the important user information.
437
 * What it does:
438
 * 	- sets up the $user_info array
439
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
440
 * 	- first checks for cookie or integration validation.
441
 * 	- uses the current session if no integration function or cookie is found.
442
 * 	- checks password length, if member is activated and the login span isn't over.
443
 * 		- if validation fails for the user, $id_member is set to 0.
444
 * 		- updates the last visit time when needed.
445
 */
446
function loadUserSettings()
447
{
448
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
449
	global $cookiename, $user_info, $language, $context, $cache_enable;
450
451
	require_once($sourcedir . '/Subs-Auth.php');
452
453
	// Check first the integration, then the cookie, and last the session.
454
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
455
	{
456
		$id_member = 0;
457
		foreach ($integration_ids as $integration_id)
458
		{
459
			$integration_id = (int) $integration_id;
460
			if ($integration_id > 0)
461
			{
462
				$id_member = $integration_id;
463
				$already_verified = true;
464
				break;
465
			}
466
		}
467
	}
468
	else
469
		$id_member = 0;
470
471
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
472
	{
473
		// First try 2.1 json-format cookie
474
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
475
476
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
477
		if (empty($cookie_data))
478
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
479
480
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
481
482
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
483
484
		// Make sure the cookie is set to the correct domain and path
485
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
486
			setLoginCookie((int) $login_span - time(), $id_member);
487
	}
488
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
489
	{
490
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
491
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
492
493
		if (empty($cookie_data))
494
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
495
496
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
497
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
498
	}
499
500
	// Only load this stuff if the user isn't a guest.
501
	if ($id_member != 0)
502
	{
503
		// Is the member data cached?
504
		if (empty($cache_enable) || $cache_enable < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
0 ignored issues
show
It seems like you are loosely comparing $user_settings = cache_g...ngs-' . $id_member, 60) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
505
		{
506
			$request = $smcFunc['db_query']('', '
507
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
508
				FROM {db_prefix}members AS mem
509
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
510
				WHERE mem.id_member = {int:id_member}
511
				LIMIT 1',
512
				array(
513
					'id_member' => $id_member,
514
				)
515
			);
516
			$user_settings = $smcFunc['db_fetch_assoc']($request);
517
			$smcFunc['db_free_result']($request);
518
519
			if (!empty($user_settings['avatar']))
520
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
521
522
			if (!empty($cache_enable) && $cache_enable >= 2)
523
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
524
		}
525
526
		// Did we find 'im?  If not, junk it.
527
		if (!empty($user_settings))
528
		{
529
			// As much as the password should be right, we can assume the integration set things up.
530
			if (!empty($already_verified) && $already_verified === true)
531
				$check = true;
532
			// SHA-512 hash should be 128 characters long.
533
			elseif (strlen($password) == 128)
534
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
535
			else
536
				$check = false;
537
538
			// Wrong password or not activated - either way, you're going nowhere.
539
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
540
		}
541
		else
542
			$id_member = 0;
543
544
		// Check if we are forcing TFA
545
		$force_tfasetup = !empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']) && SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != '.xml');
546
547
		// Don't force TFA on popups
548
		if ($force_tfasetup)
549
		{
550
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
551
				$force_tfasetup = false;
552
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
553
				$force_tfasetup = false;
554
555
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
556
		}
557
558
		// If we no longer have the member maybe they're being all hackey, stop brute force!
559
		if (!$id_member)
560
		{
561
			require_once($sourcedir . '/LogInOut.php');
562
			validatePasswordFlood(
563
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
564
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
565
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
566
				$id_member != 0
567
			);
568
		}
569
		// Validate for Two Factor Authentication
570
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
571
		{
572
			$tfacookie = $cookiename . '_tfa';
573
			$tfasecret = null;
574
575
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
576
577
			if (empty($verified) || !in_array(true, $verified))
578
			{
579
				if (!empty($_COOKIE[$tfacookie]))
580
				{
581
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
582
583
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
584
585
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
586
						$tfasecret = null;
587
				}
588
589
				// They didn't finish logging in before coming here? Then they're no one to us.
590
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
591
				{
592
					setLoginCookie(-3600, $id_member);
593
					$id_member = 0;
594
					$user_settings = array();
595
				}
596
			}
597
		}
598
		// When authenticating their two factor code, make sure to reset their ID for security
599
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
600
		{
601
			$id_member = 0;
602
			$context['tfa_member'] = $user_settings;
603
			$user_settings = array();
604
		}
605
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
606
		elseif ($force_tfasetup)
607
		{
608
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
609
			{
610
				//Build an array of ALL user membergroups.
611
				$full_groups = array($user_settings['id_group']);
612
				if (!empty($user_settings['additional_groups']))
613
				{
614
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
615
					$full_groups = array_unique($full_groups); //duplicates, maybe?
616
				}
617
618
				//Find out if any group requires 2FA
619
				$request = $smcFunc['db_query']('', '
620
					SELECT COUNT(id_group) AS total
621
					FROM {db_prefix}membergroups
622
					WHERE tfa_required = {int:tfa_required}
623
						AND id_group IN ({array_int:full_groups})',
624
					array(
625
						'tfa_required' => 1,
626
						'full_groups' => $full_groups,
627
					)
628
				);
629
				$row = $smcFunc['db_fetch_assoc']($request);
630
				$smcFunc['db_free_result']($request);
631
			}
632
			else
633
				$row['total'] = 1; //simplifies logics in the next "if"
634
635
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
636
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
637
638
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
639
				redirectexit('action=profile;area=tfasetup;forced');
640
		}
641
	}
642
643
	// Found 'im, let's set up the variables.
644
	if ($id_member != 0)
645
	{
646
		// Let's not update the last visit time in these cases...
647
		// 1. SSI doesn't count as visiting the forum.
648
		// 2. RSS feeds and XMLHTTP requests don't count either.
649
		// 3. If it was set within this session, no need to set it again.
650
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
651
		// 5. We're still logging in or authenticating
652
		if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))) && empty($_SESSION['id_msg_last_visit']) && (empty($cache_enable) || ($_SESSION['id_msg_last_visit'] = cache_get_data('user_last_visit-' . $id_member, 5 * 3600)) === null))
653
		{
654
			// @todo can this be cached?
655
			// Do a quick query to make sure this isn't a mistake.
656
			$result = $smcFunc['db_query']('', '
657
				SELECT poster_time
658
				FROM {db_prefix}messages
659
				WHERE id_msg = {int:id_msg}
660
				LIMIT 1',
661
				array(
662
					'id_msg' => $user_settings['id_msg_last_visit'],
663
				)
664
			);
665
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
666
			$smcFunc['db_free_result']($result);
667
668
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
669
670
			// If it was *at least* five hours ago...
671
			if ($visitTime < time() - 5 * 3600)
672
			{
673
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $_SERVER['REMOTE_ADDR'], 'member_ip2' => $_SERVER['BAN_CHECK_IP']));
674
				$user_settings['last_login'] = time();
675
676
				if (!empty($cache_enable) && $cache_enable >= 2)
677
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
678
679
				if (!empty($cache_enable))
680
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
681
			}
682
		}
683
		elseif (empty($_SESSION['id_msg_last_visit']))
684
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
685
686
		$username = $user_settings['member_name'];
687
688
		if (empty($user_settings['additional_groups']))
689
			$user_info = array(
690
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
691
			);
692
		else
693
			$user_info = array(
694
				'groups' => array_merge(
695
					array($user_settings['id_group'], $user_settings['id_post_group']),
696
					explode(',', $user_settings['additional_groups'])
697
				)
698
			);
699
700
		// Because history has proven that it is possible for groups to go bad - clean up in case.
701
		$user_info['groups'] = array_map('intval', $user_info['groups']);
702
703
		// This is a logged in user, so definitely not a spider.
704
		$user_info['possibly_robot'] = false;
705
706
		// Figure out the new time offset.
707
		if (!empty($user_settings['timezone']))
708
		{
709
			// Get the offsets from UTC for the server, then for the user.
710
			$tz_system = new DateTimeZone(@date_default_timezone_get());
711
			$tz_user = new DateTimeZone($user_settings['timezone']);
712
			$time_system = new DateTime('now', $tz_system);
713
			$time_user = new DateTime('now', $tz_user);
714
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
715
		}
716
		else
717
		{
718
			// !!! Compatibility.
719
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
720
		}
721
	}
722
	// If the user is a guest, initialize all the critical user settings.
723
	else
724
	{
725
		// This is what a guest's variables should be.
726
		$username = '';
727
		$user_info = array('groups' => array(-1));
728
		$user_settings = array();
729
730
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
731
			$_COOKIE[$cookiename] = '';
732
733
		// Expire the 2FA cookie
734
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
735
		{
736
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
737
738
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
739
740
			if (time() > $exp)
741
			{
742
				$_COOKIE[$cookiename . '_tfa'] = '';
743
				setTFACookie(-3600, 0, '');
744
			}
745
		}
746
747
		// Create a login token if it doesn't exist yet.
748
		if (!isset($_SESSION['token']['post-login']))
749
			createToken('login');
750
		else
751
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
752
753
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
754
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
755
		{
756
			require_once($sourcedir . '/ManageSearchEngines.php');
757
			$user_info['possibly_robot'] = SpiderCheck();
758
		}
759
		elseif (!empty($modSettings['spider_mode']))
760
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
761
		// If we haven't turned on proper spider hunts then have a guess!
762
		else
763
		{
764
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
765
			$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;
766
		}
767
768
		// We don't know the offset...
769
		$user_info['time_offset'] = 0;
770
	}
771
772
	// Login Cookie times. Format: time => txt
773
	$context['login_cookie_times'] = array(
774
		3153600 => 'always_logged_in',
775
		60 => 'one_hour',
776
		1440 => 'one_day',
777
		10080 => 'one_week',
778
		43200 => 'one_month',
779
	);
780
781
	// Set up the $user_info array.
782
	$user_info += array(
783
		'id' => $id_member,
784
		'username' => $username,
785
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
786
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
787
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
788
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
789
		'is_guest' => $id_member == 0,
790
		'is_admin' => in_array(1, $user_info['groups']),
791
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
792
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
793
		'ip' => $_SERVER['REMOTE_ADDR'],
794
		'ip2' => $_SERVER['BAN_CHECK_IP'],
795
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
796
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
797
		'avatar' => array(
798
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
799
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
800
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
801
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
802
		),
803
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
804
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
805
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
806
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
807
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
808
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
809
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
810
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
811
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
812
		'permissions' => array(),
813
	);
814
	$user_info['groups'] = array_unique($user_info['groups']);
815
	$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);
816
817
	// 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.
818
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
819
		unset($user_info['ignoreboards'][$tmp]);
820
821
	// Allow the user to change their language.
822
	if (!empty($modSettings['userLanguage']))
823
	{
824
		$languages = getLanguages();
825
826
		// Is it valid?
827
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
828
		{
829
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
830
831
			// Make it permanent for members.
832
			if (!empty($user_info['id']))
833
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
834
			else
835
				$_SESSION['language'] = $user_info['language'];
836
		}
837
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
838
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
839
	}
840
841
	$temp = build_query_board($user_info['id']);
842
	$user_info['query_see_board'] = $temp['query_see_board'];
843
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
844
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
845
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
846
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
847
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
848
849
	call_integration_hook('integrate_user_info');
850
}
851
852
/**
853
 * Check for moderators and see if they have access to the board.
854
 * What it does:
855
 * - sets up the $board_info array for current board information.
856
 * - if cache is enabled, the $board_info array is stored in cache.
857
 * - redirects to appropriate post if only message id is requested.
858
 * - is only used when inside a topic or board.
859
 * - determines the local moderators for the board.
860
 * - adds group id 3 if the user is a local moderator for the board they are in.
861
 * - prevents access if user is not in proper group nor a local moderator of the board.
862
 */
863
function loadBoard()
864
{
865
	global $txt, $scripturl, $context, $modSettings;
866
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
867
868
	// Assume they are not a moderator.
869
	$user_info['is_mod'] = false;
870
	$context['user']['is_mod'] = &$user_info['is_mod'];
871
872
	// Start the linktree off empty..
873
	$context['linktree'] = array();
874
875
	// Have they by chance specified a message id but nothing else?
876
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
877
	{
878
		// Make sure the message id is really an int.
879
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
880
881
		// Looking through the message table can be slow, so try using the cache first.
882
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
883
		{
884
			$request = $smcFunc['db_query']('', '
885
				SELECT id_topic
886
				FROM {db_prefix}messages
887
				WHERE id_msg = {int:id_msg}
888
				LIMIT 1',
889
				array(
890
					'id_msg' => $_REQUEST['msg'],
891
				)
892
			);
893
894
			// So did it find anything?
895
			if ($smcFunc['db_num_rows']($request))
896
			{
897
				list ($topic) = $smcFunc['db_fetch_row']($request);
898
				$smcFunc['db_free_result']($request);
899
				// Save save save.
900
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
901
			}
902
		}
903
904
		// Remember redirection is the key to avoiding fallout from your bosses.
905
		if (!empty($topic))
906
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
907
		else
908
		{
909
			loadPermissions();
910
			loadTheme();
911
			fatal_lang_error('topic_gone', false);
912
		}
913
	}
914
915
	// Load this board only if it is specified.
916
	if (empty($board) && empty($topic))
917
	{
918
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
919
		return;
920
	}
921
922
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
923
	{
924
		// @todo SLOW?
925
		if (!empty($topic))
926
			$temp = cache_get_data('topic_board-' . $topic, 120);
927
		else
928
			$temp = cache_get_data('board-' . $board, 120);
929
930
		if (!empty($temp))
931
		{
932
			$board_info = $temp;
933
			$board = $board_info['id'];
934
		}
935
	}
936
937
	if (empty($temp))
938
	{
939
		$custom_column_selects = [];
940
		$custom_column_parameters = [
941
			'current_topic' => $topic,
942
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
943
		];
944
945
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
946
947
		$request = $smcFunc['db_query']('load_board_info', '
948
			SELECT
949
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
950
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
951
				COALESCE(mem.id_member, 0) AS id_moderator,
952
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
953
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
954
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
955
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
956
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
957
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
958
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
959
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
960
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
961
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
962
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
963
			WHERE b.id_board = {raw:board_link}',
964
			$custom_column_parameters
965
		);
966
967
		// If there aren't any, skip.
968
		if ($smcFunc['db_num_rows']($request) > 0)
969
		{
970
			$row = $smcFunc['db_fetch_assoc']($request);
971
972
			// Set the current board.
973
			if (!empty($row['id_board']))
974
				$board = $row['id_board'];
975
976
			// Basic operating information. (globals... :/)
977
			$board_info = array(
978
				'id' => $board,
979
				'moderators' => array(),
980
				'moderator_groups' => array(),
981
				'cat' => array(
982
					'id' => $row['id_cat'],
983
					'name' => $row['cname']
984
				),
985
				'name' => $row['bname'],
986
				'description' => $row['description'],
987
				'num_topics' => $row['num_topics'],
988
				'unapproved_topics' => $row['unapproved_topics'],
989
				'unapproved_posts' => $row['unapproved_posts'],
990
				'unapproved_user_topics' => 0,
991
				'parent_boards' => getBoardParents($row['id_parent']),
992
				'parent' => $row['id_parent'],
993
				'child_level' => $row['child_level'],
994
				'theme' => $row['id_theme'],
995
				'override_theme' => !empty($row['override_theme']),
996
				'profile' => $row['id_profile'],
997
				'redirect' => $row['redirect'],
998
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
999
				'posts_count' => empty($row['count_posts']),
1000
				'cur_topic_approved' => empty($topic) || $row['approved'],
1001
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1002
			);
1003
1004
			// Load the membergroups allowed, and check permissions.
1005
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1006
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1007
1008
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1009
1010
			if (!empty($modSettings['board_manager_groups']))
1011
			{
1012
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1013
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1014
			}
1015
1016
			do
1017
			{
1018
				if (!empty($row['id_moderator']))
1019
					$board_info['moderators'][$row['id_moderator']] = array(
1020
						'id' => $row['id_moderator'],
1021
						'name' => $row['real_name'],
1022
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1023
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1024
					);
1025
1026
				if (!empty($row['id_moderator_group']))
1027
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1028
						'id' => $row['id_moderator_group'],
1029
						'name' => $row['group_name'],
1030
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1031
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1032
					);
1033
			}
1034
			while ($row = $smcFunc['db_fetch_assoc']($request));
1035
1036
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1037
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1038
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1039
			{
1040
				// Free the previous result
1041
				$smcFunc['db_free_result']($request);
1042
1043
				// @todo why is this using id_topic?
1044
				// @todo Can this get cached?
1045
				$request = $smcFunc['db_query']('', '
1046
					SELECT COUNT(id_topic)
1047
					FROM {db_prefix}topics
1048
					WHERE id_member_started={int:id_member}
1049
						AND approved = {int:unapproved}
1050
						AND id_board = {int:board}',
1051
					array(
1052
						'id_member' => $user_info['id'],
1053
						'unapproved' => 0,
1054
						'board' => $board,
1055
					)
1056
				);
1057
1058
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1059
			}
1060
1061
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1062
			{
1063
				// @todo SLOW?
1064
				if (!empty($topic))
1065
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1066
				cache_put_data('board-' . $board, $board_info, 120);
1067
			}
1068
		}
1069
		else
1070
		{
1071
			// Otherwise the topic is invalid, there are no moderators, etc.
1072
			$board_info = array(
1073
				'moderators' => array(),
1074
				'moderator_groups' => array(),
1075
				'error' => 'exist'
1076
			);
1077
			$topic = null;
1078
			$board = 0;
1079
		}
1080
		$smcFunc['db_free_result']($request);
1081
	}
1082
1083
	if (!empty($topic))
1084
		$_GET['board'] = (int) $board;
1085
1086
	if (!empty($board))
1087
	{
1088
		// Get this into an array of keys for array_intersect
1089
		$moderator_groups = array_keys($board_info['moderator_groups']);
1090
1091
		// Now check if the user is a moderator.
1092
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1093
1094
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1095
			$board_info['error'] = 'access';
1096
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1097
			$board_info['error'] = 'access';
1098
1099
		// Build up the linktree.
1100
		$context['linktree'] = array_merge(
1101
			$context['linktree'],
1102
			array(array(
1103
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1104
				'name' => $board_info['cat']['name']
1105
			)),
1106
			array_reverse($board_info['parent_boards']),
1107
			array(array(
1108
				'url' => $scripturl . '?board=' . $board . '.0',
1109
				'name' => $board_info['name']
1110
			))
1111
		);
1112
	}
1113
1114
	// Set the template contextual information.
1115
	$context['user']['is_mod'] = &$user_info['is_mod'];
1116
	$context['current_topic'] = $topic;
1117
	$context['current_board'] = $board;
1118
1119
	// No posting in redirection boards!
1120
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1121
		$board_info['error'] = 'post_in_redirect';
1122
1123
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1124
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1125
	{
1126
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1127
		loadPermissions();
1128
		loadTheme();
1129
1130
		$_GET['board'] = '';
1131
		$_GET['topic'] = '';
1132
1133
		// The linktree should not give the game away mate!
1134
		$context['linktree'] = array(
1135
			array(
1136
				'url' => $scripturl,
1137
				'name' => $context['forum_name_html_safe']
1138
			)
1139
		);
1140
1141
		// If it's a prefetching agent or we're requesting an attachment.
1142
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1143
		{
1144
			ob_end_clean();
1145
			send_http_status(403);
1146
			die;
0 ignored issues
show
Using exit here is not recommended.

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

Loading history...
1147
		}
1148
		elseif ($board_info['error'] == 'post_in_redirect')
1149
		{
1150
			// Slightly different error message here...
1151
			fatal_lang_error('cannot_post_redirect', false);
1152
		}
1153
		elseif ($user_info['is_guest'])
1154
		{
1155
			loadLanguage('Errors');
1156
			is_not_guest($txt['topic_gone']);
1157
		}
1158
		else
1159
			fatal_lang_error('topic_gone', false);
1160
	}
1161
1162
	if ($user_info['is_mod'])
1163
		$user_info['groups'][] = 3;
1164
}
1165
1166
/**
1167
 * Load this user's permissions.
1168
 */
1169
function loadPermissions()
1170
{
1171
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1172
1173
	if ($user_info['is_admin'])
1174
	{
1175
		banPermissions();
1176
		return;
1177
	}
1178
1179
	if (!empty($cache_enable))
1180
	{
1181
		$cache_groups = $user_info['groups'];
1182
		asort($cache_groups);
1183
		$cache_groups = implode(',', $cache_groups);
1184
		// If it's a spider then cache it different.
1185
		if ($user_info['possibly_robot'])
1186
			$cache_groups .= '-spider';
1187
1188
		if ($cache_enable >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
It seems like you are loosely comparing $temp = cache_get_data('...ps . ':' . $board, 240) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1189
		{
1190
			list ($user_info['permissions']) = $temp;
1191
			banPermissions();
1192
1193
			return;
1194
		}
1195
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
It seems like you are loosely comparing $temp = cache_get_data('...' . $cache_groups, 240) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1196
			list ($user_info['permissions'], $removals) = $temp;
1197
	}
1198
1199
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1200
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1201
1202
	if (empty($user_info['permissions']))
1203
	{
1204
		// Get the general permissions.
1205
		$request = $smcFunc['db_query']('', '
1206
			SELECT permission, add_deny
1207
			FROM {db_prefix}permissions
1208
			WHERE id_group IN ({array_int:member_groups})
1209
				' . $spider_restrict,
1210
			array(
1211
				'member_groups' => $user_info['groups'],
1212
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1213
			)
1214
		);
1215
		$removals = array();
1216
		while ($row = $smcFunc['db_fetch_assoc']($request))
1217
		{
1218
			if (empty($row['add_deny']))
1219
				$removals[] = $row['permission'];
1220
			else
1221
				$user_info['permissions'][] = $row['permission'];
1222
		}
1223
		$smcFunc['db_free_result']($request);
1224
1225
		if (isset($cache_groups))
1226
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1227
	}
1228
1229
	// Get the board permissions.
1230
	if (!empty($board))
1231
	{
1232
		// Make sure the board (if any) has been loaded by loadBoard().
1233
		if (!isset($board_info['profile']))
1234
			fatal_lang_error('no_board');
1235
1236
		$request = $smcFunc['db_query']('', '
1237
			SELECT permission, add_deny
1238
			FROM {db_prefix}board_permissions
1239
			WHERE (id_group IN ({array_int:member_groups})
1240
				' . $spider_restrict . ')
1241
				AND id_profile = {int:id_profile}',
1242
			array(
1243
				'member_groups' => $user_info['groups'],
1244
				'id_profile' => $board_info['profile'],
1245
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1246
			)
1247
		);
1248
		while ($row = $smcFunc['db_fetch_assoc']($request))
1249
		{
1250
			if (empty($row['add_deny']))
1251
				$removals[] = $row['permission'];
1252
			else
1253
				$user_info['permissions'][] = $row['permission'];
1254
		}
1255
		$smcFunc['db_free_result']($request);
1256
	}
1257
1258
	// Remove all the permissions they shouldn't have ;).
1259
	if (!empty($modSettings['permission_enable_deny']))
1260
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
1261
1262
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1263
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1264
1265
	// Banned?  Watch, don't touch..
1266
	banPermissions();
1267
1268
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1269
	if (!$user_info['is_guest'])
1270
	{
1271
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1272
		{
1273
			require_once($sourcedir . '/Subs-Auth.php');
1274
			rebuildModCache();
1275
		}
1276
		else
1277
			$user_info['mod_cache'] = $_SESSION['mc'];
1278
1279
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1280
		// For example this drastically simplifies certain changes to the profile area.
1281
		$user_info['permissions'][] = 'is_not_guest';
1282
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1283
		$user_info['permissions'][] = 'profile_view_own';
1284
		if (in_array('profile_view', $user_info['permissions']))
1285
			$user_info['permissions'][] = 'profile_view_any';
1286
	}
1287
}
1288
1289
/**
1290
 * Loads an array of users' data by ID or member_name.
1291
 *
1292
 * @param array|string $users An array of users by id or name or a single username/id
1293
 * @param bool $is_name Whether $users contains names
1294
 * @param string $set What kind of data to load (normal, profile, minimal)
1295
 * @return array The ids of the members loaded
1296
 */
1297
function loadMemberData($users, $is_name = false, $set = 'normal')
1298
{
1299
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1300
	global $user_info, $cache_enable;
1301
1302
	// Can't just look for no users :P.
1303
	if (empty($users))
1304
		return array();
1305
1306
	// Pass the set value
1307
	$context['loadMemberContext_set'] = $set;
1308
1309
	// Make sure it's an array.
1310
	$users = !is_array($users) ? array($users) : array_unique($users);
1311
	$loaded_ids = array();
1312
1313
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1314
	{
1315
		$users = array_values($users);
1316
		for ($i = 0, $n = count($users); $i < $n; $i++)
1317
		{
1318
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1319
			if ($data == null)
0 ignored issues
show
It seems like you are loosely comparing $data of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1320
				continue;
1321
1322
			$loaded_ids[] = $data['id_member'];
1323
			$user_profile[$data['id_member']] = $data;
1324
			unset($users[$i]);
1325
		}
1326
	}
1327
1328
	// Used by default
1329
	$select_columns = '
1330
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1331
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1332
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1333
			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,
1334
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1335
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1336
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1337
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1338
	$select_tables = '
1339
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1340
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1341
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1342
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1343
1344
	// We add or replace according the the set
1345
	switch ($set)
1346
	{
1347
		case 'normal':
1348
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1349
			break;
1350
		case 'profile':
1351
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1352
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1353
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1354
			break;
1355
		case 'minimal':
1356
			$select_columns = '
1357
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1358
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1359
			$select_tables = '';
1360
			break;
1361
		default:
1362
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1363
	}
1364
1365
	// Allow mods to easily add to the selected member data
1366
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1367
1368
	if (!empty($users))
1369
	{
1370
		// Load the member's data.
1371
		$request = $smcFunc['db_query']('', '
1372
			SELECT' . $select_columns . '
1373
			FROM {db_prefix}members AS mem' . $select_tables . '
1374
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1375
			array(
1376
				'blank_string' => '',
1377
				'users' => $users,
1378
			)
1379
		);
1380
		$new_loaded_ids = array();
1381
		while ($row = $smcFunc['db_fetch_assoc']($request))
1382
		{
1383
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1384
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1385
1386
			// Take care of proxying avatar if required, do this here for maximum reach
1387
			if (!empty($row['avatar']))
1388
				$row['avatar'] = get_proxied_url($row['avatar']);
1389
1390
			// Keep track of the member's normal member group
1391
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1392
1393
			if (isset($row['member_ip']))
1394
				$row['member_ip'] = inet_dtop($row['member_ip']);
1395
			if (isset($row['member_ip2']))
1396
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1397
			$row['id_member'] = (int) $row['id_member'];
1398
			$new_loaded_ids[] = $row['id_member'];
1399
			$loaded_ids[] = $row['id_member'];
1400
			$row['options'] = array();
1401
			$user_profile[$row['id_member']] = $row;
1402
		}
1403
		$smcFunc['db_free_result']($request);
1404
	}
1405
1406
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1407
	{
1408
		$request = $smcFunc['db_query']('', '
1409
			SELECT id_member, variable, value
1410
			FROM {db_prefix}themes
1411
			WHERE id_member IN ({array_int:loaded_ids})',
1412
			array(
1413
				'loaded_ids' => $new_loaded_ids,
1414
			)
1415
		);
1416
		while ($row = $smcFunc['db_fetch_assoc']($request))
1417
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1418
		$smcFunc['db_free_result']($request);
1419
	}
1420
1421
	$additional_mods = array();
1422
1423
	// Are any of these users in groups assigned to moderate this board?
1424
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1425
	{
1426
		foreach ($loaded_ids as $a_member)
1427
		{
1428
			if (!empty($user_profile[$a_member]['additional_groups']))
1429
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1430
			else
1431
				$groups = array($user_profile[$a_member]['id_group']);
1432
1433
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1434
1435
			if (!empty($temp))
1436
			{
1437
				$additional_mods[] = $a_member;
1438
			}
1439
		}
1440
	}
1441
1442
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1443
	{
1444
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1445
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1446
	}
1447
1448
	// Are we loading any moderators?  If so, fix their group data...
1449
	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)
1450
	{
1451
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
0 ignored issues
show
It seems like you are loosely comparing $row = cache_get_data('m...rator_group_info', 480) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison === instead.
Loading history...
1452
		{
1453
			$request = $smcFunc['db_query']('', '
1454
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1455
				FROM {db_prefix}membergroups
1456
				WHERE id_group = {int:moderator_group}
1457
				LIMIT 1',
1458
				array(
1459
					'moderator_group' => 3,
1460
				)
1461
			);
1462
			$row = $smcFunc['db_fetch_assoc']($request);
1463
			$smcFunc['db_free_result']($request);
1464
1465
			cache_put_data('moderator_group_info', $row, 480);
1466
		}
1467
1468
		foreach ($temp_mods as $id)
1469
		{
1470
			// By popular demand, don't show admins or global moderators as moderators.
1471
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1472
				$user_profile[$id]['member_group'] = $row['member_group'];
1473
1474
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1475
			if (!empty($row['icons']))
1476
				$user_profile[$id]['icons'] = $row['icons'];
1477
			if (!empty($row['member_group_color']))
1478
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1479
		}
1480
	}
1481
1482
	return $loaded_ids;
1483
}
1484
1485
/**
1486
 * Loads the user's basic values... meant for template/theme usage.
1487
 *
1488
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1489
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1490
 * @return boolean Whether or not the data was loaded successfully
1491
 */
1492
function loadMemberContext($user, $display_custom_fields = false)
1493
{
1494
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1495
	global $context, $modSettings, $settings, $smcFunc;
1496
	static $dataLoaded = array();
1497
	static $loadedLanguages = array();
1498
1499
	// If this person's data is already loaded, skip it.
1500
	if (isset($dataLoaded[$user]))
1501
		return true;
1502
1503
	// We can't load guests or members not loaded by loadMemberData()!
1504
	if ($user == 0)
1505
		return false;
1506
	if (!isset($user_profile[$user]))
1507
	{
1508
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1509
		return false;
1510
	}
1511
1512
	// Well, it's loaded now anyhow.
1513
	$dataLoaded[$user] = true;
1514
	$profile = $user_profile[$user];
1515
1516
	// Censor everything.
1517
	censorText($profile['signature']);
1518
	censorText($profile['personal_text']);
1519
1520
	// Set things up to be used before hand.
1521
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1522
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1523
1524
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1525
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1526
	// Setup the buddy status here (One whole in_array call saved :P)
1527
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1528
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1529
1530
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1531
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1532
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1533
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1534
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1535
	else //not set, bye bye
1536
		$group_icon_url = '';
1537
1538
	// These minimal values are always loaded
1539
	$memberContext[$user] = array(
1540
		'username' => $profile['member_name'],
1541
		'name' => $profile['real_name'],
1542
		'id' => $profile['id_member'],
1543
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1544
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($modSettings['onlineEnable']) ? 'class="pm_icon"' : '') . '>' . $profile['real_name'] . '</a>',
1545
		'email' => $profile['email_address'],
1546
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1547
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1548
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1549
	);
1550
1551
	// If the set isn't minimal then load the monstrous array.
1552
	if ($context['loadMemberContext_set'] != 'minimal')
1553
	{
1554
		// Go the extra mile and load the user's native language name.
1555
		if (empty($loadedLanguages))
1556
			$loadedLanguages = getLanguages();
1557
1558
		// Figure out the new time offset.
1559
		if (!empty($profile['timezone']))
1560
		{
1561
			// Get the offsets from UTC for the server, then for the user.
1562
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1563
			$tz_user = new DateTimeZone($profile['timezone']);
1564
			$time_system = new DateTime('now', $tz_system);
1565
			$time_user = new DateTime('now', $tz_user);
1566
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1567
		}
1568
		else
1569
		{
1570
			// !!! Compatibility.
1571
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1572
		}
1573
1574
		$memberContext[$user] += array(
1575
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1576
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1577
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</a>',
1578
			'is_buddy' => $profile['buddy'],
1579
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1580
			'buddies' => $buddy_list,
1581
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1582
			'blurb' => $profile['personal_text'],
1583
			'website' => array(
1584
				'title' => $profile['website_title'],
1585
				'url' => $profile['website_url'],
1586
			),
1587
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1588
			'signature' => $profile['signature'],
1589
			'real_posts' => $profile['posts'],
1590
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1591
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1592
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1593
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1594
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1595
			'online' => array(
1596
				'is_online' => $profile['is_online'],
1597
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1598
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1599
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1600
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1601
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1602
			),
1603
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1604
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1605
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1606
			'options' => $profile['options'],
1607
			'is_guest' => false,
1608
			'primary_group' => $profile['primary_group'],
1609
			'group' => $profile['member_group'],
1610
			'group_color' => $profile['member_group_color'],
1611
			'group_id' => $profile['id_group'],
1612
			'post_group' => $profile['post_group'],
1613
			'post_group_color' => $profile['post_group_color'],
1614
			'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]),
1615
			'warning' => $profile['warning'],
1616
			'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' : (''))),
1617
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1618
			'custom_fields' => array(),
1619
		);
1620
	}
1621
1622
	// If the set isn't minimal then load their avatar as well.
1623
	if ($context['loadMemberContext_set'] != 'minimal')
1624
	{
1625
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1626
		{
1627
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1628
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1629
			else
1630
				$image = get_gravatar_url($profile['email_address']);
1631
		}
1632
		else
1633
		{
1634
			// So it's stored in the member table?
1635
			if (!empty($profile['avatar']))
1636
			{
1637
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1638
			}
1639
			elseif (!empty($profile['filename']))
1640
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1641
			// Right... no avatar...use the default one
1642
			else
1643
				$image = $modSettings['avatar_url'] . '/default.png';
1644
		}
1645
		if (!empty($image))
1646
			$memberContext[$user]['avatar'] = array(
1647
				'name' => $profile['avatar'],
1648
				'image' => '<img class="avatar" src="' . $image . '" alt="">',
1649
				'href' => $image,
1650
				'url' => $image,
1651
			);
1652
	}
1653
1654
	// Are we also loading the members custom fields into context?
1655
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1656
	{
1657
		$memberContext[$user]['custom_fields'] = array();
1658
1659
		if (!isset($context['display_fields']))
1660
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1661
1662
		foreach ($context['display_fields'] as $custom)
1663
		{
1664
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1665
				continue;
1666
1667
			$value = $profile['options'][$custom['col_name']];
1668
1669
			$fieldOptions = array();
1670
			$currentKey = 0;
1671
1672
			// Create a key => value array for multiple options fields
1673
			if (!empty($custom['options']))
1674
				foreach ($custom['options'] as $k => $v)
1675
				{
1676
					$fieldOptions[] = $v;
1677
					if (empty($currentKey))
1678
						$currentKey = $v == $value ? $k : 0;
1679
				}
1680
1681
			// BBC?
1682
			if ($custom['bbc'])
1683
				$value = parse_bbc($value);
1684
1685
			// ... or checkbox?
1686
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1687
				$value = $value ? $txt['yes'] : $txt['no'];
1688
1689
			// Enclosing the user input within some other text?
1690
			if (!empty($custom['enclose']))
1691
				$value = strtr($custom['enclose'], array(
1692
					'{SCRIPTURL}' => $scripturl,
1693
					'{IMAGES_URL}' => $settings['images_url'],
1694
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1695
					'{INPUT}' => $value,
1696
					'{KEY}' => $currentKey,
1697
				));
1698
1699
			$memberContext[$user]['custom_fields'][] = array(
1700
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1701
				'col_name' => $custom['col_name'],
1702
				'value' => un_htmlspecialchars($value),
1703
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1704
			);
1705
		}
1706
	}
1707
1708
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1709
	return true;
1710
}
1711
1712
/**
1713
 * Loads the user's custom profile fields
1714
 *
1715
 * @param integer|array $users A single user ID or an array of user IDs
1716
 * @param string|array $params Either a string or an array of strings with profile field names
1717
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1718
 */
1719
function loadMemberCustomFields($users, $params)
1720
{
1721
	global $smcFunc, $txt, $scripturl, $settings;
1722
1723
	// Do not waste my time...
1724
	if (empty($users) || empty($params))
1725
		return false;
1726
1727
	// Make sure it's an array.
1728
	$users = !is_array($users) ? array($users) : array_unique($users);
1729
	$params = !is_array($params) ? array($params) : array_unique($params);
1730
	$return = array();
1731
1732
	$request = $smcFunc['db_query']('', '
1733
		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,
1734
		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
1735
		FROM {db_prefix}themes AS t
1736
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1737
		WHERE id_member IN ({array_int:loaded_ids})
1738
			AND variable IN ({array_string:params})
1739
		ORDER BY field_order',
1740
		array(
1741
			'loaded_ids' => $users,
1742
			'params' => $params,
1743
		)
1744
	);
1745
1746
	while ($row = $smcFunc['db_fetch_assoc']($request))
1747
	{
1748
		$fieldOptions = array();
1749
		$currentKey = 0;
1750
1751
		// Create a key => value array for multiple options fields
1752
		if (!empty($row['field_options']))
1753
			foreach (explode(',', $row['field_options']) as $k => $v)
1754
			{
1755
				$fieldOptions[] = $v;
1756
				if (empty($currentKey))
1757
					$currentKey = $v == $row['value'] ? $k : 0;
1758
			}
1759
1760
		// BBC?
1761
		if (!empty($row['bbc']))
1762
			$row['value'] = parse_bbc($row['value']);
1763
1764
		// ... or checkbox?
1765
		elseif (isset($row['type']) && $row['type'] == 'check')
1766
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1767
1768
		// Enclosing the user input within some other text?
1769
		if (!empty($row['enclose']))
1770
			$row['value'] = strtr($row['enclose'], array(
1771
				'{SCRIPTURL}' => $scripturl,
1772
				'{IMAGES_URL}' => $settings['images_url'],
1773
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1774
				'{INPUT}' => un_htmlspecialchars($row['value']),
1775
				'{KEY}' => $currentKey,
1776
			));
1777
1778
		// Send a simple array if there is just 1 param
1779
		if (count($params) == 1)
1780
			$return[$row['id_member']] = $row;
1781
1782
		// More than 1? knock yourself out...
1783
		else
1784
		{
1785
			if (!isset($return[$row['id_member']]))
1786
				$return[$row['id_member']] = array();
1787
1788
			$return[$row['id_member']][$row['variable']] = $row;
1789
		}
1790
	}
1791
1792
	$smcFunc['db_free_result']($request);
1793
1794
	return !empty($return) ? $return : false;
1795
}
1796
1797
/**
1798
 * Loads information about what browser the user is viewing with and places it in $context
1799
 *  - uses the class from {@link Class-BrowserDetect.php}
1800
 */
1801
function detectBrowser()
1802
{
1803
	// Load the current user's browser of choice
1804
	$detector = new browser_detector;
1805
	$detector->detectBrowser();
1806
}
1807
1808
/**
1809
 * Are we using this browser?
1810
 *
1811
 * Wrapper function for detectBrowser
1812
 *
1813
 * @param string $browser The browser we are checking for.
1814
 * @return bool Whether or not the current browser is what we're looking for
1815
 */
1816
function isBrowser($browser)
1817
{
1818
	global $context;
1819
1820
	// Don't know any browser!
1821
	if (empty($context['browser']))
1822
		detectBrowser();
1823
1824
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1825
}
1826
1827
/**
1828
 * Load a theme, by ID.
1829
 *
1830
 * @param int $id_theme The ID of the theme to load
1831
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1832
 */
1833
function loadTheme($id_theme = 0, $initialize = true)
1834
{
1835
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1836
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1837
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $cache_enable;
1838
1839
	if (empty($id_theme))
1840
	{
1841
		// The theme was specified by the board.
1842
		if (!empty($board_info['theme']))
1843
			$id_theme = $board_info['theme'];
1844
		// The theme is the forum's default.
1845
		else
1846
			$id_theme = $modSettings['theme_guests'];
1847
1848
		// Sometimes the user can choose their own theme.
1849
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1850
		{
1851
			// The theme was specified by REQUEST.
1852
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1853
			{
1854
				$id_theme = (int) $_REQUEST['theme'];
1855
				$_SESSION['id_theme'] = $id_theme;
1856
			}
1857
			// The theme was specified by REQUEST... previously.
1858
			elseif (!empty($_SESSION['id_theme']))
1859
				$id_theme = (int) $_SESSION['id_theme'];
1860
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1861
			elseif (!empty($user_info['theme']))
1862
				$id_theme = $user_info['theme'];
1863
		}
1864
1865
		// Verify the id_theme... no foul play.
1866
		// Always allow the board specific theme, if they are overriding.
1867
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1868
			$id_theme = $board_info['theme'];
1869
		elseif (!empty($modSettings['enableThemes']))
1870
		{
1871
			$themes = explode(',', $modSettings['enableThemes']);
1872
			if (!in_array($id_theme, $themes))
1873
				$id_theme = $modSettings['theme_guests'];
1874
			else
1875
				$id_theme = (int) $id_theme;
1876
		}
1877
	}
1878
1879
	// Allow mod authors the option to override the theme id for custom page themes
1880
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1881
1882
	// We already load the basic stuff?
1883
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1884
	{
1885
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1886
1887
		if (!empty($cache_enable) && $cache_enable >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
It seems like you are loosely comparing $temp = cache_get_data('...me . ':' . $member, 60) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1888
		{
1889
			$themeData = $temp;
1890
			$flag = true;
1891
		}
1892
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
It seems like you are loosely comparing $temp = cache_get_data('...ings-' . $id_theme, 90) of type null|string against null; this is ambiguous if the string can be empty. Consider using a strict comparison !== instead.
Loading history...
1893
			$themeData = $temp + array($member => array());
1894
		else
1895
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1896
1897
		if (empty($flag))
1898
		{
1899
			// Load variables from the current or default theme, global or this user's.
1900
			$result = $smcFunc['db_query']('', '
1901
				SELECT variable, value, id_member, id_theme
1902
				FROM {db_prefix}themes
1903
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1904
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
1905
				ORDER BY id_theme asc',
1906
				array(
1907
					'id_theme' => $id_theme,
1908
					'id_member' => $member,
1909
				)
1910
			);
1911
			// Pick between $settings and $options depending on whose data it is.
1912
			foreach ($smcFunc['db_fetch_all']($result) as $row)
1913
			{
1914
				// There are just things we shouldn't be able to change as members.
1915
				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')))
1916
					continue;
1917
1918
				// If this is the theme_dir of the default theme, store it.
1919
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1920
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1921
1922
				// If this isn't set yet, is a theme option, or is not the default theme..
1923
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1924
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1925
			}
1926
			$smcFunc['db_free_result']($result);
1927
1928
			if (!empty($themeData[-1]))
1929
				foreach ($themeData[-1] as $k => $v)
1930
				{
1931
					if (!isset($themeData[$member][$k]))
1932
						$themeData[$member][$k] = $v;
1933
				}
1934
1935
			if (!empty($cache_enable) && $cache_enable >= 2)
1936
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1937
			// Only if we didn't already load that part of the cache...
1938
			elseif (!isset($temp))
1939
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1940
		}
1941
1942
		$settings = $themeData[0];
1943
		$options = $themeData[$member];
1944
1945
		$settings['theme_id'] = $id_theme;
1946
1947
		$settings['actual_theme_url'] = $settings['theme_url'];
1948
		$settings['actual_images_url'] = $settings['images_url'];
1949
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1950
1951
		$settings['template_dirs'] = array();
1952
		// This theme first.
1953
		$settings['template_dirs'][] = $settings['theme_dir'];
1954
1955
		// Based on theme (if there is one).
1956
		if (!empty($settings['base_theme_dir']))
1957
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1958
1959
		// Lastly the default theme.
1960
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1961
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1962
	}
1963
1964
	if (!$initialize)
1965
		return;
1966
1967
	// Check to see if we're forcing SSL
1968
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
1969
		!httpsOn() && SMF != 'SSI')
1970
	{
1971
		if (isset($_GET['sslRedirect']))
1972
		{
1973
			loadLanguage('Errors');
1974
			fatal_lang_error('login_ssl_required', false);
1975
		}
1976
1977
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
1978
	}
1979
1980
	// Check to see if they're accessing it from the wrong place.
1981
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1982
	{
1983
		$detected_url = httpsOn() ? 'https://' : 'http://';
1984
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1985
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1986
		if ($temp != '/')
1987
			$detected_url .= $temp;
1988
	}
1989
	if (isset($detected_url) && $detected_url != $boardurl)
1990
	{
1991
		// Try #1 - check if it's in a list of alias addresses.
1992
		if (!empty($modSettings['forum_alias_urls']))
1993
		{
1994
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1995
1996
			foreach ($aliases as $alias)
1997
			{
1998
				// Rip off all the boring parts, spaces, etc.
1999
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
2000
					$do_fix = true;
2001
			}
2002
		}
2003
2004
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
2005
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
2006
		{
2007
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
2008
			if (empty($_GET))
2009
				redirectexit('wwwRedirect');
2010
			else
2011
			{
2012
				$k = key($_GET);
2013
				$v = current($_GET);
2014
2015
				if ($k != 'wwwRedirect')
2016
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2017
			}
2018
		}
2019
2020
		// #3 is just a check for SSL...
2021
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2022
			$do_fix = true;
2023
2024
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2025
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
2026
		{
2027
			// Caching is good ;).
2028
			$oldurl = $boardurl;
2029
2030
			// Fix $boardurl and $scripturl.
2031
			$boardurl = $detected_url;
2032
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2033
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2034
2035
			// Fix the theme urls...
2036
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2037
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2038
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2039
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2040
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2041
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2042
2043
			// And just a few mod settings :).
2044
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2045
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2046
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2047
2048
			// Clean up after loadBoard().
2049
			if (isset($board_info['moderators']))
2050
			{
2051
				foreach ($board_info['moderators'] as $k => $dummy)
2052
				{
2053
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2054
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2055
				}
2056
			}
2057
			foreach ($context['linktree'] as $k => $dummy)
2058
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2059
		}
2060
	}
2061
	// Set up the contextual user array.
2062
	if (!empty($user_info))
2063
	{
2064
		$context['user'] = array(
2065
			'id' => $user_info['id'],
2066
			'is_logged' => !$user_info['is_guest'],
2067
			'is_guest' => &$user_info['is_guest'],
2068
			'is_admin' => &$user_info['is_admin'],
2069
			'is_mod' => &$user_info['is_mod'],
2070
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2071
			'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'])))),
2072
			'name' => $user_info['username'],
2073
			'language' => $user_info['language'],
2074
			'email' => $user_info['email'],
2075
			'ignoreusers' => $user_info['ignoreusers'],
2076
		);
2077
		if (!$context['user']['is_guest'])
2078
			$context['user']['name'] = $user_info['name'];
2079
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2080
			$context['user']['name'] = $txt['guest_title'];
2081
2082
		// Determine the current smiley set.
2083
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2084
		$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'];
2085
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2086
	}
2087
	else
2088
	{
2089
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2090
		$context['user'] = array(
2091
			'id' => -1,
2092
			'is_logged' => false,
2093
			'is_guest' => true,
2094
			'is_mod' => false,
2095
			'can_mod' => false,
2096
			'name' => $txt['guest_title'],
2097
			'language' => $language,
2098
			'email' => '',
2099
			'ignoreusers' => array(),
2100
		);
2101
		// Note we should stuff $user_info with some guest values also...
2102
		$user_info = array(
2103
			'id' => 0,
2104
			'is_guest' => true,
2105
			'is_admin' => false,
2106
			'is_mod' => false,
2107
			'username' => $txt['guest_title'],
2108
			'language' => $language,
2109
			'email' => '',
2110
			'smiley_set' => '',
2111
			'permissions' => array(),
2112
			'groups' => array(),
2113
			'ignoreusers' => array(),
2114
			'possibly_robot' => true,
2115
			'time_offset' => 0,
2116
			'time_format' => $modSettings['time_format'],
2117
		);
2118
	}
2119
2120
	// Some basic information...
2121
	if (!isset($context['html_headers']))
2122
		$context['html_headers'] = '';
2123
	if (!isset($context['javascript_files']))
2124
		$context['javascript_files'] = array();
2125
	if (!isset($context['css_files']))
2126
		$context['css_files'] = array();
2127
	if (!isset($context['css_header']))
2128
		$context['css_header'] = array();
2129
	if (!isset($context['javascript_inline']))
2130
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2131
	if (!isset($context['javascript_vars']))
2132
		$context['javascript_vars'] = array();
2133
2134
	$context['login_url'] = $scripturl . '?action=login2';
2135
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2136
	$context['session_var'] = $_SESSION['session_var'];
2137
	$context['session_id'] = $_SESSION['session_value'];
2138
	$context['forum_name'] = $mbname;
2139
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2140
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2141
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2142
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2143
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2144
	if (isset($modSettings['load_average']))
2145
		$context['load_average'] = $modSettings['load_average'];
2146
2147
	// Detect the browser. This is separated out because it's also used in attachment downloads
2148
	detectBrowser();
2149
2150
	// Set the top level linktree up.
2151
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2152
	if (empty($context['linktree']))
2153
		$context['linktree'] = array();
2154
	array_unshift($context['linktree'], array(
2155
		'url' => $scripturl,
2156
		'name' => $context['forum_name_html_safe']
2157
	));
2158
2159
	// This allows sticking some HTML on the page output - useful for controls.
2160
	$context['insert_after_template'] = '';
2161
2162
	if (!isset($txt))
2163
		$txt = array();
2164
2165
	$simpleActions = array(
2166
		'findmember',
2167
		'helpadmin',
2168
		'printpage',
2169
		'spellcheck',
2170
	);
2171
2172
	// Parent action => array of areas
2173
	$simpleAreas = array(
2174
		'profile' => array('popup', 'alerts_popup',),
2175
	);
2176
2177
	// Parent action => array of subactions
2178
	$simpleSubActions = array(
2179
		'pm' => array('popup',),
2180
		'signup' => array('usernamecheck'),
2181
	);
2182
2183
	// Extra params like ;preview ;js, etc.
2184
	$extraParams = array(
2185
		'preview',
2186
		'splitjs',
2187
	);
2188
2189
	// Actions that specifically uses XML output.
2190
	$xmlActions = array(
2191
		'quotefast',
2192
		'jsmodify',
2193
		'xmlhttp',
2194
		'post2',
2195
		'suggest',
2196
		'stats',
2197
		'notifytopic',
2198
		'notifyboard',
2199
	);
2200
2201
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2202
2203
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2204
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2205
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2206
2207
	// See if theres any extra param to check.
2208
	$requiresXML = false;
2209
	foreach ($extraParams as $key => $extra)
2210
		if (isset($_REQUEST[$extra]))
2211
			$requiresXML = true;
2212
2213
	// Output is fully XML, so no need for the index template.
2214
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2215
	{
2216
		loadLanguage('index+Modifications');
2217
		loadTemplate('Xml');
2218
		$context['template_layers'] = array();
2219
	}
2220
2221
	// These actions don't require the index template at all.
2222
	elseif (!empty($context['simple_action']))
2223
	{
2224
		loadLanguage('index+Modifications');
2225
		$context['template_layers'] = array();
2226
	}
2227
2228
	else
2229
	{
2230
		// Custom templates to load, or just default?
2231
		if (isset($settings['theme_templates']))
2232
			$templates = explode(',', $settings['theme_templates']);
2233
		else
2234
			$templates = array('index');
2235
2236
		// Load each template...
2237
		foreach ($templates as $template)
2238
			loadTemplate($template);
2239
2240
		// ...and attempt to load their associated language files.
2241
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2242
		loadLanguage($required_files, '', false);
2243
2244
		// Custom template layers?
2245
		if (isset($settings['theme_layers']))
2246
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2247
		else
2248
			$context['template_layers'] = array('html', 'body');
2249
	}
2250
2251
	// Initialize the theme.
2252
	loadSubTemplate('init', 'ignore');
2253
2254
	// Allow overriding the board wide time/number formats.
2255
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2256
		$user_info['time_format'] = $txt['time_format'];
2257
2258
	// Set the character set from the template.
2259
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2260
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2261
2262
	// Guests may still need a name.
2263
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2264
		$context['user']['name'] = $txt['guest_title'];
2265
2266
	// Any theme-related strings that need to be loaded?
2267
	if (!empty($settings['require_theme_strings']))
2268
		loadLanguage('ThemeStrings', '', false);
2269
2270
	// Make a special URL for the language.
2271
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2272
2273
	// And of course, let's load the default CSS file.
2274
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2275
2276
	// Here is my luvly Responsive CSS
2277
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2278
2279
	if ($context['right_to_left'])
2280
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2281
2282
	// We allow theme variants, because we're cool.
2283
	$context['theme_variant'] = '';
2284
	$context['theme_variant_url'] = '';
2285
	if (!empty($settings['theme_variants']))
2286
	{
2287
		// Overriding - for previews and that ilk.
2288
		if (!empty($_REQUEST['variant']))
2289
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2290
		// User selection?
2291
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2292
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2293
		// If not a user variant, select the default.
2294
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2295
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2296
2297
		// Do this to keep things easier in the templates.
2298
		$context['theme_variant'] = '_' . $context['theme_variant'];
2299
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2300
2301
		if (!empty($context['theme_variant']))
2302
		{
2303
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2304
			if ($context['right_to_left'])
2305
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2306
		}
2307
	}
2308
2309
	// Let's be compatible with old themes!
2310
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2311
		$context['template_layers'] = array('main');
2312
2313
	$context['tabindex'] = 1;
2314
2315
	// Compatibility.
2316
	if (!isset($settings['theme_version']))
2317
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2318
2319
	// Default JS variables for use in every theme
2320
	$context['javascript_vars'] = array(
2321
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2322
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2323
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2324
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2325
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2326
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2327
		'smf_scripturl' => '"' . $scripturl . '"',
2328
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2329
		'smf_charset' => '"' . $context['character_set'] . '"',
2330
		'smf_session_id' => '"' . $context['session_id'] . '"',
2331
		'smf_session_var' => '"' . $context['session_var'] . '"',
2332
		'smf_member_id' => $context['user']['id'],
2333
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2334
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2335
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2336
		'smf_txt_expand' => JavaScriptEscape($txt['code_expand']),
2337
		'smf_txt_shrink' => JavaScriptEscape($txt['code_shrink']),
2338
		'smf_quote_expand' => !empty($modSettings['quote_expand']) ? $modSettings['quote_expand'] : 'false',
2339
	);
2340
2341
	// Add the JQuery library to the list of files to load.
2342
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2343
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2344
2345
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2346
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2347
2348
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2349
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2350
2351
	// Auto loading? template_javascript() will take care of the local half of this.
2352
	else
2353
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2354
2355
	// Queue our JQuery plugins!
2356
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2357
	if (!$user_info['is_guest'])
2358
	{
2359
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2360
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2361
	}
2362
2363
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2364
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2365
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2366
2367
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2368
	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())
2369
	{
2370
		if (isBrowser('possibly_robot'))
2371
		{
2372
			// @todo Maybe move this somewhere better?!
2373
			require_once($sourcedir . '/ScheduledTasks.php');
2374
2375
			// What to do, what to do?!
2376
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2377
				AutoTask();
2378
			else
2379
				ReduceMailQueue();
2380
		}
2381
		else
2382
		{
2383
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2384
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2385
2386
			addInlineJavaScript('
2387
		function smfAutoTask()
2388
		{
2389
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2390
		}
2391
		window.setTimeout("smfAutoTask();", 1);');
2392
		}
2393
	}
2394
2395
	// And we should probably trigger the cron too.
2396
	if (empty($modSettings['cron_is_real_cron']))
2397
	{
2398
		$ts = time();
2399
		$ts -= $ts % 15;
2400
		addInlineJavaScript('
2401
	function triggerCron()
2402
	{
2403
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2404
	}
2405
	window.setTimeout(triggerCron, 1);', true);
2406
	}
2407
2408
	// Filter out the restricted boards from the linktree
2409
	if (!$user_info['is_admin'] && !empty($board))
2410
	{
2411
		foreach ($context['linktree'] as $k => $element)
2412
		{
2413
			if (!empty($element['groups']) &&
2414
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2415
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2416
			{
2417
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2418
				$context['linktree'][$k]['extra_before'] = '<i>';
2419
				$context['linktree'][$k]['extra_after'] = '</i>';
2420
				unset($context['linktree'][$k]['url']);
2421
			}
2422
		}
2423
	}
2424
2425
	// Any files to include at this point?
2426
	if (!empty($modSettings['integrate_theme_include']))
2427
	{
2428
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2429
		foreach ($theme_includes as $include)
2430
		{
2431
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2432
			if (file_exists($include))
2433
				require_once($include);
2434
		}
2435
	}
2436
2437
	// Call load theme integration functions.
2438
	call_integration_hook('integrate_load_theme');
2439
2440
	// We are ready to go.
2441
	$context['theme_loaded'] = true;
2442
}
2443
2444
/**
2445
 * Load a template - if the theme doesn't include it, use the default.
2446
 * What this function does:
2447
 *  - loads a template file with the name template_name from the current, default, or base theme.
2448
 *  - detects a wrong default theme directory and tries to work around it.
2449
 *
2450
 * @uses the template_include() function to include the file.
2451
 * @param string $template_name The name of the template to load
2452
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2453
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2454
 * @return boolean Whether or not the template was loaded
2455
 */
2456
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2457
{
2458
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2459
2460
	// Do any style sheets first, cause we're easy with those.
2461
	if (!empty($style_sheets))
2462
	{
2463
		if (!is_array($style_sheets))
2464
			$style_sheets = array($style_sheets);
2465
2466
		foreach ($style_sheets as $sheet)
2467
			loadCSSFile($sheet . '.css', array(), $sheet);
2468
	}
2469
2470
	// No template to load?
2471
	if ($template_name === false)
2472
		return true;
2473
2474
	$loaded = false;
2475
	foreach ($settings['template_dirs'] as $template_dir)
2476
	{
2477
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2478
		{
2479
			$loaded = true;
2480
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2481
			break;
2482
		}
2483
	}
2484
2485
	if ($loaded)
2486
	{
2487
		if ($db_show_debug === true)
2488
			$context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')';
2489
2490
		// If they have specified an initialization function for this template, go ahead and call it now.
2491
		if (function_exists('template_' . $template_name . '_init'))
2492
			call_user_func('template_' . $template_name . '_init');
2493
	}
2494
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2495
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2496
	{
2497
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2498
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2499
2500
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2501
		{
2502
			loadLanguage('Errors');
2503
			echo '
2504
<div class="alert errorbox">
2505
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2506
</div>';
2507
		}
2508
2509
		loadTemplate($template_name);
2510
	}
2511
	// Cause an error otherwise.
2512
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2513
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2514
	elseif ($fatal)
2515
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load Themes/default/%s.template.php!', (string) $template_name), 'template'));
0 ignored issues
show
Using exit here is not recommended.

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

Loading history...
2516
	else
2517
		return false;
2518
}
2519
2520
/**
2521
 * Load a sub-template.
2522
 * What it does:
2523
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2524
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2525
 *	for debugging purposes.
2526
 *
2527
 * @todo get rid of reading $_REQUEST directly
2528
 *
2529
 * @param string $sub_template_name The name of the sub-template to load
2530
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2531
 */
2532
function loadSubTemplate($sub_template_name, $fatal = false)
2533
{
2534
	global $context, $txt, $db_show_debug;
2535
2536
	if ($db_show_debug === true)
2537
		$context['debug']['sub_templates'][] = $sub_template_name;
2538
2539
	// Figure out what the template function is named.
2540
	$theme_function = 'template_' . $sub_template_name;
2541
	if (function_exists($theme_function))
2542
		$theme_function();
2543
	elseif ($fatal === false)
2544
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2545
	elseif ($fatal !== 'ignore')
2546
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template'));
0 ignored issues
show
Using exit here is not recommended.

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

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

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

Loading history...
3361
	}
3362
}
3363
3364
/**
3365
 * Initialize a database connection.
3366
 */
3367
function loadDatabase()
3368
{
3369
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3370
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port, $db_mb4;
3371
3372
	// Figure out what type of database we are using.
3373
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3374
		$db_type = 'mysql';
3375
3376
	// Load the file for the database.
3377
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3378
3379
	$db_options = array();
3380
3381
	// Add in the port if needed
3382
	if (!empty($db_port))
3383
		$db_options['port'] = $db_port;
3384
3385
	if (!empty($db_mb4))
3386
		$db_options['db_mb4'] = $db_mb4;
3387
3388
	// 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.
3389
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3390
	{
3391
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3392
3393
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3394
	}
3395
3396
	// Either we aren't in SSI mode, or it failed.
3397
	if (empty($db_connection))
3398
	{
3399
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3400
3401
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3402
	}
3403
3404
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3405
	if (!$db_connection)
3406
		display_db_error();
3407
3408
	// If in SSI mode fix up the prefix.
3409
	if (SMF == 'SSI')
3410
		db_fix_prefix($db_prefix, $db_name);
3411
}
3412
3413
/**
3414
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3415
 *
3416
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
3417
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3418
 * @return object|false A object of $cacheAPI, or False on failure.
3419
 */
3420
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3421
{
3422
	global $sourcedir, $cacheAPI, $cache_accelerator, $cache_enable;
3423
3424
	// is caching enabled?
3425
	if (empty($cache_enable) && empty($overrideCache))
3426
		return false;
3427
3428
	// Not overriding this and we have a cacheAPI, send it back.
3429
	if (empty($overrideCache) && is_object($cacheAPI))
3430
		return $cacheAPI;
3431
	elseif (is_null($cacheAPI))
3432
		$cacheAPI = false;
3433
3434
	// Make sure our class is in session.
3435
	require_once($sourcedir . '/Class-CacheAPI.php');
3436
3437
	// What accelerator we are going to try.
3438
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : (!empty($cache_accelerator) ? $cache_accelerator : 'smf');
3439
	$tryAccelerator = strtolower($tryAccelerator);
3440
3441
	// Do some basic tests.
3442
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3443
	{
3444
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3445
3446
		$cache_class_name = $tryAccelerator . '_cache';
3447
		$testAPI = new $cache_class_name();
3448
3449
		// No Support?  NEXT!
3450
		if (!$testAPI->isSupported())
3451
		{
3452
			// Can we save ourselves?
3453
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3454
				return loadCacheAccelerator(null, false);
3455
			return false;
3456
		}
3457
3458
		// Connect up to the accelerator.
3459
		$testAPI->connect();
3460
3461
		// Don't set this if we are overriding the cache.
3462
		if (is_null($overrideCache))
3463
		{
3464
			$cacheAPI = $testAPI;
3465
			return $cacheAPI;
3466
		}
3467
		else
3468
			return $testAPI;
3469
	}
3470
}
3471
3472
/**
3473
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3474
 *
3475
 * @param string $key The key for this entry
3476
 * @param string $file The file associated with this entry
3477
 * @param string $function The function to call
3478
 * @param array $params Parameters to be passed to the specified function
3479
 * @param int $level The cache level
3480
 * @return string The cached data
3481
 */
3482
function cache_quick_get($key, $file, $function, $params, $level = 1)
3483
{
3484
	global $modSettings, $sourcedir, $cache_enable;
3485
3486
	// @todo Why are we doing this if caching is disabled?
3487
3488
	if (function_exists('call_integration_hook'))
3489
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3490
3491
	/* Refresh the cache if either:
3492
		1. Caching is disabled.
3493
		2. The cache level isn't high enough.
3494
		3. The item has not been cached or the cached item expired.
3495
		4. The cached item has a custom expiration condition evaluating to true.
3496
		5. The expire time set in the cache item has passed (needed for Zend).
3497
	*/
3498
	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()))
3499
	{
3500
		require_once($sourcedir . '/' . $file);
3501
		$cache_block = call_user_func_array($function, $params);
3502
3503
		if (!empty($cache_enable) && $cache_enable >= $level)
3504
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3505
	}
3506
3507
	// Some cached data may need a freshening up after retrieval.
3508
	if (!empty($cache_block['post_retri_eval']))
3509
		eval($cache_block['post_retri_eval']);
3510
3511
	if (function_exists('call_integration_hook'))
3512
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3513
3514
	return $cache_block['data'];
3515
}
3516
3517
/**
3518
 * Puts value in the cache under key for ttl seconds.
3519
 *
3520
 * - It may "miss" so shouldn't be depended on
3521
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3522
 * - It supports:
3523
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3524
 *	 memcache: https://php.net/memcache
3525
 *	 APC: https://php.net/apc
3526
 *   APCu: https://php.net/book.apcu
3527
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3528
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3529
 *
3530
 * @param string $key A key for this value
3531
 * @param mixed $value The data to cache
3532
 * @param int $ttl How long (in seconds) the data should be cached for
3533
 */
3534
function cache_put_data($key, $value, $ttl = 120)
3535
{
3536
	global $smcFunc, $cache_enable, $cacheAPI;
3537
	global $cache_hits, $cache_count, $db_show_debug;
3538
3539
	if (empty($cache_enable) || empty($cacheAPI))
3540
		return;
3541
3542
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3543
	if (isset($db_show_debug) && $db_show_debug === true)
3544
	{
3545
		$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)));
3546
		$st = microtime(true);
3547
	}
3548
3549
	// The API will handle the rest.
3550
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3551
	$cacheAPI->putData($key, $value, $ttl);
3552
3553
	if (function_exists('call_integration_hook'))
3554
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3555
3556
	if (isset($db_show_debug) && $db_show_debug === true)
3557
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
3558
}
3559
3560
/**
3561
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3562
 * - It may often "miss", so shouldn't be depended on.
3563
 * - It supports the same as cache_put_data().
3564
 *
3565
 * @param string $key The key for the value to retrieve
3566
 * @param int $ttl The maximum age of the cached data
3567
 * @return string|null The cached data or null if nothing was loaded
3568
 */
3569
function cache_get_data($key, $ttl = 120)
3570
{
3571
	global $smcFunc, $cache_enable, $cacheAPI;
3572
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
3573
3574
	if (empty($cache_enable) || empty($cacheAPI))
3575
		return;
3576
3577
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3578
	if (isset($db_show_debug) && $db_show_debug === true)
3579
	{
3580
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3581
		$st = microtime(true);
3582
		$original_key = $key;
3583
	}
3584
3585
	// Ask the API to get the data.
3586
	$value = $cacheAPI->getData($key, $ttl);
3587
3588
	if (isset($db_show_debug) && $db_show_debug === true)
3589
	{
3590
		$cache_hits[$cache_count]['t'] = microtime(true) - $st;
3591
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3592
3593
		if (empty($value))
3594
		{
3595
			if (!is_array($cache_misses))
3596
				$cache_misses = array();
3597
3598
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3599
			$cache_misses[$cache_count_misses] = array('k' => $original_key, 'd' => 'get');
3600
		}
3601
	}
3602
3603
	if (function_exists('call_integration_hook') && isset($value))
3604
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3605
3606
	return empty($value) ? null : (isset($smcFunc['json_decode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3607
}
3608
3609
/**
3610
 * Empty out the cache in use as best it can
3611
 *
3612
 * It may only remove the files of a certain type (if the $type parameter is given)
3613
 * Type can be user, data or left blank
3614
 * 	- user clears out user data
3615
 *  - data clears out system / opcode data
3616
 *  - If no type is specified will perform a complete cache clearing
3617
 * For cache engines that do not distinguish on types, a full cache flush will be done
3618
 *
3619
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3620
 */
3621
function clean_cache($type = '')
3622
{
3623
	global $cacheAPI;
3624
3625
	// If we can't get to the API, can't do this.
3626
	if (empty($cacheAPI))
3627
		return;
3628
3629
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3630
	$cacheAPI->cleanCache($type);
3631
3632
	call_integration_hook('integrate_clean_cache');
3633
	clearstatcache();
3634
}
3635
3636
/**
3637
 * Helper function to set an array of data for an user's avatar.
3638
 *
3639
 * Makes assumptions based on the data provided, the following keys are required:
3640
 * - avatar The raw "avatar" column in members table
3641
 * - email The user's email. Used to get the gravatar info
3642
 * - filename The attachment filename
3643
 *
3644
 * @param array $data An array of raw info
3645
 * @return array An array of avatar data
3646
 */
3647
function set_avatar_data($data = array())
3648
{
3649
	global $modSettings, $smcFunc, $user_info;
3650
3651
	// Come on!
3652
	if (empty($data))
3653
		return array();
3654
3655
	// Set a nice default var.
3656
	$image = '';
3657
3658
	// Gravatar has been set as mandatory!
3659
	if (!empty($modSettings['gravatarOverride']))
3660
	{
3661
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3662
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3663
3664
		elseif (!empty($data['email']))
3665
			$image = get_gravatar_url($data['email']);
3666
	}
3667
3668
	// Look if the user has a gravatar field or has set an external url as avatar.
3669
	else
3670
	{
3671
		// So it's stored in the member table?
3672
		if (!empty($data['avatar']))
3673
		{
3674
			// Gravatar.
3675
			if (stristr($data['avatar'], 'gravatar://'))
3676
			{
3677
				if ($data['avatar'] == 'gravatar://')
3678
					$image = get_gravatar_url($data['email']);
3679
3680
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3681
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3682
			}
3683
3684
			// External url.
3685
			else
3686
				$image = parse_url($data['avatar'], PHP_URL_SCHEME) !== null ? get_proxied_url($data['avatar']) : $modSettings['avatar_url'] . '/' . $data['avatar'];
3687
		}
3688
3689
		// Perhaps this user has an attachment as avatar...
3690
		elseif (!empty($data['filename']))
3691
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3692
3693
		// Right... no avatar... use our default image.
3694
		else
3695
			$image = $modSettings['avatar_url'] . '/default.png';
3696
	}
3697
3698
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3699
3700
	// 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.
3701
	if (!empty($image))
3702
		return array(
3703
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3704
			'image' => '<img class="avatar" src="' . $image . '" alt="">',
3705
			'href' => $image,
3706
			'url' => $image,
3707
		);
3708
3709
	// Fallback to make life easier for everyone...
3710
	else
3711
		return array(
3712
			'name' => '',
3713
			'image' => '',
3714
			'href' => '',
3715
			'url' => '',
3716
		);
3717
}
3718
3719
/**
3720
 * Gets, and if necessary creates, the authentication secret to use for cookies, tokens, etc.
3721
 *
3722
 * Note: Never use the $auth_secret variable directly. Always call this function instead.
3723
 *
3724
 * @return string The authentication secret.
3725
 */
3726
function get_auth_secret()
3727
{
3728
	global $auth_secret, $sourcedir, $smcFunc;
3729
3730
	if (empty($auth_secret))
3731
	{
3732
		$auth_secret = bin2hex($smcFunc['random_bytes'](32));
3733
3734
		// It is important to store this in Settings.php, not the database.
3735
		require_once($sourcedir . '/Subs-Admin.php');
3736
		updateSettingsFile(array('auth_secret' => $auth_secret));
3737
	}
3738
3739
	return $auth_secret;
3740
}
3741
3742
?>