Passed
Push — release-2.1 ( 908430...ab1855 )
by Mathias
11:01 queued 11s
created

loadMemberCustomFields()   C

Complexity

Conditions 16

Size

Total Lines 78
Code Lines 39

Duplication

Lines 0
Ratio 0 %

Importance

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

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

Loading history...
91
	}
92
93
	$modSettings['cache_enable'] = $cache_enable;
94
95
	// Used to force browsers to download fresh CSS and JavaScript when necessary
96
	$modSettings['browser_cache'] = !empty($modSettings['browser_cache']) ? (int) $modSettings['browser_cache'] : 0;
97
	$context['browser_cache'] = '?' . preg_replace('~\W~', '', strtolower(SMF_FULL_VERSION)) . '_' . $modSettings['browser_cache'];
98
99
	// Disable image proxy if we don't have SSL enabled
100
	if (empty($modSettings['force_ssl']))
101
		$image_proxy_enabled = false;
102
103
	// UTF-8 ?
104
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
105
	$context['utf8'] = $utf8;
106
107
	// Set a list of common functions.
108
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
109
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
110
		{
111
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
112
			return $string;
113
		} : function($string)
114
		{
115
			return $string;
116
		};
117
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
118
	{
119
		if (!$utf8 || $smcFunc['db_mb4'])
120
			return $string;
121
122
		$i = 0;
123
		$len = strlen($string);
124
		$new_string = '';
125
		while ($i < $len)
126
		{
127
			$ord = ord($string[$i]);
128
			if ($ord < 128)
129
			{
130
				$new_string .= $string[$i];
131
				$i++;
132
			}
133
			elseif ($ord < 224)
134
			{
135
				$new_string .= $string[$i] . $string[$i + 1];
136
				$i += 2;
137
			}
138
			elseif ($ord < 240)
139
			{
140
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
141
				$i += 3;
142
			}
143
			elseif ($ord < 248)
144
			{
145
				// Magic happens.
146
				$val = (ord($string[$i]) & 0x07) << 18;
147
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
148
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
149
				$val += (ord($string[$i + 3]) & 0x3F);
150
				$new_string .= '&#' . $val . ';';
151
				$i += 4;
152
			}
153
		}
154
		return $new_string;
155
	};
156
157
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
158
	$smcFunc += array(
159
		'entity_fix' => function($string)
160
		{
161
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
162
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
163
		},
164
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4, &$smcFunc)
165
		{
166
			$string = $smcFunc['normalize']($string);
167
168
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
169
		},
170
		'htmltrim' => function($string) use ($utf8, $ent_check)
171
		{
172
			// Preg_replace space characters depend on the character set in use
173
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
174
175
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
176
		},
177
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
178
		{
179
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
180
		},
181
		'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...
182
		{
183
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
184
185
			if (strlen($needle) === 1)
186
			{
187
				$result = array_search($needle, array_slice($haystack_arr, $offset));
188
				return is_int($result) ? $result + $offset : false;
189
			}
190
			else
191
			{
192
				$needle_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($needle), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
193
				$needle_size = count($needle_arr);
194
195
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
196
				while ((int) $result === $result)
197
				{
198
					$offset += $result;
199
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
200
						return $offset;
201
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
202
				}
203
				return false;
204
			}
205
		},
206
		'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...
207
		{
208
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
209
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
210
		},
211
		'strtolower' => $utf8 ? function($string) use ($sourcedir, &$smcFunc)
212
		{
213
			$string = $smcFunc['normalize']($string);
214
215
			if (!function_exists('mb_strtolower'))
216
			{
217
				require_once($sourcedir . '/Subs-Charset.php');
218
				return utf8_strtolower($string);
219
			}
220
221
			return mb_strtolower($string, 'UTF-8');
222
		} : 'strtolower',
223
		'strtoupper' => $utf8 ? function($string) use ($sourcedir, &$smcFunc)
224
		{
225
			$string = $smcFunc['normalize']($string);
226
227
			if (!function_exists('mb_strtolower'))
228
			{
229
				require_once($sourcedir . '/Subs-Charset.php');
230
				return utf8_strtoupper($string);
231
			}
232
233
			return mb_strtoupper($string, 'UTF-8');
234
		} : 'strtoupper',
235
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
236
		{
237
			$string = $ent_check($string);
238
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
239
			$string = $matches[0];
240
			while (strlen($string) > $length)
241
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
242
			return $string;
243
		},
244
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
245
		{
246
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
247
		} : 'ucfirst',
248
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
249
		{
250
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
251
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
252
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
253
			return implode('', $words);
254
		} : 'ucwords',
255
		'json_decode' => 'smf_json_decode',
256
		'json_encode' => 'json_encode',
257
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
258
		{
259
			global $sourcedir;
260
261
			// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
262
			if (!is_callable('random_int'))
263
				require_once($sourcedir . '/random_compat/random.php');
264
265
			return random_int($min, $max);
266
		},
267
		'random_bytes' => function($length = 64)
268
		{
269
			global $sourcedir;
270
271
			if (!is_callable('random_bytes'))
272
				require_once($sourcedir . '/random_compat/random.php');
273
274
			// Make sure length is valid
275
			$length = max(1, (int) $length);
276
277
			return random_bytes($length);
278
		},
279
		'normalize' => function($string, $form = 'c') use ($utf8)
280
		{
281
			global $sourcedir;
282
283
			if (!$utf8)
284
				return $string;
285
286
			require_once($sourcedir . '/Subs-Charset.php');
287
288
			$normalize_func = 'utf8_normalize_' . strtolower((string) $form);
289
290
			if (!function_exists($normalize_func))
291
				return false;
292
293
			return $normalize_func($string);
294
		},
295
	);
296
297
	// Setting the timezone is a requirement for some functions.
298
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
Are you sure the usage of timezone_identifiers_list() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
Bug introduced by
timezone_identifiers_list() of type void is incompatible with the type array expected by parameter $haystack of in_array(). ( Ignorable by Annotation )

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

298
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
299
		date_default_timezone_set($modSettings['default_timezone']);
300
	else
301
	{
302
		// Get PHP's default timezone, if set
303
		$ini_tz = ini_get('date.timezone');
304
		if (!empty($ini_tz))
305
			$modSettings['default_timezone'] = $ini_tz;
306
		else
307
			$modSettings['default_timezone'] = '';
308
309
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
310
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
Are you sure the usage of timezone_identifiers_list() is correct as it seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

class A
{
    function getObject()
    {
        return null;
    }

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

The reason is most likely that a function or method is imcomplete or has been reduced for debug purposes.

Loading history...
311
		{
312
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
313
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
0 ignored issues
show
Bug introduced by
It seems like $server_offset can also be of type false; however, parameter $utcOffset of timezone_name_from_abbr() does only seem to accept integer, maybe add an additional type check? ( Ignorable by Annotation )

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

313
			$modSettings['default_timezone'] = timezone_name_from_abbr('', /** @scrutinizer ignore-type */ $server_offset, 0);
Loading history...
314
		}
315
316
		date_default_timezone_set($modSettings['default_timezone']);
317
	}
318
319
	// Check the load averages?
320
	if (!empty($modSettings['loadavg_enable']))
321
	{
322
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
323
		{
324
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
325
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
326
				$modSettings['load_average'] = (float) $matches[1];
327
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
328
				$modSettings['load_average'] = (float) $matches[1];
329
			else
330
				unset($modSettings['load_average']);
331
332
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
333
				cache_put_data('loadavg', $modSettings['load_average'], 90);
334
		}
335
336
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
337
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
338
339
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
340
			display_loadavg_error();
341
	}
342
343
	// Ensure we know who can manage boards.
344
	if (!isset($modSettings['board_manager_groups']))
345
	{
346
		require_once($sourcedir . '/Subs-Members.php');
347
		$board_managers = groupsAllowedTo('manage_boards', null);
348
		$board_managers = implode(',', $board_managers['allowed']);
349
		updateSettings(array('board_manager_groups' => $board_managers));
350
	}
351
352
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
353
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
354
355
	// Here to justify the name of this function. :P
356
	// It should be added to the install and upgrade scripts.
357
	// But since the converters need to be updated also. This is easier.
358
	if (empty($modSettings['currentAttachmentUploadDir']))
359
	{
360
		updateSettings(array(
361
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
362
			'currentAttachmentUploadDir' => 1,
363
		));
364
	}
365
366
	// Respect PHP's limits.
367
	$post_max_kb = floor(memoryReturnBytes(ini_get('post_max_size')) / 1024);
368
	$file_max_kb = floor(memoryReturnBytes(ini_get('upload_max_filesize')) / 1024);
369
	$modSettings['attachmentPostLimit'] = empty($modSettings['attachmentPostLimit']) ? $post_max_kb : min($modSettings['attachmentPostLimit'], $post_max_kb);
370
	$modSettings['attachmentSizeLimit'] = empty($modSettings['attachmentSizeLimit']) ? $file_max_kb : min($modSettings['attachmentSizeLimit'], $file_max_kb);
371
372
	// Integration is cool.
373
	if (defined('SMF_INTEGRATION_SETTINGS'))
374
	{
375
		$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...
376
		foreach ($integration_settings as $hook => $function)
377
			add_integration_function($hook, $function, false);
378
	}
379
380
	// Any files to pre include?
381
	if (!empty($modSettings['integrate_pre_include']))
382
	{
383
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
384
		foreach ($pre_includes as $include)
385
		{
386
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
387
			if (file_exists($include))
388
				require_once($include);
389
		}
390
	}
391
392
	// This determines the server... not used in many places, except for login fixing.
393
	$context['server'] = array(
394
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
395
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
396
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
397
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
398
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
399
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
400
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
401
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
402
	);
403
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
404
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
405
406
	// Define a list of icons used across multiple places.
407
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
408
409
	// Define an array for custom profile fields placements.
410
	$context['cust_profile_fields_placement'] = array(
411
		'standard',
412
		'icons',
413
		'above_signature',
414
		'below_signature',
415
		'below_avatar',
416
		'above_member',
417
		'bottom_poster',
418
		'before_member',
419
		'after_member',
420
	);
421
422
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
423
	$context['meta_tags'] = array();
424
425
	// Define an array of allowed HTML tags.
426
	$context['allowed_html_tags'] = array(
427
		'<img>',
428
		'<div>',
429
	);
430
431
	// These are the only valid image types for SMF attachments, by default anyway.
432
	// Note: The values are for image mime types, not file extensions.
433
	$context['valid_image_types'] = array(
434
		IMAGETYPE_GIF => 'gif',
435
		IMAGETYPE_JPEG => 'jpeg',
436
		IMAGETYPE_PNG => 'png',
437
		IMAGETYPE_PSD => 'psd',
438
		IMAGETYPE_BMP => 'bmp',
439
		IMAGETYPE_TIFF_II => 'tiff',
440
		IMAGETYPE_TIFF_MM => 'tiff',
441
		IMAGETYPE_IFF => 'iff'
442
	);
443
444
	// Define a list of allowed tags for descriptions.
445
	$context['description_allowed_tags'] = array(
446
		'abbr', 'anchor', 'b', 'br', 'center', 'color', 'font', 'hr', 'i', 'img',
447
		'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub',
448
		'sup', 'table', 'td', 'tr', 'u', 'url',
449
	);
450
451
	// Define a list of deprecated BBC tags
452
	// Even when enabled, they'll only work in old posts and not new ones
453
	$context['legacy_bbc'] = array(
454
		'acronym', 'bdo', 'black', 'blue', 'flash', 'ftp', 'glow',
455
		'green', 'move', 'red', 'shadow', 'tt', 'white',
456
	);
457
458
	// Define a list of BBC tags that require permissions to use
459
	$context['restricted_bbc'] = array(
460
		'html',
461
	);
462
463
	// Login Cookie times. Format: time => txt
464
	$context['login_cookie_times'] = array(
465
		3153600 => 'always_logged_in',
466
		60 => 'one_hour',
467
		1440 => 'one_day',
468
		10080 => 'one_week',
469
		43200 => 'one_month',
470
	);
471
472
	$context['show_spellchecking'] = false;
473
474
	// Call pre load integration functions.
475
	call_integration_hook('integrate_pre_load');
476
}
477
478
/**
479
 * Load all the important user information.
480
 * What it does:
481
 * 	- sets up the $user_info array
482
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
483
 * 	- first checks for cookie or integration validation.
484
 * 	- uses the current session if no integration function or cookie is found.
485
 * 	- checks password length, if member is activated and the login span isn't over.
486
 * 		- if validation fails for the user, $id_member is set to 0.
487
 * 		- updates the last visit time when needed.
488
 */
489
function loadUserSettings()
490
{
491
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
492
	global $cookiename, $user_info, $language, $context, $cache_enable;
493
494
	require_once($sourcedir . '/Subs-Auth.php');
495
496
	// Check first the integration, then the cookie, and last the session.
497
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
498
	{
499
		$id_member = 0;
500
		foreach ($integration_ids as $integration_id)
501
		{
502
			$integration_id = (int) $integration_id;
503
			if ($integration_id > 0)
504
			{
505
				$id_member = $integration_id;
506
				$already_verified = true;
507
				break;
508
			}
509
		}
510
	}
511
	else
512
		$id_member = 0;
513
514
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
515
	{
516
		// First try 2.1 json-format cookie
517
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
518
519
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
520
		if (empty($cookie_data))
521
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
522
523
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
524
525
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
526
527
		// Make sure the cookie is set to the correct domain and path
528
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
529
			setLoginCookie((int) $login_span - time(), $id_member);
530
	}
531
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
532
	{
533
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
534
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
535
536
		if (empty($cookie_data))
537
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
538
539
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
540
		$id_member = !empty($id_member) && strlen($password) == 40 && (int) $login_span > time() ? (int) $id_member : 0;
541
	}
542
543
	// Only load this stuff if the user isn't a guest.
544
	if ($id_member != 0)
545
	{
546
		// Is the member data cached?
547
		if (empty($cache_enable) || $cache_enable < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
548
		{
549
			$request = $smcFunc['db_query']('', '
550
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type, a.width AS "attachment_width", a.height AS "attachment_height"
551
				FROM {db_prefix}members AS mem
552
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
553
				WHERE mem.id_member = {int:id_member}
554
				LIMIT 1',
555
				array(
556
					'id_member' => $id_member,
557
				)
558
			);
559
			$user_settings = $smcFunc['db_fetch_assoc']($request);
560
			$smcFunc['db_free_result']($request);
561
562
			if (!empty($user_settings['avatar']))
563
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
564
565
			if (!empty($cache_enable) && $cache_enable >= 2)
566
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
567
		}
568
569
		// Did we find 'im?  If not, junk it.
570
		if (!empty($user_settings))
571
		{
572
			// As much as the password should be right, we can assume the integration set things up.
573
			if (!empty($already_verified) && $already_verified === true)
574
				$check = true;
575
			// SHA-512 hash should be 128 characters long.
576
			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...
577
				$check = hash_equals(hash_salt($user_settings['passwd'], $user_settings['password_salt']), $password);
578
			else
579
				$check = false;
580
581
			// Wrong password or not activated - either way, you're going nowhere.
582
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
583
		}
584
		else
585
			$id_member = 0;
586
587
		// Check if we are forcing TFA
588
		$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...
589
590
		// Don't force TFA on popups
591
		if ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
592
		{
593
			if (isset($_REQUEST['action']) && $_REQUEST['action'] == 'profile' && isset($_REQUEST['area']) && in_array($_REQUEST['area'], array('popup', 'alerts_popup')))
594
				$force_tfasetup = false;
595
			elseif (isset($_REQUEST['action']) && $_REQUEST['action'] == 'pm' && (isset($_REQUEST['sa']) && $_REQUEST['sa'] == 'popup'))
596
				$force_tfasetup = false;
597
598
			call_integration_hook('integrate_force_tfasetup', array(&$force_tfasetup));
599
		}
600
601
		// If we no longer have the member maybe they're being all hackey, stop brute force!
602
		if (!$id_member)
603
		{
604
			require_once($sourcedir . '/LogInOut.php');
605
			validatePasswordFlood(
606
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
607
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
608
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
609
				$id_member != 0
610
			);
611
		}
612
		// Validate for Two Factor Authentication
613
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
614
		{
615
			$tfacookie = $cookiename . '_tfa';
616
			$tfasecret = null;
617
618
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
619
620
			if (empty($verified) || !in_array(true, $verified))
621
			{
622
				if (!empty($_COOKIE[$tfacookie]))
623
				{
624
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
625
626
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
627
628
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
629
						$tfasecret = null;
630
				}
631
632
				// They didn't finish logging in before coming here? Then they're no one to us.
633
				if (empty($tfasecret) || !hash_equals(hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']), $tfasecret))
634
				{
635
					setLoginCookie(-3600, $id_member);
636
					$id_member = 0;
637
					$user_settings = array();
638
				}
639
			}
640
		}
641
		// When authenticating their two factor code, make sure to reset their ID for security
642
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
643
		{
644
			$id_member = 0;
645
			$context['tfa_member'] = $user_settings;
646
			$user_settings = array();
647
		}
648
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
649
		elseif ($force_tfasetup)
0 ignored issues
show
introduced by
The condition $force_tfasetup is always false.
Loading history...
650
		{
651
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
652
			{
653
				//Build an array of ALL user membergroups.
654
				$full_groups = array($user_settings['id_group']);
655
				if (!empty($user_settings['additional_groups']))
656
				{
657
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
658
					$full_groups = array_unique($full_groups); //duplicates, maybe?
659
				}
660
661
				//Find out if any group requires 2FA
662
				$request = $smcFunc['db_query']('', '
663
					SELECT COUNT(id_group) AS total
664
					FROM {db_prefix}membergroups
665
					WHERE tfa_required = {int:tfa_required}
666
						AND id_group IN ({array_int:full_groups})',
667
					array(
668
						'tfa_required' => 1,
669
						'full_groups' => $full_groups,
670
					)
671
				);
672
				$row = $smcFunc['db_fetch_assoc']($request);
673
				$smcFunc['db_free_result']($request);
674
			}
675
			else
676
				$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...
677
678
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
679
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
680
681
			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...
682
				redirectexit('action=profile;area=tfasetup;forced');
683
		}
684
	}
685
686
	// Found 'im, let's set up the variables.
687
	if ($id_member != 0)
688
	{
689
		// Let's not update the last visit time in these cases...
690
		// 1. SSI doesn't count as visiting the forum.
691
		// 2. RSS feeds and XMLHTTP requests don't count either.
692
		// 3. If it was set within this session, no need to set it again.
693
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
694
		// 5. We're still logging in or authenticating
695
		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...
696
		{
697
			// @todo can this be cached?
698
			// Do a quick query to make sure this isn't a mistake.
699
			$result = $smcFunc['db_query']('', '
700
				SELECT poster_time
701
				FROM {db_prefix}messages
702
				WHERE id_msg = {int:id_msg}
703
				LIMIT 1',
704
				array(
705
					'id_msg' => $user_settings['id_msg_last_visit'],
706
				)
707
			);
708
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
709
			$smcFunc['db_free_result']($result);
710
711
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
712
713
			// If it was *at least* five hours ago...
714
			if ($visitTime < time() - 5 * 3600)
715
			{
716
				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']));
717
				$user_settings['last_login'] = time();
718
719
				if (!empty($cache_enable) && $cache_enable >= 2)
720
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
721
722
				if (!empty($cache_enable))
723
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
724
			}
725
		}
726
		elseif (empty($_SESSION['id_msg_last_visit']))
727
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
728
729
		$username = $user_settings['member_name'];
730
731
		if (empty($user_settings['additional_groups']))
732
			$user_info = array(
733
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
734
			);
735
736
		else
737
			$user_info = array(
738
				'groups' => array_merge(
739
					array($user_settings['id_group'], $user_settings['id_post_group']),
740
					explode(',', $user_settings['additional_groups'])
741
				)
742
			);
743
744
		// Because history has proven that it is possible for groups to go bad - clean up in case.
745
		$user_info['groups'] = array_map('intval', $user_info['groups']);
746
747
		// This is a logged in user, so definitely not a spider.
748
		$user_info['possibly_robot'] = false;
749
750
		// Figure out the new time offset.
751
		if (!empty($user_settings['timezone']))
752
		{
753
			// Get the offsets from UTC for the server, then for the user.
754
			$tz_system = new DateTimeZone(@date_default_timezone_get());
755
			$tz_user = new DateTimeZone($user_settings['timezone']);
756
			$time_system = new DateTime('now', $tz_system);
757
			$time_user = new DateTime('now', $tz_user);
758
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
759
		}
760
		else
761
		{
762
			// !!! Compatibility.
763
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
764
		}
765
	}
766
	// If the user is a guest, initialize all the critical user settings.
767
	else
768
	{
769
		// This is what a guest's variables should be.
770
		$username = '';
771
		$user_info = array('groups' => array(-1));
772
		$user_settings = array();
773
774
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
775
			$_COOKIE[$cookiename] = '';
776
777
		// Expire the 2FA cookie
778
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
779
		{
780
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
781
782
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
783
784
			if (time() > $exp)
785
			{
786
				$_COOKIE[$cookiename . '_tfa'] = '';
787
				setTFACookie(-3600, 0, '');
788
			}
789
		}
790
791
		// Create a login token if it doesn't exist yet.
792
		if (!isset($_SESSION['token']['post-login']))
793
			createToken('login');
794
		else
795
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
796
797
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
798
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
799
		{
800
			require_once($sourcedir . '/ManageSearchEngines.php');
801
			$user_info['possibly_robot'] = SpiderCheck();
802
		}
803
		elseif (!empty($modSettings['spider_mode']))
804
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
805
		// If we haven't turned on proper spider hunts then have a guess!
806
		else
807
		{
808
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
809
			$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;
810
		}
811
812
		// We don't know the offset...
813
		$user_info['time_offset'] = 0;
814
	}
815
816
	// Set up the $user_info array.
817
	$user_info += array(
818
		'id' => $id_member,
819
		'username' => $username,
820
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
821
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
822
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
823
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
824
		'is_guest' => $id_member == 0,
825
		'is_admin' => in_array(1, $user_info['groups']),
826
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
827
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
828
		'ip' => $_SERVER['REMOTE_ADDR'],
829
		'ip2' => $_SERVER['BAN_CHECK_IP'],
830
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
831
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
832
		'avatar' => array(
833
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
834
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
835
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
836
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0,
837
			'width' => isset($user_settings['attachment_width']) > 0 ? $user_settings['attachment_width']: 0,
838
			'height' => isset($user_settings['attachment_height']) > 0 ? $user_settings['attachment_height'] : 0,
839
		),
840
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
841
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
842
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
843
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
844
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
845
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
846
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
847
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
848
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
849
		'permissions' => array(),
850
	);
851
	$user_info['groups'] = array_unique($user_info['groups']);
852
	$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);
853
854
	// 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.
855
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
856
		unset($user_info['ignoreboards'][$tmp]);
857
858
	// Allow the user to change their language.
859
	if (!empty($modSettings['userLanguage']))
860
	{
861
		$languages = getLanguages();
862
863
		// Is it valid?
864
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
865
		{
866
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
867
868
			// Make it permanent for members.
869
			if (!empty($user_info['id']))
870
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
871
			else
872
				$_SESSION['language'] = $user_info['language'];
873
			// Reload same url with new language, if it exist
874
			if (isset($_SESSION['old_url']))
875
				redirectexit($_SESSION['old_url']);
876
		}
877
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
878
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
879
	}
880
881
	$temp = build_query_board($user_info['id']);
882
	$user_info['query_see_board'] = $temp['query_see_board'];
883
	$user_info['query_see_message_board'] = $temp['query_see_message_board'];
884
	$user_info['query_see_topic_board'] = $temp['query_see_topic_board'];
885
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
886
	$user_info['query_wanna_see_message_board'] = $temp['query_wanna_see_message_board'];
887
	$user_info['query_wanna_see_topic_board'] = $temp['query_wanna_see_topic_board'];
888
889
	call_integration_hook('integrate_user_info');
890
}
891
892
/**
893
 * Load minimal user info from members table.
894
 * Intended for use by background tasks that need to populate $user_info.
895
 *
896
 * @param int|array $user_ids The users IDs to get the data for.
897
 * @return array
898
 * @throws Exception
899
 */
900
function loadMinUserInfo($user_ids = array())
901
{
902
	global $smcFunc, $modSettings, $language;
903
	static $user_info_min = array();
904
905
	$user_ids = (array) $user_ids;
906
907
	// Already loaded?
908
	if (!empty($user_ids))
909
		$user_ids = array_diff($user_ids, array_keys($user_info_min));
910
911
	if (empty($user_ids))
912
		return $user_info_min;
913
914
	$columns_to_load = array(
915
		'id_member',
916
		'member_name',
917
		'real_name',
918
		'time_offset',
919
		'additional_groups',
920
		'id_group',
921
		'id_post_group',
922
		'lngfile',
923
		'smiley_set',
924
		'timezone',
925
	);
926
927
	call_integration_hook('integrate_load_min_user_settings_columns', array(&$columns_to_load));
928
929
	$request = $smcFunc['db_query']('', '
930
		SELECT {raw:columns}
931
		FROM {db_prefix}members
932
		WHERE id_member IN ({array_int:user_ids})',
933
		array(
934
			'user_ids' => array_map('intval', array_unique($user_ids)),
935
			'columns' => implode(', ', $columns_to_load)
936
		)
937
	);
938
939
	while ($row = $smcFunc['db_fetch_assoc']($request))
940
	{
941
		$user_info_min[$row['id_member']] = array(
942
			'id' => $row['id_member'],
943
			'username' => $row['member_name'],
944
			'name' => isset($row['real_name']) ? $row['real_name'] : '',
945
			'language' => (empty($row['lngfile']) || empty($modSettings['userLanguage'])) ? $language : $row['lngfile'],
946
			'is_guest' => false,
947
			'time_format' => empty($row['time_format']) ? $modSettings['time_format'] : $row['time_format'],
948
			'smiley_set' => empty($row['smiley_set']) ? $modSettings['smiley_sets_default'] : $row['smiley_set'],
949
		);
950
951
		if (empty($row['additional_groups']))
952
			$user_info_min[$row['id_member']]['groups'] = array($row['id_group'], $row['id_post_group']);
953
954
		else
955
			$user_info_min[$row['id_member']]['groups'] = array_merge(
956
				array($row['id_group'], $row['id_post_group']),
957
				explode(',', $row['additional_groups'])
958
			);
959
960
		$user_info_min[$row['id_member']]['is_admin'] = in_array(1, $user_info_min[$row['id_member']]['groups']);
961
962
		if (!empty($row['timezone']))
963
		{
964
			$tz_system = new \DateTimeZone(@date_default_timezone_get());
965
			$tz_user = new \DateTimeZone($row['timezone']);
966
			$time_system = new \DateTime('now', $tz_system);
967
			$time_user = new \DateTime('now', $tz_user);
968
			$user_info_min[$row['id_member']]['time_offset'] = ($tz_user->getOffset($time_user) -
969
					$tz_system->getOffset($time_system)) / 3600;
970
		}
971
972
		else
973
			$user_info_min[$row['id_member']]['time_offset'] = empty($row['time_offset']) ? 0 : $row['time_offset'];
974
	}
975
976
	$smcFunc['db_free_result']($request);
977
978
	call_integration_hook('integrate_load_min_user_settings', array(&$user_info_min));
979
980
	return $user_info_min;
981
}
982
983
/**
984
 * Check for moderators and see if they have access to the board.
985
 * What it does:
986
 * - sets up the $board_info array for current board information.
987
 * - if cache is enabled, the $board_info array is stored in cache.
988
 * - redirects to appropriate post if only message id is requested.
989
 * - is only used when inside a topic or board.
990
 * - determines the local moderators for the board.
991
 * - adds group id 3 if the user is a local moderator for the board they are in.
992
 * - prevents access if user is not in proper group nor a local moderator of the board.
993
 */
994
function loadBoard()
995
{
996
	global $txt, $scripturl, $context, $modSettings;
997
	global $board_info, $board, $topic, $user_info, $smcFunc, $cache_enable;
998
999
	// Assume they are not a moderator.
1000
	$user_info['is_mod'] = false;
1001
	$context['user']['is_mod'] = &$user_info['is_mod'];
1002
1003
	// Start the linktree off empty..
1004
	$context['linktree'] = array();
1005
1006
	// Have they by chance specified a message id but nothing else?
1007
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
1008
	{
1009
		// Make sure the message id is really an int.
1010
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
1011
1012
		// Looking through the message table can be slow, so try using the cache first.
1013
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
1014
		{
1015
			$request = $smcFunc['db_query']('', '
1016
				SELECT id_topic
1017
				FROM {db_prefix}messages
1018
				WHERE id_msg = {int:id_msg}
1019
				LIMIT 1',
1020
				array(
1021
					'id_msg' => $_REQUEST['msg'],
1022
				)
1023
			);
1024
1025
			// So did it find anything?
1026
			if ($smcFunc['db_num_rows']($request))
1027
			{
1028
				list ($topic) = $smcFunc['db_fetch_row']($request);
1029
				$smcFunc['db_free_result']($request);
1030
				// Save save save.
1031
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
1032
			}
1033
		}
1034
1035
		// Remember redirection is the key to avoiding fallout from your bosses.
1036
		if (!empty($topic))
1037
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
1038
		else
1039
		{
1040
			loadPermissions();
1041
			loadTheme();
1042
			fatal_lang_error('topic_gone', false);
1043
		}
1044
	}
1045
1046
	// Load this board only if it is specified.
1047
	if (empty($board) && empty($topic))
1048
	{
1049
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
1050
		return;
1051
	}
1052
1053
	if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1054
	{
1055
		// @todo SLOW?
1056
		if (!empty($topic))
1057
			$temp = cache_get_data('topic_board-' . $topic, 120);
1058
		else
1059
			$temp = cache_get_data('board-' . $board, 120);
1060
1061
		if (!empty($temp))
1062
		{
1063
			$board_info = $temp;
1064
			$board = $board_info['id'];
1065
		}
1066
	}
1067
1068
	if (empty($temp))
1069
	{
1070
		$custom_column_selects = array();
1071
		$custom_column_parameters = [
1072
			'current_topic' => $topic,
1073
			'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
1074
		];
1075
1076
		call_integration_hook('integrate_load_board', array(&$custom_column_selects, &$custom_column_parameters));
1077
1078
		$request = $smcFunc['db_query']('load_board_info', '
1079
			SELECT
1080
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
1081
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
1082
				COALESCE(mem.id_member, 0) AS id_moderator,
1083
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
1084
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
1085
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
1086
				' . (!empty($custom_column_selects) ? (', ' . implode(', ', $custom_column_selects)) : '') . '
1087
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
1088
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
1089
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
1090
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
1091
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
1092
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
1093
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1094
			WHERE b.id_board = {raw:board_link}',
1095
			$custom_column_parameters
1096
		);
1097
1098
		// If there aren't any, skip.
1099
		if ($smcFunc['db_num_rows']($request) > 0)
1100
		{
1101
			$row = $smcFunc['db_fetch_assoc']($request);
1102
1103
			// Set the current board.
1104
			if (!empty($row['id_board']))
1105
				$board = $row['id_board'];
1106
1107
			// Basic operating information. (globals... :/)
1108
			$board_info = array(
1109
				'id' => $board,
1110
				'moderators' => array(),
1111
				'moderator_groups' => array(),
1112
				'cat' => array(
1113
					'id' => $row['id_cat'],
1114
					'name' => $row['cname']
1115
				),
1116
				'name' => $row['bname'],
1117
				'description' => $row['description'],
1118
				'num_topics' => $row['num_topics'],
1119
				'unapproved_topics' => $row['unapproved_topics'],
1120
				'unapproved_posts' => $row['unapproved_posts'],
1121
				'unapproved_user_topics' => 0,
1122
				'parent_boards' => getBoardParents($row['id_parent']),
1123
				'parent' => $row['id_parent'],
1124
				'child_level' => $row['child_level'],
1125
				'theme' => $row['id_theme'],
1126
				'override_theme' => !empty($row['override_theme']),
1127
				'profile' => $row['id_profile'],
1128
				'redirect' => $row['redirect'],
1129
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
1130
				'posts_count' => empty($row['count_posts']),
1131
				'cur_topic_approved' => empty($topic) || $row['approved'],
1132
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
1133
			);
1134
1135
			// Load the membergroups allowed, and check permissions.
1136
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
1137
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
1138
1139
			call_integration_hook('integrate_board_info', array(&$board_info, $row));
1140
1141
			if (!empty($modSettings['board_manager_groups']))
1142
			{
1143
				$board_info['groups'] = array_unique(array_merge($board_info['groups'], explode(',', $modSettings['board_manager_groups'])));
1144
				$board_info['deny_groups'] = array_diff($board_info['deny_groups'], explode(',', $modSettings['board_manager_groups']));
1145
			}
1146
1147
			do
1148
			{
1149
				if (!empty($row['id_moderator']))
1150
					$board_info['moderators'][$row['id_moderator']] = array(
1151
						'id' => $row['id_moderator'],
1152
						'name' => $row['real_name'],
1153
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
1154
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
1155
					);
1156
1157
				if (!empty($row['id_moderator_group']))
1158
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
1159
						'id' => $row['id_moderator_group'],
1160
						'name' => $row['group_name'],
1161
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
1162
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
1163
					);
1164
			}
1165
			while ($row = $smcFunc['db_fetch_assoc']($request));
1166
1167
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
1168
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
1169
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
1170
			{
1171
				// Free the previous result
1172
				$smcFunc['db_free_result']($request);
1173
1174
				// @todo why is this using id_topic?
1175
				// @todo Can this get cached?
1176
				$request = $smcFunc['db_query']('', '
1177
					SELECT COUNT(id_topic)
1178
					FROM {db_prefix}topics
1179
					WHERE id_member_started={int:id_member}
1180
						AND approved = {int:unapproved}
1181
						AND id_board = {int:board}',
1182
					array(
1183
						'id_member' => $user_info['id'],
1184
						'unapproved' => 0,
1185
						'board' => $board,
1186
					)
1187
				);
1188
1189
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
1190
			}
1191
1192
			if (!empty($cache_enable) && (empty($topic) || $cache_enable >= 3))
1193
			{
1194
				// @todo SLOW?
1195
				if (!empty($topic))
1196
					cache_put_data('topic_board-' . $topic, $board_info, 120);
1197
				cache_put_data('board-' . $board, $board_info, 120);
1198
			}
1199
		}
1200
		else
1201
		{
1202
			// Otherwise the topic is invalid, there are no moderators, etc.
1203
			$board_info = array(
1204
				'moderators' => array(),
1205
				'moderator_groups' => array(),
1206
				'error' => 'exist'
1207
			);
1208
			$topic = null;
1209
			$board = 0;
1210
		}
1211
		$smcFunc['db_free_result']($request);
1212
	}
1213
1214
	if (!empty($topic))
1215
		$_GET['board'] = (int) $board;
1216
1217
	if (!empty($board))
1218
	{
1219
		// Get this into an array of keys for array_intersect
1220
		$moderator_groups = array_keys($board_info['moderator_groups']);
1221
1222
		// Now check if the user is a moderator.
1223
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1224
1225
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1226
			$board_info['error'] = 'access';
1227
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1228
			$board_info['error'] = 'access';
1229
1230
		// Build up the linktree.
1231
		$context['linktree'] = array_merge(
1232
			$context['linktree'],
1233
			array(array(
1234
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1235
				'name' => $board_info['cat']['name']
1236
			)),
1237
			array_reverse($board_info['parent_boards']),
1238
			array(array(
1239
				'url' => $scripturl . '?board=' . $board . '.0',
1240
				'name' => $board_info['name']
1241
			))
1242
		);
1243
	}
1244
1245
	// Set the template contextual information.
1246
	$context['user']['is_mod'] = &$user_info['is_mod'];
1247
	$context['current_topic'] = $topic;
1248
	$context['current_board'] = $board;
1249
1250
	// No posting in redirection boards!
1251
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1252
		$board_info['error'] = 'post_in_redirect';
1253
1254
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1255
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1256
	{
1257
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1258
		loadPermissions();
1259
		loadTheme();
1260
1261
		$_GET['board'] = '';
1262
		$_GET['topic'] = '';
1263
1264
		// The linktree should not give the game away mate!
1265
		$context['linktree'] = array(
1266
			array(
1267
				'url' => $scripturl,
1268
				'name' => $context['forum_name_html_safe']
1269
			)
1270
		);
1271
1272
		// If it's a prefetching agent or we're requesting an attachment.
1273
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1274
		{
1275
			ob_end_clean();
1276
			send_http_status(403);
1277
			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...
1278
		}
1279
		elseif ($board_info['error'] == 'post_in_redirect')
1280
		{
1281
			// Slightly different error message here...
1282
			fatal_lang_error('cannot_post_redirect', false);
1283
		}
1284
		elseif ($user_info['is_guest'])
1285
		{
1286
			loadLanguage('Errors');
1287
			is_not_guest($txt['topic_gone']);
1288
		}
1289
		else
1290
			fatal_lang_error('topic_gone', false);
1291
	}
1292
1293
	if ($user_info['is_mod'])
1294
		$user_info['groups'][] = 3;
1295
}
1296
1297
/**
1298
 * Load this user's permissions.
1299
 */
1300
function loadPermissions()
1301
{
1302
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir, $cache_enable;
1303
1304
	if ($user_info['is_admin'])
1305
	{
1306
		banPermissions();
1307
		return;
1308
	}
1309
1310
	if (!empty($cache_enable))
1311
	{
1312
		$cache_groups = $user_info['groups'];
1313
		asort($cache_groups);
1314
		$cache_groups = implode(',', $cache_groups);
1315
		// If it's a spider then cache it different.
1316
		if ($user_info['possibly_robot'])
1317
			$cache_groups .= '-spider';
1318
1319
		if ($cache_enable >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1320
		{
1321
			list ($user_info['permissions']) = $temp;
1322
			banPermissions();
1323
1324
			return;
1325
		}
1326
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1327
			list ($user_info['permissions'], $removals) = $temp;
1328
	}
1329
1330
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1331
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1332
1333
	if (empty($user_info['permissions']))
1334
	{
1335
		// Get the general permissions.
1336
		$request = $smcFunc['db_query']('', '
1337
			SELECT permission, add_deny
1338
			FROM {db_prefix}permissions
1339
			WHERE id_group IN ({array_int:member_groups})
1340
				' . $spider_restrict,
1341
			array(
1342
				'member_groups' => $user_info['groups'],
1343
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1344
			)
1345
		);
1346
		$removals = array();
1347
		while ($row = $smcFunc['db_fetch_assoc']($request))
1348
		{
1349
			if (empty($row['add_deny']))
1350
				$removals[] = $row['permission'];
1351
			else
1352
				$user_info['permissions'][] = $row['permission'];
1353
		}
1354
		$smcFunc['db_free_result']($request);
1355
1356
		if (isset($cache_groups))
1357
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1358
	}
1359
1360
	// Get the board permissions.
1361
	if (!empty($board))
1362
	{
1363
		// Make sure the board (if any) has been loaded by loadBoard().
1364
		if (!isset($board_info['profile']))
1365
			fatal_lang_error('no_board');
1366
1367
		$request = $smcFunc['db_query']('', '
1368
			SELECT permission, add_deny
1369
			FROM {db_prefix}board_permissions
1370
			WHERE (id_group IN ({array_int:member_groups})
1371
				' . $spider_restrict . ')
1372
				AND id_profile = {int:id_profile}',
1373
			array(
1374
				'member_groups' => $user_info['groups'],
1375
				'id_profile' => $board_info['profile'],
1376
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1377
			)
1378
		);
1379
		while ($row = $smcFunc['db_fetch_assoc']($request))
1380
		{
1381
			if (empty($row['add_deny']))
1382
				$removals[] = $row['permission'];
1383
			else
1384
				$user_info['permissions'][] = $row['permission'];
1385
		}
1386
		$smcFunc['db_free_result']($request);
1387
	}
1388
1389
	// Remove all the permissions they shouldn't have ;).
1390
	if (!empty($modSettings['permission_enable_deny']))
1391
		$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...
1392
1393
	if (isset($cache_groups) && !empty($board) && $cache_enable >= 2)
1394
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1395
1396
	// Banned?  Watch, don't touch..
1397
	banPermissions();
1398
1399
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1400
	if (!$user_info['is_guest'])
1401
	{
1402
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1403
		{
1404
			require_once($sourcedir . '/Subs-Auth.php');
1405
			rebuildModCache();
1406
		}
1407
		else
1408
			$user_info['mod_cache'] = $_SESSION['mc'];
1409
1410
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1411
		// For example this drastically simplifies certain changes to the profile area.
1412
		$user_info['permissions'][] = 'is_not_guest';
1413
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1414
		$user_info['permissions'][] = 'profile_view_own';
1415
		if (in_array('profile_view', $user_info['permissions']))
1416
			$user_info['permissions'][] = 'profile_view_any';
1417
	}
1418
}
1419
1420
/**
1421
 * Loads an array of users' data by ID or member_name.
1422
 *
1423
 * @param array|string $users An array of users by id or name or a single username/id
1424
 * @param bool $is_name Whether $users contains names
1425
 * @param string $set What kind of data to load (normal, profile, minimal)
1426
 * @return array The ids of the members loaded
1427
 */
1428
function loadMemberData($users, $is_name = false, $set = 'normal')
1429
{
1430
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1431
	global $user_info, $cache_enable, $txt;
1432
1433
	// Can't just look for no users :P.
1434
	if (empty($users))
1435
		return array();
1436
1437
	// Pass the set value
1438
	$context['loadMemberContext_set'] = $set;
1439
1440
	// Make sure it's an array.
1441
	$users = !is_array($users) ? array($users) : array_unique($users);
1442
	$loaded_ids = array();
1443
1444
	if (!$is_name && !empty($cache_enable) && $cache_enable >= 3)
1445
	{
1446
		$users = array_values($users);
1447
		for ($i = 0, $n = count($users); $i < $n; $i++)
1448
		{
1449
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1450
			if ($data == null)
1451
				continue;
1452
1453
			$loaded_ids[] = $data['id_member'];
1454
			$user_profile[$data['id_member']] = $data;
1455
			unset($users[$i]);
1456
		}
1457
	}
1458
1459
	// Used by default
1460
	$select_columns = '
1461
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type, a.width "attachment_width", a.height "attachment_height",
1462
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1463
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1464
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.id_post_group, mem.lngfile, mem.id_group, mem.time_offset, mem.timezone, mem.show_online,
1465
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1466
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1467
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1468
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1469
	$select_tables = '
1470
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1471
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1472
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1473
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1474
1475
	// We add or replace according the the set
1476
	switch ($set)
1477
	{
1478
		case 'normal':
1479
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1480
			break;
1481
		case 'profile':
1482
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1483
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1484
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1485
			break;
1486
		case 'minimal':
1487
			$select_columns = '
1488
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1489
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1490
			$select_tables = '';
1491
			break;
1492
		default:
1493
		{
1494
			loadLanguage('Errors');
1495
			trigger_error(sprintf($txt['invalid_member_data_set'], $set), E_USER_WARNING);
1496
		}
1497
	}
1498
1499
	// Allow mods to easily add to the selected member data
1500
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1501
1502
	if (!empty($users))
1503
	{
1504
		// Load the member's data.
1505
		$request = $smcFunc['db_query']('', '
1506
			SELECT' . $select_columns . '
1507
			FROM {db_prefix}members AS mem' . $select_tables . '
1508
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1509
			array(
1510
				'blank_string' => '',
1511
				'users' => $users,
1512
			)
1513
		);
1514
		$new_loaded_ids = array();
1515
		while ($row = $smcFunc['db_fetch_assoc']($request))
1516
		{
1517
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1518
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1519
1520
			// Take care of proxying avatar if required, do this here for maximum reach
1521
			if (!empty($row['avatar']))
1522
				$row['avatar'] = get_proxied_url($row['avatar']);
1523
1524
			// Keep track of the member's normal member group
1525
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1526
1527
			if (isset($row['member_ip']))
1528
				$row['member_ip'] = inet_dtop($row['member_ip']);
1529
			if (isset($row['member_ip2']))
1530
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1531
			$row['id_member'] = (int) $row['id_member'];
1532
			$new_loaded_ids[] = $row['id_member'];
1533
			$loaded_ids[] = $row['id_member'];
1534
			$row['options'] = array();
1535
			$user_profile[$row['id_member']] = $row;
1536
		}
1537
		$smcFunc['db_free_result']($request);
1538
	}
1539
1540
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1541
	{
1542
		$request = $smcFunc['db_query']('', '
1543
			SELECT id_member, variable, value
1544
			FROM {db_prefix}themes
1545
			WHERE id_member IN ({array_int:loaded_ids})',
1546
			array(
1547
				'loaded_ids' => $new_loaded_ids,
1548
			)
1549
		);
1550
		while ($row = $smcFunc['db_fetch_assoc']($request))
1551
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1552
		$smcFunc['db_free_result']($request);
1553
	}
1554
1555
	$additional_mods = array();
1556
1557
	// Are any of these users in groups assigned to moderate this board?
1558
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1559
	{
1560
		foreach ($loaded_ids as $a_member)
1561
		{
1562
			if (!empty($user_profile[$a_member]['additional_groups']))
1563
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1564
			else
1565
				$groups = array($user_profile[$a_member]['id_group']);
1566
1567
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1568
1569
			if (!empty($temp))
1570
			{
1571
				$additional_mods[] = $a_member;
1572
			}
1573
		}
1574
	}
1575
1576
	if (!empty($new_loaded_ids) && !empty($cache_enable) && $cache_enable >= 3)
1577
	{
1578
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1579
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1580
	}
1581
1582
	// Are we loading any moderators?  If so, fix their group data...
1583
	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)
1584
	{
1585
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1586
		{
1587
			$request = $smcFunc['db_query']('', '
1588
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1589
				FROM {db_prefix}membergroups
1590
				WHERE id_group = {int:moderator_group}
1591
				LIMIT 1',
1592
				array(
1593
					'moderator_group' => 3,
1594
				)
1595
			);
1596
			$row = $smcFunc['db_fetch_assoc']($request);
1597
			$smcFunc['db_free_result']($request);
1598
1599
			cache_put_data('moderator_group_info', $row, 480);
1600
		}
1601
1602
		foreach ($temp_mods as $id)
1603
		{
1604
			// By popular demand, don't show admins or global moderators as moderators.
1605
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1606
				$user_profile[$id]['member_group'] = $row['member_group'];
1607
1608
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1609
			if (!empty($row['icons']))
1610
				$user_profile[$id]['icons'] = $row['icons'];
1611
			if (!empty($row['member_group_color']))
1612
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1613
		}
1614
	}
1615
1616
	return $loaded_ids;
1617
}
1618
1619
/**
1620
 * Loads the user's basic values... meant for template/theme usage.
1621
 *
1622
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1623
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1624
 * @return boolean|array  False if the data wasn't loaded or the loaded data.
1625
 * @throws Exception
1626
 */
1627
function loadMemberContext($user, $display_custom_fields = false)
1628
{
1629
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1630
	global $context, $modSettings, $settings, $smcFunc;
1631
	static $already_loaded_custom_fields = array();
1632
	static $loadedLanguages = array();
1633
1634
	// If this person's data is already loaded, skip it.
1635
	if (!empty($memberContext[$user]) && !empty($already_loaded_custom_fields[$user]) >= $display_custom_fields)
1636
		return $memberContext[$user];
1637
1638
	// We can't load guests or members not loaded by loadMemberData()!
1639
	if ($user == 0)
1640
		return false;
1641
	if (!isset($user_profile[$user]))
1642
	{
1643
		loadLanguage('Errors');
1644
		trigger_error(sprintf($txt['user_not_loaded'], $user), E_USER_WARNING);
1645
		return false;
1646
	}
1647
1648
	// Well, it's loaded now anyhow.
1649
	$profile = $user_profile[$user];
1650
1651
	// These minimal values are always loaded
1652
	$memberContext[$user] = array(
1653
		'username' => $profile['member_name'],
1654
		'name' => $profile['real_name'],
1655
		'id' => $profile['id_member'],
1656
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1657
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . sprintf($txt['view_profile_of_username'], $profile['real_name']) . '">' . $profile['real_name'] . '</a>',
1658
		'email' => $profile['email_address'],
1659
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1660
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1661
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1662
	);
1663
1664
	// If the set isn't minimal then load the monstrous array.
1665
	if ($context['loadMemberContext_set'] != 'minimal')
1666
	{
1667
		// Censor everything.
1668
		censorText($profile['signature']);
1669
		censorText($profile['personal_text']);
1670
1671
		// Set things up to be used before hand.
1672
		$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1673
		$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member'], get_signature_allowed_bbc_tags());
1674
1675
		$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1676
		$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1677
		// Setup the buddy status here (One whole in_array call saved :P)
1678
		$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1679
		$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1680
1681
		//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1682
		if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1683
			$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1684
		elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1685
			$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1686
		else //not set, bye bye
1687
			$group_icon_url = '';
1688
1689
		// Go the extra mile and load the user's native language name.
1690
		if (empty($loadedLanguages))
1691
			$loadedLanguages = getLanguages();
1692
1693
		// Figure out the new time offset.
1694
		if (!empty($profile['timezone']))
1695
		{
1696
			// Get the offsets from UTC for the server, then for the user.
1697
			$tz_system = new DateTimeZone(@date_default_timezone_get());
1698
			$tz_user = new DateTimeZone($profile['timezone']);
1699
			$time_system = new DateTime('now', $tz_system);
1700
			$time_user = new DateTime('now', $tz_user);
1701
			$profile['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
1702
		}
1703
1704
		else
1705
		{
1706
			// !!! Compatibility.
1707
			$profile['time_offset'] = empty($profile['time_offset']) ? 0 : $profile['time_offset'];
1708
		}
1709
1710
		$memberContext[$user] += array(
1711
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1712
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1713
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . sprintf($txt['view_profile_of_username'], $profile['real_name']) . '" ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</a>',
1714
			'is_buddy' => $profile['buddy'],
1715
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1716
			'buddies' => $buddy_list,
1717
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1718
			'blurb' => $profile['personal_text'],
1719
			'website' => array(
1720
				'title' => $profile['website_title'],
1721
				'url' => $profile['website_url'],
1722
			),
1723
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1724
			'signature' => $profile['signature'],
1725
			'real_posts' => $profile['posts'],
1726
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1727
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1728
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1729
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1730
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1731
			'online' => array(
1732
				'is_online' => $profile['is_online'],
1733
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1734
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1735
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1736
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1737
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1738
			),
1739
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1740
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1741
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1742
			'options' => $profile['options'],
1743
			'is_guest' => false,
1744
			'primary_group' => $profile['primary_group'],
1745
			'group' => $profile['member_group'],
1746
			'group_color' => $profile['member_group_color'],
1747
			'group_id' => $profile['id_group'],
1748
			'post_group' => $profile['post_group'],
1749
			'post_group_color' => $profile['post_group_color'],
1750
			'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]),
1751
			'warning' => $profile['warning'],
1752
			'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' : (''))),
1753
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1754
			'custom_fields' => array(),
1755
		);
1756
	}
1757
1758
	// If the set isn't minimal then load their avatar as well.
1759
	if ($context['loadMemberContext_set'] != 'minimal')
1760
	{
1761
		$avatarData = set_avatar_data(array(
1762
			'filename' => $profile['filename'],
1763
			'avatar' => $profile['avatar'],
1764
			'email' => $profile['email_address'],
1765
		));
1766
1767
		if (!empty($avatarData['image']))
1768
			$memberContext[$user]['avatar'] = $avatarData;
1769
	}
1770
1771
	// Are we also loading the members custom fields into context?
1772
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1773
	{
1774
		$memberContext[$user]['custom_fields'] = array();
1775
1776
		if (!isset($context['display_fields']))
1777
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1778
1779
		foreach ($context['display_fields'] as $custom)
1780
		{
1781
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1782
				continue;
1783
1784
			$value = $profile['options'][$custom['col_name']];
1785
1786
			$fieldOptions = array();
1787
			$currentKey = 0;
1788
1789
			// Create a key => value array for multiple options fields
1790
			if (!empty($custom['options']))
1791
				foreach ($custom['options'] as $k => $v)
1792
				{
1793
					$fieldOptions[] = $v;
1794
					if (empty($currentKey))
1795
						$currentKey = $v == $value ? $k : 0;
1796
				}
1797
1798
			// BBC?
1799
			if ($custom['bbc'])
1800
				$value = parse_bbc($value);
1801
1802
			// ... or checkbox?
1803
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1804
				$value = $value ? $txt['yes'] : $txt['no'];
1805
1806
			// Enclosing the user input within some other text?
1807
			if (!empty($custom['enclose']))
1808
				$value = strtr($custom['enclose'], array(
1809
					'{SCRIPTURL}' => $scripturl,
1810
					'{IMAGES_URL}' => $settings['images_url'],
1811
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1812
					'{INPUT}' => tokenTxtReplace($value),
1813
					'{KEY}' => $currentKey,
1814
				));
1815
1816
			$memberContext[$user]['custom_fields'][] = array(
1817
				'title' => tokenTxtReplace(!empty($custom['title']) ? $custom['title'] : $custom['col_name']),
1818
				'col_name' => tokenTxtReplace($custom['col_name']),
1819
				'value' => un_htmlspecialchars(tokenTxtReplace($value)),
1820
				'raw' => $profile['options'][$custom['col_name']],
1821
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1822
			);
1823
		}
1824
	}
1825
1826
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1827
1828
	$already_loaded_custom_fields[$user] = !empty($already_loaded_custom_fields[$user]) | $display_custom_fields;
0 ignored issues
show
Bug introduced by
Are you sure you want to use the bitwise | or did you mean ||?
Loading history...
1829
1830
	return $memberContext[$user];
1831
}
1832
1833
/**
1834
 * Loads the user's custom profile fields
1835
 *
1836
 * @param integer|array $users A single user ID or an array of user IDs
1837
 * @param string|array $params Either a string or an array of strings with profile field names
1838
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1839
 */
1840
function loadMemberCustomFields($users, $params)
1841
{
1842
	global $smcFunc, $txt, $scripturl, $settings;
1843
1844
	// Do not waste my time...
1845
	if (empty($users) || empty($params))
1846
		return false;
1847
1848
	// Make sure it's an array.
1849
	$users = (array) array_unique($users);
0 ignored issues
show
Bug introduced by
It seems like $users can also be of type integer; however, parameter $array of array_unique() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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