loadCSSFile()   D
last analyzed

Complexity

Conditions 27

Size

Total Lines 76
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 27
eloc 39
nop 3
dl 0
loc 76
rs 4.1666
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

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

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

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

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

Loading history...
171
		{
172
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
173
174
			if (strlen($needle) === 1)
175
			{
176
				$result = array_search($needle, array_slice($haystack_arr, $offset));
0 ignored issues
show
Bug introduced by
It seems like $haystack_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

176
				$result = array_search($needle, array_slice(/** @scrutinizer ignore-type */ $haystack_arr, $offset));
Loading history...
177
				return is_int($result) ? $result + $offset : false;
178
			}
179
			else
180
			{
181
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
182
				$needle_size = count($needle_arr);
0 ignored issues
show
Bug introduced by
It seems like $needle_arr can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

182
				$needle_size = count(/** @scrutinizer ignore-type */ $needle_arr);
Loading history...
183
184
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
185
				while ((int) $result === $result)
186
				{
187
					$offset += $result;
188
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
189
						return $offset;
190
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
191
				}
192
				return false;
193
			}
194
		},
195
		'substr' => function($string, $start, $length = null) use ($utf8, $ent_check, $ent_list, $modSettings)
0 ignored issues
show
Unused Code introduced by
The import $modSettings is not used and could be removed.

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

Loading history...
196
		{
197
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
198
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
0 ignored issues
show
Bug introduced by
It seems like $ent_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

198
			return $length === null ? implode('', array_slice(/** @scrutinizer ignore-type */ $ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
Loading history...
199
		},
200
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
201
		{
202
			if (!function_exists('mb_strtolower'))
203
			{
204
				require_once($sourcedir . '/Subs-Charset.php');
205
				return utf8_strtolower($string);
206
			}
207
208
			return mb_strtolower($string, 'UTF-8');
209
		} : 'strtolower',
210
		'strtoupper' => $utf8 ? function($string)
211
		{
212
			global $sourcedir;
213
214
			if (!function_exists('mb_strtolower'))
215
			{
216
				require_once($sourcedir . '/Subs-Charset.php');
217
				return utf8_strtoupper($string);
218
			}
219
220
			return mb_strtoupper($string, 'UTF-8');
221
		} : 'strtoupper',
222
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
223
		{
224
			$string = $ent_check($string);
225
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
226
			$string = $matches[0];
227
			while (strlen($string) > $length)
228
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
229
			return $string;
230
		},
231
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
232
		{
233
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
234
		} : 'ucfirst',
235
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
236
		{
237
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
238
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

238
			for ($i = 0, $n = count(/** @scrutinizer ignore-type */ $words); $i < $n; $i += 2)
Loading history...
239
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
240
			return implode('', $words);
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

240
			return implode('', /** @scrutinizer ignore-type */ $words);
Loading history...
241
		} : 'ucwords',
242
		'json_decode' => 'smf_json_decode',
243
		'json_encode' => 'json_encode',
244
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
245
		{
246
			global $sourcedir;
247
248
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
249
			if (!is_callable('random_int'))
250
				require_once($sourcedir . '/random_compat/random.php');
251
252
			return random_int($min, $max);
253
		},
254
		'random_bytes' => function($length = 64)
255
		{
256
			global $sourcedir;
257
258
			if (!is_callable('random_bytes'))
259
				require_once($sourcedir . '/random_compat/random.php');
260
261
			// Make sure length is valid
262
			$length = max(1, (int) $length);
263
264
			return random_bytes($length);
265
		},
266
	);
267
268
	// Setting the timezone is a requirement for some functions.
269
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
It seems like timezone_identifiers_list() can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

Loading history...
1131
		}
1132
		elseif ($board_info['error'] == 'post_in_redirect')
1133
		{
1134
			// Slightly different error message here...
1135
			fatal_lang_error('cannot_post_redirect', false);
1136
		}
1137
		elseif ($user_info['is_guest'])
1138
		{
1139
			loadLanguage('Errors');
1140
			is_not_guest($txt['topic_gone']);
1141
		}
1142
		else
1143
			fatal_lang_error('topic_gone', false);
1144
	}
1145
1146
	if ($user_info['is_mod'])
1147
		$user_info['groups'][] = 3;
1148
}
1149
1150
/**
1151
 * Load this user's permissions.
1152
 */
1153
function loadPermissions()
1154
{
1155
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1156
1157
	if ($user_info['is_admin'])
1158
	{
1159
		banPermissions();
1160
		return;
1161
	}
1162
1163
	if (!empty($cache_enable))
1164
	{
1165
		$cache_groups = $user_info['groups'];
1166
		asort($cache_groups);
1167
		$cache_groups = implode(',', $cache_groups);
1168
		// If it's a spider then cache it different.
1169
		if ($user_info['possibly_robot'])
1170
			$cache_groups .= '-spider';
1171
1172
		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
Bug introduced by
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...
1173
		{
1174
			list ($user_info['permissions']) = $temp;
1175
			banPermissions();
1176
1177
			return;
1178
		}
1179
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
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...
1180
			list ($user_info['permissions'], $removals) = $temp;
1181
	}
1182
1183
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1184
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1185
1186
	if (empty($user_info['permissions']))
1187
	{
1188
		// Get the general permissions.
1189
		$request = $smcFunc['db_query']('', '
1190
			SELECT permission, add_deny
1191
			FROM {db_prefix}permissions
1192
			WHERE id_group IN ({array_int:member_groups})
1193
				' . $spider_restrict,
1194
			array(
1195
				'member_groups' => $user_info['groups'],
1196
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1197
			)
1198
		);
1199
		$removals = array();
1200
		while ($row = $smcFunc['db_fetch_assoc']($request))
1201
		{
1202
			if (empty($row['add_deny']))
1203
				$removals[] = $row['permission'];
1204
			else
1205
				$user_info['permissions'][] = $row['permission'];
1206
		}
1207
		$smcFunc['db_free_result']($request);
1208
1209
		if (isset($cache_groups))
1210
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1211
	}
1212
1213
	// Get the board permissions.
1214
	if (!empty($board))
1215
	{
1216
		// Make sure the board (if any) has been loaded by loadBoard().
1217
		if (!isset($board_info['profile']))
1218
			fatal_lang_error('no_board');
1219
1220
		$request = $smcFunc['db_query']('', '
1221
			SELECT permission, add_deny
1222
			FROM {db_prefix}board_permissions
1223
			WHERE (id_group IN ({array_int:member_groups})
1224
				' . $spider_restrict . ')
1225
				AND id_profile = {int:id_profile}',
1226
			array(
1227
				'member_groups' => $user_info['groups'],
1228
				'id_profile' => $board_info['profile'],
1229
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1230
			)
1231
		);
1232
		while ($row = $smcFunc['db_fetch_assoc']($request))
1233
		{
1234
			if (empty($row['add_deny']))
1235
				$removals[] = $row['permission'];
1236
			else
1237
				$user_info['permissions'][] = $row['permission'];
1238
		}
1239
		$smcFunc['db_free_result']($request);
1240
	}
1241
1242
	// Remove all the permissions they shouldn't have ;).
1243
	if (!empty($modSettings['permission_enable_deny']))
1244
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $removals does not seem to be defined for all execution paths leading up to this point.
Loading history...
1245
1246
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1247
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1248
1249
	// Banned?  Watch, don't touch..
1250
	banPermissions();
1251
1252
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1253
	if (!$user_info['is_guest'])
1254
	{
1255
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1256
		{
1257
			require_once($sourcedir . '/Subs-Auth.php');
1258
			rebuildModCache();
1259
		}
1260
		else
1261
			$user_info['mod_cache'] = $_SESSION['mc'];
1262
1263
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1264
		// For example this drastically simplifies certain changes to the profile area.
1265
		$user_info['permissions'][] = 'is_not_guest';
1266
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1267
		$user_info['permissions'][] = 'profile_view_own';
1268
		if (in_array('profile_view', $user_info['permissions']))
1269
			$user_info['permissions'][] = 'profile_view_any';
1270
	}
1271
}
1272
1273
/**
1274
 * Loads an array of users' data by ID or member_name.
1275
 *
1276
 * @param array|string $users An array of users by id or name or a single username/id
1277
 * @param bool $is_name Whether $users contains names
1278
 * @param string $set What kind of data to load (normal, profile, minimal)
1279
 * @return array The ids of the members loaded
1280
 */
1281
function loadMemberData($users, $is_name = false, $set = 'normal')
1282
{
1283
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1284
	global $image_proxy_enabled, $user_info, $cache_enable;
1285
1286
	// Can't just look for no users :P.
1287
	if (empty($users))
1288
		return array();
1289
1290
	// Pass the set value
1291
	$context['loadMemberContext_set'] = $set;
1292
1293
	// Make sure it's an array.
1294
	$users = !is_array($users) ? array($users) : array_unique($users);
1295
	$loaded_ids = array();
1296
1297
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1298
	{
1299
		$users = array_values($users);
1300
		for ($i = 0, $n = count($users); $i < $n; $i++)
1301
		{
1302
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1303
			if ($data == null)
0 ignored issues
show
Bug introduced by
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...
1304
				continue;
1305
1306
			$loaded_ids[] = $data['id_member'];
1307
			$user_profile[$data['id_member']] = $data;
1308
			unset($users[$i]);
1309
		}
1310
	}
1311
1312
	// Used by default
1313
	$select_columns = '
1314
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1315
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1316
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1317
			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.show_online,
1318
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1319
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1320
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1321
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1322
	$select_tables = '
1323
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1324
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1325
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1326
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1327
1328
	// We add or replace according the the set
1329
	switch ($set)
1330
	{
1331
		case 'normal':
1332
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1333
			break;
1334
		case 'profile':
1335
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1336
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1337
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1338
			break;
1339
		case 'minimal':
1340
			$select_columns = '
1341
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1342
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1343
			$select_tables = '';
1344
			break;
1345
		default:
1346
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1347
	}
1348
1349
	// Allow mods to easily add to the selected member data
1350
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1351
1352
	if (!empty($users))
1353
	{
1354
		// Load the member's data.
1355
		$request = $smcFunc['db_query']('', '
1356
			SELECT' . $select_columns . '
1357
			FROM {db_prefix}members AS mem' . $select_tables . '
1358
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1359
			array(
1360
				'blank_string' => '',
1361
				'users' => $users,
1362
			)
1363
		);
1364
		$new_loaded_ids = array();
1365
		while ($row = $smcFunc['db_fetch_assoc']($request))
1366
		{
1367
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1368
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1369
1370
			// Take care of proxying avatar if required, do this here for maximum reach
1371
			if ($image_proxy_enabled && !empty($row['avatar']) && stripos($row['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
1372
				$row['avatar'] = get_proxied_url($row['avatar']);
1373
1374
			// Keep track of the member's normal member group
1375
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1376
1377
			if (isset($row['member_ip']))
1378
				$row['member_ip'] = inet_dtop($row['member_ip']);
1379
			if (isset($row['member_ip2']))
1380
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1381
			$row['id_member'] = (int) $row['id_member'];
1382
			$new_loaded_ids[] = $row['id_member'];
1383
			$loaded_ids[] = $row['id_member'];
1384
			$row['options'] = array();
1385
			$user_profile[$row['id_member']] = $row;
1386
		}
1387
		$smcFunc['db_free_result']($request);
1388
	}
1389
1390
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1391
	{
1392
		$request = $smcFunc['db_query']('', '
1393
			SELECT id_member, variable, value
1394
			FROM {db_prefix}themes
1395
			WHERE id_member IN ({array_int:loaded_ids})',
1396
			array(
1397
				'loaded_ids' => $new_loaded_ids,
1398
			)
1399
		);
1400
		while ($row = $smcFunc['db_fetch_assoc']($request))
1401
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1402
		$smcFunc['db_free_result']($request);
1403
	}
1404
1405
	$additional_mods = array();
1406
1407
	// Are any of these users in groups assigned to moderate this board?
1408
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1409
	{
1410
		foreach ($loaded_ids as $a_member)
1411
		{
1412
			if (!empty($user_profile[$a_member]['additional_groups']))
1413
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1414
			else
1415
				$groups = array($user_profile[$a_member]['id_group']);
1416
1417
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1418
1419
			if (!empty($temp))
1420
			{
1421
				$additional_mods[] = $a_member;
1422
			}
1423
		}
1424
	}
1425
1426
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1427
	{
1428
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1429
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1430
	}
1431
1432
	// Are we loading any moderators?  If so, fix their group data...
1433
	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)
1434
	{
1435
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
0 ignored issues
show
Bug introduced by
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...
1436
		{
1437
			$request = $smcFunc['db_query']('', '
1438
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1439
				FROM {db_prefix}membergroups
1440
				WHERE id_group = {int:moderator_group}
1441
				LIMIT 1',
1442
				array(
1443
					'moderator_group' => 3,
1444
				)
1445
			);
1446
			$row = $smcFunc['db_fetch_assoc']($request);
1447
			$smcFunc['db_free_result']($request);
1448
1449
			cache_put_data('moderator_group_info', $row, 480);
1450
		}
1451
1452
		foreach ($temp_mods as $id)
1453
		{
1454
			// By popular demand, don't show admins or global moderators as moderators.
1455
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1456
				$user_profile[$id]['member_group'] = $row['member_group'];
1457
1458
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1459
			if (!empty($row['icons']))
1460
				$user_profile[$id]['icons'] = $row['icons'];
1461
			if (!empty($row['member_group_color']))
1462
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1463
		}
1464
	}
1465
1466
	return $loaded_ids;
1467
}
1468
1469
/**
1470
 * Loads the user's basic values... meant for template/theme usage.
1471
 *
1472
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1473
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1474
 * @return boolean Whether or not the data was loaded successfully
1475
 */
1476
function loadMemberContext($user, $display_custom_fields = false)
1477
{
1478
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1479
	global $context, $modSettings, $settings, $smcFunc;
1480
	static $dataLoaded = array();
1481
	static $loadedLanguages = array();
1482
1483
	// If this person's data is already loaded, skip it.
1484
	if (isset($dataLoaded[$user]))
1485
		return true;
1486
1487
	// We can't load guests or members not loaded by loadMemberData()!
1488
	if ($user == 0)
1489
		return false;
1490
	if (!isset($user_profile[$user]))
1491
	{
1492
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1493
		return false;
1494
	}
1495
1496
	// Well, it's loaded now anyhow.
1497
	$dataLoaded[$user] = true;
1498
	$profile = $user_profile[$user];
1499
1500
	// Censor everything.
1501
	censorText($profile['signature']);
1502
	censorText($profile['personal_text']);
1503
1504
	// Set things up to be used before hand.
1505
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1506
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1507
1508
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $profile['is_online'] = ...ofile['is_online'] > 0), Probably Intended Meaning: $profile['is_online'] = ...ofile['is_online'] > 0)
Loading history...
1509
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1510
	// Setup the buddy status here (One whole in_array call saved :P)
1511
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1512
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1513
1514
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1515
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1516
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1517
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1518
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1519
	else //not set, bye bye
1520
		$group_icon_url = '';
1521
1522
	// These minimal values are always loaded
1523
	$memberContext[$user] = array(
1524
		'username' => $profile['member_name'],
1525
		'name' => $profile['real_name'],
1526
		'id' => $profile['id_member'],
1527
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1528
		'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>',
1529
		'email' => $profile['email_address'],
1530
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1531
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1532
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1533
	);
1534
1535
	// If the set isn't minimal then load the monstrous array.
1536
	if ($context['loadMemberContext_set'] != 'minimal')
1537
	{
1538
		// Go the extra mile and load the user's native language name.
1539
		if (empty($loadedLanguages))
1540
			$loadedLanguages = getLanguages();
1541
1542
		// Figure out the new time offset.
1543
		if (!empty($profile['timezone']))
1544
		{
1545
			// Get the offsets from UTC for the server, then for the user.
1546
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1547
			$tz_user = new DateTimeZone($profile['timezone']);
1548
			$time_system = new DateTime('now', $tz_system);
1549
			$time_user = new DateTime('now', $tz_user);
1550
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1551
		}
1552
		else
1553
		{
1554
			// !!! Compatibility.
1555
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1556
		}
1557
1558
		$memberContext[$user] += array(
1559
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1560
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1561
			'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>',
1562
			'is_buddy' => $profile['buddy'],
1563
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1564
			'buddies' => $buddy_list,
1565
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1566
			'blurb' => $profile['personal_text'],
1567
			'website' => array(
1568
				'title' => $profile['website_title'],
1569
				'url' => $profile['website_url'],
1570
			),
1571
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1572
			'signature' => $profile['signature'],
1573
			'real_posts' => $profile['posts'],
1574
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1575
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1576
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1577
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1578
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1579
			'online' => array(
1580
				'is_online' => $profile['is_online'],
1581
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1582
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1583
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1584
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1585
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1586
			),
1587
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1588
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1589
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1590
			'options' => $profile['options'],
1591
			'is_guest' => false,
1592
			'primary_group' => $profile['primary_group'],
1593
			'group' => $profile['member_group'],
1594
			'group_color' => $profile['member_group_color'],
1595
			'group_id' => $profile['id_group'],
1596
			'post_group' => $profile['post_group'],
1597
			'post_group_color' => $profile['post_group_color'],
1598
			'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]),
1599
			'warning' => $profile['warning'],
1600
			'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' : (''))),
1601
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1602
			'custom_fields' => array(),
1603
		);
1604
	}
1605
1606
	// If the set isn't minimal then load their avatar as well.
1607
	if ($context['loadMemberContext_set'] != 'minimal')
1608
	{
1609
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1610
		{
1611
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1612
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1613
			else
1614
				$image = get_gravatar_url($profile['email_address']);
1615
		}
1616
		else
1617
		{
1618
			// So it's stored in the member table?
1619
			if (!empty($profile['avatar']))
1620
			{
1621
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1622
			}
1623
			elseif (!empty($profile['filename']))
1624
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1625
			// Right... no avatar...use the default one
1626
			else
1627
				$image = $modSettings['avatar_url'] . '/default.png';
1628
		}
1629
		if (!empty($image))
1630
			$memberContext[$user]['avatar'] = array(
1631
				'name' => $profile['avatar'],
1632
				'image' => '<img class="avatar" src="' . $image . '" alt="">',
1633
				'href' => $image,
1634
				'url' => $image,
1635
			);
1636
	}
1637
1638
	// Are we also loading the members custom fields into context?
1639
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1640
	{
1641
		$memberContext[$user]['custom_fields'] = array();
1642
1643
		if (!isset($context['display_fields']))
1644
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1645
1646
		foreach ($context['display_fields'] as $custom)
1647
		{
1648
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1649
				continue;
1650
1651
			$value = $profile['options'][$custom['col_name']];
1652
1653
			$fieldOptions = array();
1654
			$currentKey = 0;
1655
1656
			// Create a key => value array for multiple options fields
1657
			if (!empty($custom['options']))
1658
				foreach ($custom['options'] as $k => $v)
1659
				{
1660
					$fieldOptions[] = $v;
1661
					if (empty($currentKey))
1662
						$currentKey = $v == $value ? $k : 0;
1663
				}
1664
1665
			// BBC?
1666
			if ($custom['bbc'])
1667
				$value = parse_bbc($value);
1668
1669
			// ... or checkbox?
1670
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1671
				$value = $value ? $txt['yes'] : $txt['no'];
1672
1673
			// Enclosing the user input within some other text?
1674
			if (!empty($custom['enclose']))
1675
				$value = strtr($custom['enclose'], array(
1676
					'{SCRIPTURL}' => $scripturl,
1677
					'{IMAGES_URL}' => $settings['images_url'],
1678
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1679
					'{INPUT}' => $value,
1680
					'{KEY}' => $currentKey,
1681
				));
1682
1683
			$memberContext[$user]['custom_fields'][] = array(
1684
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1685
				'col_name' => $custom['col_name'],
1686
				'value' => un_htmlspecialchars($value),
1687
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1688
			);
1689
		}
1690
	}
1691
1692
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1693
	return true;
1694
}
1695
1696
/**
1697
 * Loads the user's custom profile fields
1698
 *
1699
 * @param integer|array $users A single user ID or an array of user IDs
1700
 * @param string|array $params Either a string or an array of strings with profile field names
1701
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1702
 */
1703
function loadMemberCustomFields($users, $params)
1704
{
1705
	global $smcFunc, $txt, $scripturl, $settings;
1706
1707
	// Do not waste my time...
1708
	if (empty($users) || empty($params))
1709
		return false;
1710
1711
	// Make sure it's an array.
1712
	$users = !is_array($users) ? array($users) : array_unique($users);
1713
	$params = !is_array($params) ? array($params) : array_unique($params);
1714
	$return = array();
1715
1716
	$request = $smcFunc['db_query']('', '
1717
		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,
1718
		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
1719
		FROM {db_prefix}themes AS t
1720
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1721
		WHERE id_member IN ({array_int:loaded_ids})
1722
			AND variable IN ({array_string:params})
1723
		ORDER BY field_order',
1724
		array(
1725
			'loaded_ids' => $users,
1726
			'params' => $params,
1727
		)
1728
	);
1729
1730
	while ($row = $smcFunc['db_fetch_assoc']($request))
1731
	{
1732
		$fieldOptions = array();
1733
		$currentKey = 0;
1734
1735
		// Create a key => value array for multiple options fields
1736
		if (!empty($row['field_options']))
1737
			foreach (explode(',', $row['field_options']) as $k => $v)
1738
			{
1739
				$fieldOptions[] = $v;
1740
				if (empty($currentKey))
1741
					$currentKey = $v == $row['value'] ? $k : 0;
1742
			}
1743
1744
		// BBC?
1745
		if (!empty($row['bbc']))
1746
			$row['value'] = parse_bbc($row['value']);
1747
1748
		// ... or checkbox?
1749
		elseif (isset($row['type']) && $row['type'] == 'check')
1750
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1751
1752
		// Enclosing the user input within some other text?
1753
		if (!empty($row['enclose']))
1754
			$row['value'] = strtr($row['enclose'], array(
1755
				'{SCRIPTURL}' => $scripturl,
1756
				'{IMAGES_URL}' => $settings['images_url'],
1757
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1758
				'{INPUT}' => un_htmlspecialchars($row['value']),
1759
				'{KEY}' => $currentKey,
1760
			));
1761
1762
		// Send a simple array if there is just 1 param
1763
		if (count($params) == 1)
1764
			$return[$row['id_member']] = $row;
1765
1766
		// More than 1? knock yourself out...
1767
		else
1768
		{
1769
			if (!isset($return[$row['id_member']]))
1770
				$return[$row['id_member']] = array();
1771
1772
			$return[$row['id_member']][$row['variable']] = $row;
1773
		}
1774
	}
1775
1776
	$smcFunc['db_free_result']($request);
1777
1778
	return !empty($return) ? $return : false;
1779
}
1780
1781
/**
1782
 * Loads information about what browser the user is viewing with and places it in $context
1783
 *  - uses the class from {@link Class-BrowserDetect.php}
1784
 */
1785
function detectBrowser()
1786
{
1787
	// Load the current user's browser of choice
1788
	$detector = new browser_detector;
1789
	$detector->detectBrowser();
1790
}
1791
1792
/**
1793
 * Are we using this browser?
1794
 *
1795
 * Wrapper function for detectBrowser
1796
 *
1797
 * @param string $browser The browser we are checking for.
1798
 * @return bool Whether or not the current browser is what we're looking for
1799
 */
1800
function isBrowser($browser)
1801
{
1802
	global $context;
1803
1804
	// Don't know any browser!
1805
	if (empty($context['browser']))
1806
		detectBrowser();
1807
1808
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1809
}
1810
1811
/**
1812
 * Load a theme, by ID.
1813
 *
1814
 * @param int $id_theme The ID of the theme to load
1815
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1816
 */
1817
function loadTheme($id_theme = 0, $initialize = true)
1818
{
1819
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1820
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1821
	global $context, $settings, $options, $sourcedir, $smcFunc, $language, $board, $image_proxy_enabled, $cache_enable;
1822
1823
	if (empty($id_theme))
1824
	{
1825
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
1826
		{
1827
			// The theme was specified by REQUEST.
1828
			if (!empty($_REQUEST['theme']) && (allowedTo('admin_forum') || in_array($_REQUEST['theme'], explode(',', $modSettings['knownThemes']))))
1829
			{
1830
				$id_theme = (int) $_REQUEST['theme'];
1831
				$_SESSION['id_theme'] = $id_theme;
1832
			}
1833
			// The theme was specified by REQUEST... previously.
1834
			elseif (!empty($_SESSION['id_theme']))
1835
				$id_theme = (int) $_SESSION['id_theme'];
1836
			// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1837
			elseif (!empty($user_info['theme']))
1838
				$id_theme = $user_info['theme'];
1839
		}
1840
		// The theme was specified by the board.
1841
		elseif (!empty($board_info['theme']))
1842
			$id_theme = $board_info['theme'];
1843
		// The theme is the forum's default.
1844
		else
1845
			$id_theme = $modSettings['theme_guests'];
1846
1847
		// Verify the id_theme... no foul play.
1848
		// Always allow the board specific theme, if they are overriding.
1849
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1850
			$id_theme = $board_info['theme'];
1851
		elseif (!empty($modSettings['enableThemes']))
1852
		{
1853
			$themes = explode(',', $modSettings['enableThemes']);
1854
			if (!in_array($id_theme, $themes))
1855
				$id_theme = $modSettings['theme_guests'];
1856
			else
1857
				$id_theme = (int) $id_theme;
1858
		}
1859
	}
1860
1861
	// Allow mod authors the option to override the theme id for custom page themes
1862
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1863
1864
	// We already load the basic stuff?
1865
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme)
1866
	{
1867
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1868
1869
		// Disable image proxy if we don't have SSL enabled
1870
		if (empty($modSettings['force_ssl']))
1871
			$image_proxy_enabled = false;
1872
1873
		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
Bug introduced by
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...
1874
		{
1875
			$themeData = $temp;
1876
			$flag = true;
1877
		}
1878
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
0 ignored issues
show
Bug introduced by
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...
1879
			$themeData = $temp + array($member => array());
1880
		else
1881
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1882
1883
		if (empty($flag))
1884
		{
1885
			// Load variables from the current or default theme, global or this user's.
1886
			$result = $smcFunc['db_query']('', '
1887
				SELECT variable, value, id_member, id_theme
1888
				FROM {db_prefix}themes
1889
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1890
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') . '
1891
				ORDER BY id_theme asc',
1892
				array(
1893
					'id_theme' => $id_theme,
1894
					'id_member' => $member,
1895
				)
1896
			);
1897
			// Pick between $settings and $options depending on whose data it is.
1898
			foreach ($smcFunc['db_fetch_all']($result) as $row)
1899
			{
1900
				// There are just things we shouldn't be able to change as members.
1901
				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')))
1902
					continue;
1903
1904
				// If this is the theme_dir of the default theme, store it.
1905
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1906
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1907
1908
				// If this isn't set yet, is a theme option, or is not the default theme..
1909
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1910
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1911
			}
1912
			$smcFunc['db_free_result']($result);
1913
1914
			if (!empty($themeData[-1]))
1915
				foreach ($themeData[-1] as $k => $v)
1916
				{
1917
					if (!isset($themeData[$member][$k]))
1918
						$themeData[$member][$k] = $v;
1919
				}
1920
1921
			if (!empty($cache_enable) && $cache_enable >= 2)
1922
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1923
			// Only if we didn't already load that part of the cache...
1924
			elseif (!isset($temp))
1925
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1926
		}
1927
1928
		$settings = $themeData[0];
1929
		$options = $themeData[$member];
1930
1931
		$settings['theme_id'] = $id_theme;
1932
1933
		$settings['actual_theme_url'] = $settings['theme_url'];
1934
		$settings['actual_images_url'] = $settings['images_url'];
1935
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1936
1937
		$settings['template_dirs'] = array();
1938
		// This theme first.
1939
		$settings['template_dirs'][] = $settings['theme_dir'];
1940
1941
		// Based on theme (if there is one).
1942
		if (!empty($settings['base_theme_dir']))
1943
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1944
1945
		// Lastly the default theme.
1946
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1947
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1948
	}
1949
1950
	if (!$initialize)
1951
		return;
1952
1953
	// Check to see if we're forcing SSL
1954
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
1955
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
1956
	{
1957
		if (isset($_GET['sslRedirect']))
1958
		{
1959
			loadLanguage('Errors');
1960
			fatal_lang_error('login_ssl_required', false);
1961
		}
1962
1963
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
1964
	}
1965
1966
	// Check to see if they're accessing it from the wrong place.
1967
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1968
	{
1969
		$detected_url = httpsOn() ? 'https://' : 'http://';
1970
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1971
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1972
		if ($temp != '/')
1973
			$detected_url .= $temp;
1974
	}
1975
	if (isset($detected_url) && $detected_url != $boardurl)
1976
	{
1977
		// Try #1 - check if it's in a list of alias addresses.
1978
		if (!empty($modSettings['forum_alias_urls']))
1979
		{
1980
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1981
1982
			foreach ($aliases as $alias)
1983
			{
1984
				// Rip off all the boring parts, spaces, etc.
1985
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1986
					$do_fix = true;
1987
			}
1988
		}
1989
1990
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1991
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
1992
		{
1993
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1994
			if (empty($_GET))
1995
				redirectexit('wwwRedirect');
1996
			else
1997
			{
1998
				$k = key($_GET);
1999
				$v = current($_GET);
2000
2001
				if ($k != 'wwwRedirect')
2002
					redirectexit('wwwRedirect;' . $k . '=' . $v);
2003
			}
2004
		}
2005
2006
		// #3 is just a check for SSL...
2007
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
2008
			$do_fix = true;
2009
2010
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
2011
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $detected_url does not seem to be defined for all execution paths leading up to this point.
Loading history...
2012
		{
2013
			// Caching is good ;).
2014
			$oldurl = $boardurl;
2015
2016
			// Fix $boardurl and $scripturl.
2017
			$boardurl = $detected_url;
2018
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
2019
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
2020
2021
			// Fix the theme urls...
2022
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
2023
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
2024
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
2025
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
2026
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
2027
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
2028
2029
			// And just a few mod settings :).
2030
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
2031
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
2032
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
2033
2034
			// Clean up after loadBoard().
2035
			if (isset($board_info['moderators']))
2036
			{
2037
				foreach ($board_info['moderators'] as $k => $dummy)
2038
				{
2039
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
2040
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
2041
				}
2042
			}
2043
			foreach ($context['linktree'] as $k => $dummy)
2044
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
2045
		}
2046
	}
2047
	// Set up the contextual user array.
2048
	if (!empty($user_info))
2049
	{
2050
		$context['user'] = array(
2051
			'id' => $user_info['id'],
2052
			'is_logged' => !$user_info['is_guest'],
2053
			'is_guest' => &$user_info['is_guest'],
2054
			'is_admin' => &$user_info['is_admin'],
2055
			'is_mod' => &$user_info['is_mod'],
2056
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
2057
			'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'])))),
2058
			'name' => $user_info['username'],
2059
			'language' => $user_info['language'],
2060
			'email' => $user_info['email'],
2061
			'ignoreusers' => $user_info['ignoreusers'],
2062
		);
2063
		if (!$context['user']['is_guest'])
2064
			$context['user']['name'] = $user_info['name'];
2065
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
2066
			$context['user']['name'] = $txt['guest_title'];
2067
2068
		// Determine the current smiley set.
2069
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
2070
		$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'];
2071
		$context['user']['smiley_set'] = $user_info['smiley_set'];
2072
	}
2073
	else
2074
	{
2075
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2076
		$context['user'] = array(
2077
			'id' => -1,
2078
			'is_logged' => false,
2079
			'is_guest' => true,
2080
			'is_mod' => false,
2081
			'can_mod' => false,
2082
			'name' => $txt['guest_title'],
2083
			'language' => $language,
2084
			'email' => '',
2085
			'ignoreusers' => array(),
2086
		);
2087
		// Note we should stuff $user_info with some guest values also...
2088
		$user_info = array(
2089
			'id' => 0,
2090
			'is_guest' => true,
2091
			'is_admin' => false,
2092
			'is_mod' => false,
2093
			'username' => $txt['guest_title'],
2094
			'language' => $language,
2095
			'email' => '',
2096
			'smiley_set' => '',
2097
			'permissions' => array(),
2098
			'groups' => array(),
2099
			'ignoreusers' => array(),
2100
			'possibly_robot' => true,
2101
			'time_offset' => 0,
2102
			'time_format' => $modSettings['time_format'],
2103
		);
2104
	}
2105
2106
	// Some basic information...
2107
	if (!isset($context['html_headers']))
2108
		$context['html_headers'] = '';
2109
	if (!isset($context['javascript_files']))
2110
		$context['javascript_files'] = array();
2111
	if (!isset($context['css_files']))
2112
		$context['css_files'] = array();
2113
	if (!isset($context['css_header']))
2114
		$context['css_header'] = array();
2115
	if (!isset($context['javascript_inline']))
2116
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2117
	if (!isset($context['javascript_vars']))
2118
		$context['javascript_vars'] = array();
2119
2120
	$context['login_url'] = $scripturl . '?action=login2';
2121
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2122
	$context['session_var'] = $_SESSION['session_var'];
2123
	$context['session_id'] = $_SESSION['session_value'];
2124
	$context['forum_name'] = $mbname;
2125
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2126
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2127
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2128
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2129
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2130
	if (isset($modSettings['load_average']))
2131
		$context['load_average'] = $modSettings['load_average'];
2132
2133
	// Detect the browser. This is separated out because it's also used in attachment downloads
2134
	detectBrowser();
2135
2136
	// Set the top level linktree up.
2137
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2138
	if (empty($context['linktree']))
2139
		$context['linktree'] = array();
2140
	array_unshift($context['linktree'], array(
2141
		'url' => $scripturl,
2142
		'name' => $context['forum_name_html_safe']
2143
	));
2144
2145
	// This allows sticking some HTML on the page output - useful for controls.
2146
	$context['insert_after_template'] = '';
2147
2148
	if (!isset($txt))
2149
		$txt = array();
2150
2151
	$simpleActions = array(
2152
		'findmember',
2153
		'helpadmin',
2154
		'printpage',
2155
		'spellcheck',
2156
	);
2157
2158
	// Parent action => array of areas
2159
	$simpleAreas = array(
2160
		'profile' => array('popup', 'alerts_popup',),
2161
	);
2162
2163
	// Parent action => array of subactions
2164
	$simpleSubActions = array(
2165
		'pm' => array('popup',),
2166
		'signup' => array('usernamecheck'),
2167
	);
2168
2169
	// Extra params like ;preview ;js, etc.
2170
	$extraParams = array(
2171
		'preview',
2172
		'splitjs',
2173
	);
2174
2175
	// Actions that specifically uses XML output.
2176
	$xmlActions = array(
2177
		'quotefast',
2178
		'jsmodify',
2179
		'xmlhttp',
2180
		'post2',
2181
		'suggest',
2182
		'stats',
2183
		'notifytopic',
2184
		'notifyboard',
2185
	);
2186
2187
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2188
2189
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2190
		(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2191
		(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2192
2193
	// See if theres any extra param to check.
2194
	$requiresXML = false;
2195
	foreach ($extraParams as $key => $extra)
2196
		if (isset($_REQUEST[$extra]))
2197
			$requiresXML = true;
2198
2199
	// Output is fully XML, so no need for the index template.
2200
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2201
	{
2202
		loadLanguage('index+Modifications');
2203
		loadTemplate('Xml');
2204
		$context['template_layers'] = array();
2205
	}
2206
2207
	// These actions don't require the index template at all.
2208
	elseif (!empty($context['simple_action']))
2209
	{
2210
		loadLanguage('index+Modifications');
2211
		$context['template_layers'] = array();
2212
	}
2213
2214
	else
2215
	{
2216
		// Custom templates to load, or just default?
2217
		if (isset($settings['theme_templates']))
2218
			$templates = explode(',', $settings['theme_templates']);
2219
		else
2220
			$templates = array('index');
2221
2222
		// Load each template...
2223
		foreach ($templates as $template)
2224
			loadTemplate($template);
2225
2226
		// ...and attempt to load their associated language files.
2227
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2228
		loadLanguage($required_files, '', false);
2229
2230
		// Custom template layers?
2231
		if (isset($settings['theme_layers']))
2232
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2233
		else
2234
			$context['template_layers'] = array('html', 'body');
2235
	}
2236
2237
	// Initialize the theme.
2238
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Bug introduced by
'ignore' of type string is incompatible with the type boolean expected by parameter $fatal of loadSubTemplate(). ( Ignorable by Annotation )

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

2238
	loadSubTemplate('init', /** @scrutinizer ignore-type */ 'ignore');
Loading history...
2239
2240
	// Allow overriding the board wide time/number formats.
2241
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2242
		$user_info['time_format'] = $txt['time_format'];
2243
2244
	// Set the character set from the template.
2245
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2246
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2247
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2248
2249
	// Guests may still need a name.
2250
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2251
		$context['user']['name'] = $txt['guest_title'];
2252
2253
	// Any theme-related strings that need to be loaded?
2254
	if (!empty($settings['require_theme_strings']))
2255
		loadLanguage('ThemeStrings', '', false);
2256
2257
	// Make a special URL for the language.
2258
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2259
2260
	// And of course, let's load the default CSS file.
2261
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2262
2263
	// Here is my luvly Responsive CSS
2264
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2265
2266
	if ($context['right_to_left'])
2267
		loadCSSFile('rtl.css', array('order_pos' => 4000), 'smf_rtl');
2268
2269
	// We allow theme variants, because we're cool.
2270
	$context['theme_variant'] = '';
2271
	$context['theme_variant_url'] = '';
2272
	if (!empty($settings['theme_variants']))
2273
	{
2274
		// Overriding - for previews and that ilk.
2275
		if (!empty($_REQUEST['variant']))
2276
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2277
		// User selection?
2278
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2279
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2280
		// If not a user variant, select the default.
2281
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2282
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2283
2284
		// Do this to keep things easier in the templates.
2285
		$context['theme_variant'] = '_' . $context['theme_variant'];
2286
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2287
2288
		if (!empty($context['theme_variant']))
2289
		{
2290
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2291
			if ($context['right_to_left'])
2292
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 4200), 'smf_rtl' . $context['theme_variant']);
2293
		}
2294
	}
2295
2296
	// Let's be compatible with old themes!
2297
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2298
		$context['template_layers'] = array('main');
2299
2300
	$context['tabindex'] = 1;
2301
2302
	// Compatibility.
2303
	if (!isset($settings['theme_version']))
2304
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2305
2306
	// Default JS variables for use in every theme
2307
	$context['javascript_vars'] = array(
2308
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2309
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2310
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2311
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2312
		'smf_smiley_sets' => '"' . $modSettings['smiley_sets_known'] . '"',
2313
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2314
		'smf_scripturl' => '"' . $scripturl . '"',
2315
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2316
		'smf_charset' => '"' . $context['character_set'] . '"',
2317
		'smf_session_id' => '"' . $context['session_id'] . '"',
2318
		'smf_session_var' => '"' . $context['session_var'] . '"',
2319
		'smf_member_id' => $context['user']['id'],
2320
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2321
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2322
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2323
	);
2324
2325
	// Add the JQuery library to the list of files to load.
2326
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2327
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2328
2329
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2330
		loadJavaScriptFile('jquery-' . JQUERY_VERSION . '.min.js', array('seed' => false), 'smf_jquery');
2331
2332
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2333
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2334
2335
	// Auto loading? template_javascript() will take care of the local half of this.
2336
	else
2337
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/' . JQUERY_VERSION . '/jquery.min.js', array('external' => true), 'smf_jquery');
2338
2339
	// Queue our JQuery plugins!
2340
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2341
	if (!$user_info['is_guest'])
2342
	{
2343
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2344
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2345
	}
2346
2347
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2348
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2349
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2350
2351
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2352
	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())
2353
	{
2354
		if (isBrowser('possibly_robot'))
2355
		{
2356
			// @todo Maybe move this somewhere better?!
2357
			require_once($sourcedir . '/ScheduledTasks.php');
2358
2359
			// What to do, what to do?!
2360
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2361
				AutoTask();
2362
			else
2363
				ReduceMailQueue();
2364
		}
2365
		else
2366
		{
2367
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2368
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2369
2370
			addInlineJavaScript('
2371
		function smfAutoTask()
2372
		{
2373
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2374
		}
2375
		window.setTimeout("smfAutoTask();", 1);');
2376
		}
2377
	}
2378
2379
	// And we should probably trigger the cron too.
2380
	if (empty($modSettings['cron_is_real_cron']))
2381
	{
2382
		$ts = time();
2383
		$ts -= $ts % 15;
2384
		addInlineJavaScript('
2385
	function triggerCron()
2386
	{
2387
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2388
	}
2389
	window.setTimeout(triggerCron, 1);', true);
2390
	}
2391
2392
	// Filter out the restricted boards from the linktree
2393
	if (!$user_info['is_admin'] && !empty($board))
2394
	{
2395
		foreach ($context['linktree'] as $k => $element)
2396
		{
2397
			if (!empty($element['groups']) &&
2398
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2399
					(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2400
			{
2401
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2402
				$context['linktree'][$k]['extra_before'] = '<i>';
2403
				$context['linktree'][$k]['extra_after'] = '</i>';
2404
				unset($context['linktree'][$k]['url']);
2405
			}
2406
		}
2407
	}
2408
2409
	// Any files to include at this point?
2410
	if (!empty($modSettings['integrate_theme_include']))
2411
	{
2412
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2413
		foreach ($theme_includes as $include)
2414
		{
2415
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2416
			if (file_exists($include))
2417
				require_once($include);
2418
		}
2419
	}
2420
2421
	// Call load theme integration functions.
2422
	call_integration_hook('integrate_load_theme');
2423
2424
	// We are ready to go.
2425
	$context['theme_loaded'] = true;
2426
}
2427
2428
/**
2429
 * Load a template - if the theme doesn't include it, use the default.
2430
 * What this function does:
2431
 *  - loads a template file with the name template_name from the current, default, or base theme.
2432
 *  - detects a wrong default theme directory and tries to work around it.
2433
 *
2434
 * @uses the template_include() function to include the file.
2435
 * @param string $template_name The name of the template to load
2436
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2437
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2438
 * @return boolean Whether or not the template was loaded
2439
 */
2440
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2441
{
2442
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2443
2444
	// Do any style sheets first, cause we're easy with those.
2445
	if (!empty($style_sheets))
2446
	{
2447
		if (!is_array($style_sheets))
2448
			$style_sheets = array($style_sheets);
2449
2450
		foreach ($style_sheets as $sheet)
2451
			loadCSSFile($sheet . '.css', array(), $sheet);
2452
	}
2453
2454
	// No template to load?
2455
	if ($template_name === false)
0 ignored issues
show
introduced by
The condition $template_name === false is always false.
Loading history...
2456
		return true;
2457
2458
	$loaded = false;
2459
	foreach ($settings['template_dirs'] as $template_dir)
2460
	{
2461
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2462
		{
2463
			$loaded = true;
2464
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2465
			break;
2466
		}
2467
	}
2468
2469
	if ($loaded)
0 ignored issues
show
introduced by
The condition $loaded is always false.
Loading history...
2470
	{
2471
		if ($db_show_debug === true)
2472
			$context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $template_dir seems to be defined by a foreach iteration on line 2459. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2473
2474
		// If they have specified an initialization function for this template, go ahead and call it now.
2475
		if (function_exists('template_' . $template_name . '_init'))
2476
			call_user_func('template_' . $template_name . '_init');
2477
	}
2478
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2479
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2480
	{
2481
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2482
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2483
2484
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2485
		{
2486
			loadLanguage('Errors');
2487
			echo '
2488
<div class="alert errorbox">
2489
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2490
</div>';
2491
		}
2492
2493
		loadTemplate($template_name);
2494
	}
2495
	// Cause an error otherwise.
2496
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2497
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2498
	elseif ($fatal)
2499
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load Themes/default/%s.template.php!', (string) $template_name), 'template'));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
2500
	else
2501
		return false;
2502
}
2503
2504
/**
2505
 * Load a sub-template.
2506
 * What it does:
2507
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2508
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2509
 *	for debugging purposes.
2510
 *
2511
 * @todo get rid of reading $_REQUEST directly
2512
 *
2513
 * @param string $sub_template_name The name of the sub-template to load
2514
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2515
 */
2516
function loadSubTemplate($sub_template_name, $fatal = false)
2517
{
2518
	global $context, $txt, $db_show_debug;
2519
2520
	if ($db_show_debug === true)
2521
		$context['debug']['sub_templates'][] = $sub_template_name;
2522
2523
	// Figure out what the template function is named.
2524
	$theme_function = 'template_' . $sub_template_name;
2525
	if (function_exists($theme_function))
2526
		$theme_function();
2527
	elseif ($fatal === false)
2528
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2529
	elseif ($fatal !== 'ignore')
0 ignored issues
show
introduced by
The condition $fatal !== 'ignore' is always true.
Loading history...
2530
		die(log_error(sprintf(isset($txt['theme_template_error']) ? $txt['theme_template_error'] : 'Unable to load the %s sub template!', (string) $sub_template_name), 'template'));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

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

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

3267
				$data2 = highlight_php_code(implode('', /** @scrutinizer ignore-type */ $data));
Loading history...
3268
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3269
3270
				// Fix the PHP code stuff...
3271
				if (!isBrowser('gecko'))
3272
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3273
				else
3274
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3275
3276
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3277
				$j = -1;
3278
				foreach ($data as $line)
3279
				{
3280
					$j++;
3281
3282
					if (substr_count($line, '<br>') == 0)
3283
						continue;
3284
3285
					$n = substr_count($line, '<br>');
3286
					for ($i = 0; $i < $n; $i++)
3287
					{
3288
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3289
						unset($data2[$j + $i + 1]);
3290
					}
3291
					$j += $n;
3292
				}
3293
				$data2 = array_values($data2);
3294
				array_unshift($data2, '');
3295
3296
				echo '
3297
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3298
3299
				// Figure out what the color coding was before...
3300
				$line = max($match[1] - 9, 1);
3301
				$last_line = '';
3302
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3303
					if (strpos($data2[$line2], '<') !== false)
3304
					{
3305
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3306
							$last_line = $color_match[1];
3307
						break;
3308
					}
3309
3310
				// Show the relevant lines...
3311
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3312
				{
3313
					if ($line == $match[1])
3314
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3315
3316
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3317
					if (isset($data2[$line]) && $data2[$line] != '')
3318
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3319
3320
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3321
					{
3322
						$last_line = $color_match[1];
3323
						echo '</', substr($last_line, 1, 4), '>';
3324
					}
3325
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3326
						$last_line = '';
3327
					elseif ($last_line != '' && $data2[$line] != '')
3328
						echo '</', substr($last_line, 1, 4), '>';
3329
3330
					if ($line == $match[1])
3331
						echo '</pre></div><pre style="margin: 0;">';
3332
					else
3333
						echo "\n";
3334
				}
3335
3336
				echo '</pre></div>';
3337
			}
3338
3339
			echo '
3340
	</body>
3341
</html>';
3342
		}
3343
3344
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

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