Passed
Pull Request — release-2.1 (#4892)
by Mathias
06:47 queued 57s
created

loadUserSettings()   F

Complexity

Conditions 132
Paths > 20000

Size

Total Lines 384
Code Lines 205

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 132
eloc 205
c 0
b 0
f 0
nop 0
dl 0
loc 384
rs 0
nc 1342177280

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2018 Simple Machines and individual contributors
11
 * @license http://www.simplemachines.org/about/smf/license.php BSD
12
 *
13
 * @version 2.1 Beta 4
14
 */
15
16
if (!defined('SMF'))
17
	die('No direct access...');
18
19
/**
20
 * Load the $modSettings array.
21
 */
22
function reloadSettings()
23
{
24
	global $modSettings, $boarddir, $smcFunc, $txt, $db_character_set;
25
	global $cache_enable, $sourcedir, $context;
26
27
	// Most database systems have not set UTF-8 as their default input charset.
28
	if (!empty($db_character_set))
29
		$smcFunc['db_query']('', '
30
			SET NAMES {string:db_character_set}',
31
			array(
32
				'db_character_set' => $db_character_set,
33
			)
34
		);
35
36
	// We need some caching support, maybe.
37
	loadCacheAccelerator();
38
39
	// Try to load it from the cache first; it'll never get cached if the setting is off.
40
	if (($modSettings = cache_get_data('modSettings', 90)) == null)
41
	{
42
		$request = $smcFunc['db_query']('', '
43
			SELECT variable, value
44
			FROM {db_prefix}settings',
45
			array(
46
			)
47
		);
48
		$modSettings = array();
49
		if (!$request)
50
			display_db_error();
51
		while ($row = $smcFunc['db_fetch_row']($request))
52
			$modSettings[$row[0]] = $row[1];
53
		$smcFunc['db_free_result']($request);
54
55
		// Do a few things to protect against missing settings or settings with invalid values...
56
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
57
			$modSettings['defaultMaxTopics'] = 20;
58
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
59
			$modSettings['defaultMaxMessages'] = 15;
60
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
61
			$modSettings['defaultMaxMembers'] = 30;
62
		if (empty($modSettings['defaultMaxListItems']) || $modSettings['defaultMaxListItems'] <= 0 || $modSettings['defaultMaxListItems'] > 999)
63
			$modSettings['defaultMaxListItems'] = 15;
64
65
		// We explicitly do not use $smcFunc['json_decode'] here yet, as $smcFunc is not fully loaded.
66
		if (!is_array($modSettings['attachmentUploadDir']))
67
			$modSettings['attachmentUploadDir'] = smf_json_decode($modSettings['attachmentUploadDir'], true);
68
69
		if (!empty($cache_enable))
70
			cache_put_data('modSettings', $modSettings, 90);
71
	}
72
73
	$modSettings['cache_enable'] = $cache_enable;
74
75
	// UTF-8 ?
76
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
77
78
	// Set a list of common functions.
79
	$ent_list = '&(?:#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . '|quot|amp|lt|gt|nbsp);';
80
	$ent_check = empty($modSettings['disableEntityCheck']) ? function($string)
81
		{
82
			$string = preg_replace_callback('~(&#(\d{1,7}|x[0-9a-fA-F]{1,6});)~', 'entity_fix__callback', $string);
83
			return $string;
84
		} : function($string)
85
		{
86
			return $string;
87
		};
88
	$fix_utf8mb4 = function($string) use ($utf8, $smcFunc)
89
	{
90
		if (!$utf8 || $smcFunc['db_mb4'])
91
			return $string;
92
93
		$i = 0;
94
		$len = strlen($string);
95
		$new_string = '';
96
		while ($i < $len)
97
		{
98
			$ord = ord($string[$i]);
99
			if ($ord < 128)
100
			{
101
				$new_string .= $string[$i];
102
				$i++;
103
			}
104
			elseif ($ord < 224)
105
			{
106
				$new_string .= $string[$i] . $string[$i + 1];
107
				$i += 2;
108
			}
109
			elseif ($ord < 240)
110
			{
111
				$new_string .= $string[$i] . $string[$i + 1] . $string[$i + 2];
112
				$i += 3;
113
			}
114
			elseif ($ord < 248)
115
			{
116
				// Magic happens.
117
				$val = (ord($string[$i]) & 0x07) << 18;
118
				$val += (ord($string[$i + 1]) & 0x3F) << 12;
119
				$val += (ord($string[$i + 2]) & 0x3F) << 6;
120
				$val += (ord($string[$i + 3]) & 0x3F);
121
				$new_string .= '&#' . $val . ';';
122
				$i += 4;
123
			}
124
		}
125
		return $new_string;
126
	};
127
128
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
129
	$smcFunc += array(
130
		'entity_fix' => function($string)
131
		{
132
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
133
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
134
		},
135
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
136
		{
137
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
138
		},
139
		'htmltrim' => function($string) use ($utf8, $ent_check)
140
		{
141
			// Preg_replace space characters depend on the character set in use
142
			$space_chars = $utf8 ? '\p{Z}\p{C}' : '\x00-\x20\x80-\xA0';
143
144
			return preg_replace('~^(?:[' . $space_chars . ']|&nbsp;)+|(?:[' . $space_chars . ']|&nbsp;)+$~' . ($utf8 ? 'u' : ''), '', $ent_check($string));
145
		},
146
		'strlen' => function($string) use ($ent_list, $utf8, $ent_check)
147
		{
148
			return strlen(preg_replace('~' . $ent_list . ($utf8 ? '|.~u' : '~'), '_', $ent_check($string)));
149
		},
150
		'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...
151
		{
152
			$haystack_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : ''), $ent_check($haystack), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
153
154
			if (strlen($needle) === 1)
155
			{
156
				$result = array_search($needle, array_slice($haystack_arr, $offset));
0 ignored issues
show
Bug introduced by
It seems like $haystack_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

162
				$needle_size = count(/** @scrutinizer ignore-type */ $needle_arr);
Loading history...
163
164
				$result = array_search($needle_arr[0], array_slice($haystack_arr, $offset));
165
				while ((int) $result === $result)
166
				{
167
					$offset += $result;
168
					if (array_slice($haystack_arr, $offset, $needle_size) === $needle_arr)
169
						return $offset;
170
					$result = array_search($needle_arr[0], array_slice($haystack_arr, ++$offset));
171
				}
172
				return false;
173
			}
174
		},
175
		'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...
176
		{
177
			$ent_arr = preg_split('~(' . $ent_list . '|.)~' . ($utf8 ? 'u' : '') . '', $ent_check($string), -1, PREG_SPLIT_DELIM_CAPTURE | PREG_SPLIT_NO_EMPTY);
178
			return $length === null ? implode('', array_slice($ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
0 ignored issues
show
Bug introduced by
It seems like $ent_arr can also be of type false; however, parameter $array of array_slice() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

178
			return $length === null ? implode('', array_slice(/** @scrutinizer ignore-type */ $ent_arr, $start)) : implode('', array_slice($ent_arr, $start, $length));
Loading history...
179
		},
180
		'strtolower' => $utf8 ? function($string) use ($sourcedir)
181
		{
182
			if (!function_exists('mb_strtolower'))
183
			{
184
				require_once($sourcedir . '/Subs-Charset.php');
185
				return utf8_strtolower($string);
186
			}
187
188
			return mb_strtolower($string, 'UTF-8');
189
		} : 'strtolower',
190
		'strtoupper' => $utf8 ? function($string)
191
		{
192
			global $sourcedir;
193
194
			if (!function_exists('mb_strtolower'))
195
			{
196
				require_once($sourcedir . '/Subs-Charset.php');
197
				return utf8_strtoupper($string);
198
			}
199
200
			return mb_strtoupper($string, 'UTF-8');
201
		} : 'strtoupper',
202
		'truncate' => function($string, $length) use ($utf8, $ent_check, $ent_list, &$smcFunc)
203
		{
204
			$string = $ent_check($string);
205
			preg_match('~^(' . $ent_list . '|.){' . $smcFunc['strlen'](substr($string, 0, $length)) . '}~' . ($utf8 ? 'u' : ''), $string, $matches);
206
			$string = $matches[0];
207
			while (strlen($string) > $length)
208
				$string = preg_replace('~(?:' . $ent_list . '|.)$~' . ($utf8 ? 'u' : ''), '', $string);
209
			return $string;
210
		},
211
		'ucfirst' => $utf8 ? function($string) use (&$smcFunc)
212
		{
213
			return $smcFunc['strtoupper']($smcFunc['substr']($string, 0, 1)) . $smcFunc['substr']($string, 1);
214
		} : 'ucfirst',
215
		'ucwords' => $utf8 ? function($string) use (&$smcFunc)
216
		{
217
			$words = preg_split('~([\s\r\n\t]+)~', $string, -1, PREG_SPLIT_DELIM_CAPTURE);
218
			for ($i = 0, $n = count($words); $i < $n; $i += 2)
0 ignored issues
show
Bug introduced by
It seems like $words can also be of type false; however, parameter $var of count() does only seem to accept Countable|array, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

220
			return implode('', /** @scrutinizer ignore-type */ $words);
Loading history...
221
		} : 'ucwords',
222
		'json_decode' => 'smf_json_decode',
223
		'json_encode' => 'json_encode',
224
		'random_int' => function($min = 0, $max = PHP_INT_MAX)
225
		{
226
			global $sourcedir;
227
228
			if (!function_exists('random_int'))
229
				require_once($sourcedir . '/Subs-Compat.php');
230
231
			return random_int($min, $max);
232
		},
233
	);
234
235
	// Setting the timezone is a requirement for some functions.
236
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
0 ignored issues
show
Bug introduced by
It seems like timezone_identifiers_list() can also be of type false; however, parameter $haystack of in_array() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

236
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], /** @scrutinizer ignore-type */ timezone_identifiers_list()))
Loading history...
237
		date_default_timezone_set($modSettings['default_timezone']);
238
	else
239
	{
240
		// Get PHP's default timezone, if set
241
		$ini_tz = ini_get('date.timezone');
242
		if (!empty($ini_tz))
243
			$modSettings['default_timezone'] = $ini_tz;
244
		else
245
			$modSettings['default_timezone'] = '';
246
247
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
248
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
249
		{
250
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
251
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
252
		}
253
254
		date_default_timezone_set($modSettings['default_timezone']);
255
	}
256
257
	// Check the load averages?
258
	if (!empty($modSettings['loadavg_enable']))
259
	{
260
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
261
		{
262
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
263
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
264
				$modSettings['load_average'] = (float) $matches[1];
265
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
266
				$modSettings['load_average'] = (float) $matches[1];
267
			else
268
				unset($modSettings['load_average']);
269
270
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
271
				cache_put_data('loadavg', $modSettings['load_average'], 90);
272
		}
273
274
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
275
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
276
277
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
278
			display_loadavg_error();
279
	}
280
281
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
282
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
283
284
	// Here to justify the name of this function. :P
285
	// It should be added to the install and upgrade scripts.
286
	// But since the converters need to be updated also. This is easier.
287
	if (empty($modSettings['currentAttachmentUploadDir']))
288
	{
289
		updateSettings(array(
290
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
291
			'currentAttachmentUploadDir' => 1,
292
		));
293
	}
294
295
	// Integration is cool.
296
	if (defined('SMF_INTEGRATION_SETTINGS'))
297
	{
298
		$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...
299
		foreach ($integration_settings as $hook => $function)
300
			add_integration_function($hook, $function, '', false);
0 ignored issues
show
Bug introduced by
false of type false is incompatible with the type string expected by parameter $file of add_integration_function(). ( Ignorable by Annotation )

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

300
			add_integration_function($hook, $function, '', /** @scrutinizer ignore-type */ false);
Loading history...
Bug introduced by
'' of type string is incompatible with the type boolean expected by parameter $permanent of add_integration_function(). ( Ignorable by Annotation )

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

300
			add_integration_function($hook, $function, /** @scrutinizer ignore-type */ '', false);
Loading history...
301
	}
302
303
	// Any files to pre include?
304
	if (!empty($modSettings['integrate_pre_include']))
305
	{
306
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
307
		foreach ($pre_includes as $include)
308
		{
309
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
310
			if (file_exists($include))
311
				require_once($include);
312
		}
313
	}
314
315
	// This determines the server... not used in many places, except for login fixing.
316
	$context['server'] = array(
317
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
318
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
319
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
320
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
321
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
322
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
323
		'is_windows' => DIRECTORY_SEPARATOR === '\\',
324
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
325
	);
326
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
327
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
328
329
	// Define a list of icons used across multiple places.
330
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
331
332
	// Define an array for custom profile fields placements.
333
	$context['cust_profile_fields_placement'] = array(
334
		'standard',
335
		'icons',
336
		'above_signature',
337
		'below_signature',
338
		'below_avatar',
339
		'above_member',
340
		'bottom_poster',
341
		'before_member',
342
		'after_member',
343
	);
344
345
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
346
	$context['meta_tags'] = array();
347
348
	// Define an array of allowed HTML tags.
349
	$context['allowed_html_tags'] = array(
350
		'<img>',
351
		'<div>',
352
	);
353
354
	// These are the only valid image types for SMF, by default anyway.
355
	$context['valid_image_types'] = array(
356
		1 => 'gif',
357
		2 => 'jpeg',
358
		3 => 'png',
359
		5 => 'psd',
360
		6 => 'bmp',
361
		7 => 'tiff',
362
		8 => 'tiff',
363
		9 => 'jpeg',
364
		14 => 'iff'
365
	);
366
367
	// Define a list of allowed tags for descriptions.
368
	$context['description_allowed_tags'] = array('abbr', 'anchor', 'b', 'center', 'color', 'font', 'hr', 'i', 'img', 'iurl', 'left', 'li', 'list', 'ltr', 'pre', 'right', 's', 'sub', 'sup', 'table', 'td', 'tr', 'u', 'url',);
369
370
	// Define a list of old BBC tags no longer parsed
371
	$context['legacy_bbc'] = array(
372
		'br',
373
		'tt',
374
		'flash',
375
		'bdo',
376
		'black',
377
		'white',
378
		'red',
379
		'green',
380
		'blue',
381
		'acronym',
382
		'ftp',
383
		'glow',
384
		'move',
385
		'shadow',
386
	);
387
388
	// Call pre load integration functions.
389
	call_integration_hook('integrate_pre_load');
390
}
391
392
/**
393
 * Load all the important user information.
394
 * What it does:
395
 * 	- sets up the $user_info array
396
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
397
 * 	- first checks for cookie or integration validation.
398
 * 	- uses the current session if no integration function or cookie is found.
399
 * 	- checks password length, if member is activated and the login span isn't over.
400
 * 		- if validation fails for the user, $id_member is set to 0.
401
 * 		- updates the last visit time when needed.
402
 */
403
function loadUserSettings()
404
{
405
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
406
	global $cookiename, $user_info, $language, $context, $image_proxy_enabled;
407
408
	// Check first the integration, then the cookie, and last the session.
409
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
410
	{
411
		$id_member = 0;
412
		foreach ($integration_ids as $integration_id)
413
		{
414
			$integration_id = (int) $integration_id;
415
			if ($integration_id > 0)
416
			{
417
				$id_member = $integration_id;
418
				$already_verified = true;
419
				break;
420
			}
421
		}
422
	}
423
	else
424
		$id_member = 0;
425
426
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
427
	{
428
		// First try 2.1 json-format cookie
429
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
430
431
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
432
		if (empty($cookie_data))
433
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
434
435
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
436
437
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
438
439
		// Make sure the cookie is set to the correct domain and path
440
		require_once($sourcedir . '/Subs-Auth.php');
441
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
442
			setLoginCookie((int) $login_span - time(), $id_member);
443
	}
444
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
445
	{
446
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
447
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
448
449
		if (empty($cookie_data))
450
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
451
452
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
453
		$id_member = !empty($id_member) && strlen($password) == 128 && (int) $login_span > time() ? (int) $id_member : 0;
454
	}
455
456
	// Only load this stuff if the user isn't a guest.
457
	if ($id_member != 0)
458
	{
459
		// Is the member data cached?
460
		if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
461
		{
462
			$request = $smcFunc['db_query']('', '
463
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
464
				FROM {db_prefix}members AS mem
465
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
466
				WHERE mem.id_member = {int:id_member}
467
				LIMIT 1',
468
				array(
469
					'id_member' => $id_member,
470
				)
471
			);
472
			$user_settings = $smcFunc['db_fetch_assoc']($request);
473
			$smcFunc['db_free_result']($request);
474
475
			if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($user_settings['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
476
				$user_settings['avatar'] = get_proxied_url($user_settings['avatar']);
477
478
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
479
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
480
		}
481
482
		// Did we find 'im?  If not, junk it.
483
		if (!empty($user_settings))
484
		{
485
			// As much as the password should be right, we can assume the integration set things up.
486
			if (!empty($already_verified) && $already_verified === true)
487
				$check = true;
488
			// SHA-512 hash should be 128 characters long.
489
			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...
490
				$check = hash_salt($user_settings['passwd'], $user_settings['password_salt']) == $password;
491
			else
492
				$check = false;
493
494
			// Wrong password or not activated - either way, you're going nowhere.
495
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
496
		}
497
		else
498
			$id_member = 0;
499
500
		// If we no longer have the member maybe they're being all hackey, stop brute force!
501
		if (!$id_member)
502
		{
503
			require_once($sourcedir . '/LogInOut.php');
504
			validatePasswordFlood(
505
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
506
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
507
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
508
				$id_member != 0
509
			);
510
		}
511
		// Validate for Two Factor Authentication
512
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
513
		{
514
			$tfacookie = $cookiename . '_tfa';
515
			$tfasecret = null;
516
517
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
518
519
			if (empty($verified) || !in_array(true, $verified))
520
			{
521
				if (!empty($_COOKIE[$tfacookie]))
522
				{
523
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
524
525
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
526
527
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
528
						$tfasecret = null;
529
				}
530
531
				// They didn't finish logging in before coming here? Then they're no one to us.
532
				if (empty($tfasecret) || hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']) != $tfasecret)
533
				{
534
					setLoginCookie(-3600, $id_member);
535
					$id_member = 0;
536
					$user_settings = array();
537
				}
538
			}
539
		}
540
		// When authenticating their two factor code, make sure to reset their ID for security
541
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
542
		{
543
			$id_member = 0;
544
			$context['tfa_member'] = $user_settings;
545
			$user_settings = array();
546
		}
547
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
548
		elseif (!empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']))
549
		{
550
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
551
			{
552
				//Build an array of ALL user membergroups.
553
				$full_groups = array($user_settings['id_group']);
554
				if (!empty($user_settings['additional_groups']))
555
				{
556
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
557
					$full_groups = array_unique($full_groups); //duplicates, maybe?
558
				}
559
560
				//Find out if any group requires 2FA
561
				$request = $smcFunc['db_query']('', '
562
					SELECT COUNT(id_group) AS total
563
					FROM {db_prefix}membergroups
564
					WHERE tfa_required = {int:tfa_required}
565
						AND id_group IN ({array_int:full_groups})',
566
					array(
567
						'tfa_required' => 1,
568
						'full_groups' => $full_groups,
569
					)
570
				);
571
				$row = $smcFunc['db_fetch_assoc']($request);
572
				$smcFunc['db_free_result']($request);
573
			}
574
			else
575
				$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...
576
577
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
578
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
579
580
			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...
581
				redirectexit('action=profile;area=tfasetup;forced');
582
		}
583
	}
584
585
	// Found 'im, let's set up the variables.
586
	if ($id_member != 0)
587
	{
588
		// Let's not update the last visit time in these cases...
589
		// 1. SSI doesn't count as visiting the forum.
590
		// 2. RSS feeds and XMLHTTP requests don't count either.
591
		// 3. If it was set within this session, no need to set it again.
592
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
593
		// 5. We're still logging in or authenticating
594
		if (SMF != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || !in_array($_REQUEST['action'], array('.xml', 'login2', 'logintfa'))) && empty($_SESSION['id_msg_last_visit']) && (empty($modSettings['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...
595
		{
596
			// @todo can this be cached?
597
			// Do a quick query to make sure this isn't a mistake.
598
			$result = $smcFunc['db_query']('', '
599
				SELECT poster_time
600
				FROM {db_prefix}messages
601
				WHERE id_msg = {int:id_msg}
602
				LIMIT 1',
603
				array(
604
					'id_msg' => $user_settings['id_msg_last_visit'],
605
				)
606
			);
607
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
608
			$smcFunc['db_free_result']($result);
609
610
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
611
612
			// If it was *at least* five hours ago...
613
			if ($visitTime < time() - 5 * 3600)
614
			{
615
				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']));
616
				$user_settings['last_login'] = time();
617
618
				if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
619
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
620
621
				if (!empty($modSettings['cache_enable']))
622
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
623
			}
624
		}
625
		elseif (empty($_SESSION['id_msg_last_visit']))
626
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
627
628
		$username = $user_settings['member_name'];
629
630
		if (empty($user_settings['additional_groups']))
631
			$user_info = array(
632
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
633
			);
634
		else
635
			$user_info = array(
636
				'groups' => array_merge(
637
					array($user_settings['id_group'], $user_settings['id_post_group']),
638
					explode(',', $user_settings['additional_groups'])
639
				)
640
			);
641
642
		// Because history has proven that it is possible for groups to go bad - clean up in case.
643
		$user_info['groups'] = array_map('intval', $user_info['groups']);
644
645
		// This is a logged in user, so definitely not a spider.
646
		$user_info['possibly_robot'] = false;
647
648
		// Figure out the new time offset.
649
		if (!empty($user_settings['timezone']))
650
		{
651
			// Get the offsets from UTC for the server, then for the user.
652
			$tz_system = new DateTimeZone(@date_default_timezone_get());
653
			$tz_user = new DateTimeZone($user_settings['timezone']);
654
			$time_system = new DateTime('now', $tz_system);
655
			$time_user = new DateTime('now', $tz_user);
656
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
657
		}
658
		else
659
		{
660
			// !!! Compatibility.
661
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
662
		}
663
	}
664
	// If the user is a guest, initialize all the critical user settings.
665
	else
666
	{
667
		// This is what a guest's variables should be.
668
		$username = '';
669
		$user_info = array('groups' => array(-1));
670
		$user_settings = array();
671
672
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
673
			$_COOKIE[$cookiename] = '';
674
675
		// Expire the 2FA cookie
676
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
677
		{
678
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
679
680
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
681
682
			if (time() > $exp)
683
			{
684
				$_COOKIE[$cookiename . '_tfa'] = '';
685
				setTFACookie(-3600, 0, '');
686
			}
687
		}
688
689
		// Create a login token if it doesn't exist yet.
690
		if (!isset($_SESSION['token']['post-login']))
691
			createToken('login');
692
		else
693
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
694
695
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
696
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
697
		{
698
			require_once($sourcedir . '/ManageSearchEngines.php');
699
			$user_info['possibly_robot'] = SpiderCheck();
700
		}
701
		elseif (!empty($modSettings['spider_mode']))
702
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
703
		// If we haven't turned on proper spider hunts then have a guess!
704
		else
705
		{
706
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
707
			$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, 'msnbot') !== false;
708
		}
709
710
		// We don't know the offset...
711
		$user_info['time_offset'] = 0;
712
713
		// Login Cookie times. Format: time => txt
714
		$context['login_cookie_times'] = array(
715
			60 => 'one_hour',
716
			1440 => 'one_day',
717
			10080 => 'one_week',
718
			43200 => 'one_month',
719
			3153600 => 'always_logged_in',
720
		);
721
	}
722
723
	// Set up the $user_info array.
724
	$user_info += array(
725
		'id' => $id_member,
726
		'username' => $username,
727
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
728
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
729
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
730
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
731
		'is_guest' => $id_member == 0,
732
		'is_admin' => in_array(1, $user_info['groups']),
733
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
734
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
735
		'ip' => $_SERVER['REMOTE_ADDR'],
736
		'ip2' => $_SERVER['BAN_CHECK_IP'],
737
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
738
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
739
		'avatar' => array(
740
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
741
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
742
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
743
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
744
		),
745
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
746
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
747
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
748
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
749
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
750
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
751
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
752
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
753
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
754
		'permissions' => array(),
755
	);
756
	$user_info['groups'] = array_unique($user_info['groups']);
757
758
	// 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.
759
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
760
		unset($user_info['ignoreboards'][$tmp]);
761
762
	// Allow the user to change their language.
763
	if (!empty($modSettings['userLanguage']))
764
	{
765
		$languages = getLanguages();
766
767
		// Is it valid?
768
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
769
		{
770
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
771
772
			// Make it permanent for members.
773
			if (!empty($user_info['id']))
774
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
775
			else
776
				$_SESSION['language'] = $user_info['language'];
777
		}
778
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
779
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
780
	}
781
782
	$temp = build_query_board($user_info['id']);
783
	$user_info['query_see_board'] = $temp['query_see_board'];
784
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
785
786
	call_integration_hook('integrate_user_info');
787
}
788
789
/**
790
 * Check for moderators and see if they have access to the board.
791
 * What it does:
792
 * - sets up the $board_info array for current board information.
793
 * - if cache is enabled, the $board_info array is stored in cache.
794
 * - redirects to appropriate post if only message id is requested.
795
 * - is only used when inside a topic or board.
796
 * - determines the local moderators for the board.
797
 * - adds group id 3 if the user is a local moderator for the board they are in.
798
 * - prevents access if user is not in proper group nor a local moderator of the board.
799
 */
800
function loadBoard()
801
{
802
	global $txt, $scripturl, $context, $modSettings;
803
	global $board_info, $board, $topic, $user_info, $smcFunc;
804
805
	// Assume they are not a moderator.
806
	$user_info['is_mod'] = false;
807
	$context['user']['is_mod'] = &$user_info['is_mod'];
808
809
	// Start the linktree off empty..
810
	$context['linktree'] = array();
811
812
	// Have they by chance specified a message id but nothing else?
813
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
814
	{
815
		// Make sure the message id is really an int.
816
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
817
818
		// Looking through the message table can be slow, so try using the cache first.
819
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
0 ignored issues
show
introduced by
The condition $topic = cache_get_data(...T['msg'], 120) === null is always false.
Loading history...
820
		{
821
			$request = $smcFunc['db_query']('', '
822
				SELECT id_topic
823
				FROM {db_prefix}messages
824
				WHERE id_msg = {int:id_msg}
825
				LIMIT 1',
826
				array(
827
					'id_msg' => $_REQUEST['msg'],
828
				)
829
			);
830
831
			// So did it find anything?
832
			if ($smcFunc['db_num_rows']($request))
833
			{
834
				list ($topic) = $smcFunc['db_fetch_row']($request);
835
				$smcFunc['db_free_result']($request);
836
				// Save save save.
837
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
838
			}
839
		}
840
841
		// Remember redirection is the key to avoiding fallout from your bosses.
842
		if (!empty($topic))
843
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
844
		else
845
		{
846
			loadPermissions();
847
			loadTheme();
848
			fatal_lang_error('topic_gone', false);
849
		}
850
	}
851
852
	// Load this board only if it is specified.
853
	if (empty($board) && empty($topic))
854
	{
855
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
856
		return;
857
	}
858
859
	if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
860
	{
861
		// @todo SLOW?
862
		if (!empty($topic))
863
			$temp = cache_get_data('topic_board-' . $topic, 120);
864
		else
865
			$temp = cache_get_data('board-' . $board, 120);
866
867
		if (!empty($temp))
868
		{
869
			$board_info = $temp;
870
			$board = $board_info['id'];
871
		}
872
	}
873
874
	if (empty($temp))
875
	{
876
		$request = $smcFunc['db_query']('load_board_info', '
877
			SELECT
878
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
879
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
880
				COALESCE(mem.id_member, 0) AS id_moderator,
881
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
882
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
883
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
884
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
885
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
886
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
887
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
888
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
889
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
890
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
891
			WHERE b.id_board = {raw:board_link}',
892
			array(
893
				'current_topic' => $topic,
894
				'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
895
			)
896
		);
897
		// If there aren't any, skip.
898
		if ($smcFunc['db_num_rows']($request) > 0)
899
		{
900
			$row = $smcFunc['db_fetch_assoc']($request);
901
902
			// Set the current board.
903
			if (!empty($row['id_board']))
904
				$board = $row['id_board'];
905
906
			// Basic operating information. (globals... :/)
907
			$board_info = array(
908
				'id' => $board,
909
				'moderators' => array(),
910
				'moderator_groups' => array(),
911
				'cat' => array(
912
					'id' => $row['id_cat'],
913
					'name' => $row['cname']
914
				),
915
				'name' => $row['bname'],
916
				'description' => $row['description'],
917
				'num_topics' => $row['num_topics'],
918
				'unapproved_topics' => $row['unapproved_topics'],
919
				'unapproved_posts' => $row['unapproved_posts'],
920
				'unapproved_user_topics' => 0,
921
				'parent_boards' => getBoardParents($row['id_parent']),
922
				'parent' => $row['id_parent'],
923
				'child_level' => $row['child_level'],
924
				'theme' => $row['id_theme'],
925
				'override_theme' => !empty($row['override_theme']),
926
				'profile' => $row['id_profile'],
927
				'redirect' => $row['redirect'],
928
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
929
				'posts_count' => empty($row['count_posts']),
930
				'cur_topic_approved' => empty($topic) || $row['approved'],
931
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
932
			);
933
934
			// Load the membergroups allowed, and check permissions.
935
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
936
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
937
938
			do
939
			{
940
				if (!empty($row['id_moderator']))
941
					$board_info['moderators'][$row['id_moderator']] = array(
942
						'id' => $row['id_moderator'],
943
						'name' => $row['real_name'],
944
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
945
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
946
					);
947
948
				if (!empty($row['id_moderator_group']))
949
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
950
						'id' => $row['id_moderator_group'],
951
						'name' => $row['group_name'],
952
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
953
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
954
					);
955
			}
956
			while ($row = $smcFunc['db_fetch_assoc']($request));
957
958
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
959
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
960
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
961
			{
962
				// Free the previous result
963
				$smcFunc['db_free_result']($request);
964
965
				// @todo why is this using id_topic?
966
				// @todo Can this get cached?
967
				$request = $smcFunc['db_query']('', '
968
					SELECT COUNT(id_topic)
969
					FROM {db_prefix}topics
970
					WHERE id_member_started={int:id_member}
971
						AND approved = {int:unapproved}
972
						AND id_board = {int:board}',
973
					array(
974
						'id_member' => $user_info['id'],
975
						'unapproved' => 0,
976
						'board' => $board,
977
					)
978
				);
979
980
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
981
			}
982
983
			if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
984
			{
985
				// @todo SLOW?
986
				if (!empty($topic))
987
					cache_put_data('topic_board-' . $topic, $board_info, 120);
988
				cache_put_data('board-' . $board, $board_info, 120);
989
			}
990
		}
991
		else
992
		{
993
			// Otherwise the topic is invalid, there are no moderators, etc.
994
			$board_info = array(
995
				'moderators' => array(),
996
				'moderator_groups' => array(),
997
				'error' => 'exist'
998
			);
999
			$topic = null;
1000
			$board = 0;
1001
		}
1002
		$smcFunc['db_free_result']($request);
1003
	}
1004
1005
	if (!empty($topic))
1006
		$_GET['board'] = (int) $board;
1007
1008
	if (!empty($board))
1009
	{
1010
		// Get this into an array of keys for array_intersect
1011
		$moderator_groups = array_keys($board_info['moderator_groups']);
1012
1013
		// Now check if the user is a moderator.
1014
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
1015
1016
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
1017
			$board_info['error'] = 'access';
1018
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
1019
			$board_info['error'] = 'access';
1020
1021
		// Build up the linktree.
1022
		$context['linktree'] = array_merge(
1023
			$context['linktree'],
1024
			array(array(
1025
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
1026
				'name' => $board_info['cat']['name']
1027
			)),
1028
			array_reverse($board_info['parent_boards']),
1029
			array(array(
1030
				'url' => $scripturl . '?board=' . $board . '.0',
1031
				'name' => $board_info['name']
1032
			))
1033
		);
1034
	}
1035
1036
	// Set the template contextual information.
1037
	$context['user']['is_mod'] = &$user_info['is_mod'];
1038
	$context['current_topic'] = $topic;
1039
	$context['current_board'] = $board;
1040
1041
	// No posting in redirection boards!
1042
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1043
		$board_info['error'] == 'post_in_redirect';
1044
1045
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1046
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1047
	{
1048
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1049
		loadPermissions();
1050
		loadTheme();
1051
1052
		$_GET['board'] = '';
1053
		$_GET['topic'] = '';
1054
1055
		// The linktree should not give the game away mate!
1056
		$context['linktree'] = array(
1057
			array(
1058
				'url' => $scripturl,
1059
				'name' => $context['forum_name_html_safe']
1060
			)
1061
		);
1062
1063
		// If it's a prefetching agent or we're requesting an attachment.
1064
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
1065
		{
1066
			ob_end_clean();
1067
			send_http_status(403);
1068
			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...
1069
		}
1070
		elseif ($board_info['error'] == 'post_in_redirect')
1071
		{
1072
			// Slightly different error message here...
1073
			fatal_lang_error('cannot_post_redirect', false);
1074
		}
1075
		elseif ($user_info['is_guest'])
1076
		{
1077
			loadLanguage('Errors');
1078
			is_not_guest($txt['topic_gone']);
1079
		}
1080
		else
1081
			fatal_lang_error('topic_gone', false);
1082
	}
1083
1084
	if ($user_info['is_mod'])
1085
		$user_info['groups'][] = 3;
1086
}
1087
1088
/**
1089
 * Load this user's permissions.
1090
 */
1091
function loadPermissions()
1092
{
1093
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir;
1094
1095
	if ($user_info['is_admin'])
1096
	{
1097
		banPermissions();
1098
		return;
1099
	}
1100
1101
	if (!empty($modSettings['cache_enable']))
1102
	{
1103
		$cache_groups = $user_info['groups'];
1104
		asort($cache_groups);
1105
		$cache_groups = implode(',', $cache_groups);
1106
		// If it's a spider then cache it different.
1107
		if ($user_info['possibly_robot'])
1108
			$cache_groups .= '-spider';
1109
1110
		if ($modSettings['cache_enable'] >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1111
		{
1112
			list ($user_info['permissions']) = $temp;
1113
			banPermissions();
1114
1115
			return;
1116
		}
1117
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1118
			list ($user_info['permissions'], $removals) = $temp;
1119
	}
1120
1121
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1122
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1123
1124
	if (empty($user_info['permissions']))
1125
	{
1126
		// Get the general permissions.
1127
		$request = $smcFunc['db_query']('', '
1128
			SELECT permission, add_deny
1129
			FROM {db_prefix}permissions
1130
			WHERE id_group IN ({array_int:member_groups})
1131
				' . $spider_restrict,
1132
			array(
1133
				'member_groups' => $user_info['groups'],
1134
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1135
			)
1136
		);
1137
		$removals = array();
1138
		while ($row = $smcFunc['db_fetch_assoc']($request))
1139
		{
1140
			if (empty($row['add_deny']))
1141
				$removals[] = $row['permission'];
1142
			else
1143
				$user_info['permissions'][] = $row['permission'];
1144
		}
1145
		$smcFunc['db_free_result']($request);
1146
1147
		if (isset($cache_groups))
1148
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1149
	}
1150
1151
	// Get the board permissions.
1152
	if (!empty($board))
1153
	{
1154
		// Make sure the board (if any) has been loaded by loadBoard().
1155
		if (!isset($board_info['profile']))
1156
			fatal_lang_error('no_board');
1157
1158
		$request = $smcFunc['db_query']('', '
1159
			SELECT permission, add_deny
1160
			FROM {db_prefix}board_permissions
1161
			WHERE (id_group IN ({array_int:member_groups})
1162
				' . $spider_restrict . ')
1163
				AND id_profile = {int:id_profile}',
1164
			array(
1165
				'member_groups' => $user_info['groups'],
1166
				'id_profile' => $board_info['profile'],
1167
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1168
			)
1169
		);
1170
		while ($row = $smcFunc['db_fetch_assoc']($request))
1171
		{
1172
			if (empty($row['add_deny']))
1173
				$removals[] = $row['permission'];
1174
			else
1175
				$user_info['permissions'][] = $row['permission'];
1176
		}
1177
		$smcFunc['db_free_result']($request);
1178
	}
1179
1180
	// Remove all the permissions they shouldn't have ;).
1181
	if (!empty($modSettings['permission_enable_deny']))
1182
		$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...
1183
1184
	if (isset($cache_groups) && !empty($board) && $modSettings['cache_enable'] >= 2)
1185
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1186
1187
	// Banned?  Watch, don't touch..
1188
	banPermissions();
1189
1190
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1191
	if (!$user_info['is_guest'])
1192
	{
1193
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1194
		{
1195
			require_once($sourcedir . '/Subs-Auth.php');
1196
			rebuildModCache();
1197
		}
1198
		else
1199
			$user_info['mod_cache'] = $_SESSION['mc'];
1200
1201
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1202
		// For example this drastically simplifies certain changes to the profile area.
1203
		$user_info['permissions'][] = 'is_not_guest';
1204
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1205
		$user_info['permissions'][] = 'profile_view_own';
1206
		if (in_array('profile_view', $user_info['permissions']))
1207
			$user_info['permissions'][] = 'profile_view_any';
1208
	}
1209
}
1210
1211
/**
1212
 * Loads an array of users' data by ID or member_name.
1213
 *
1214
 * @param array|string $users An array of users by id or name or a single username/id
1215
 * @param bool $is_name Whether $users contains names
1216
 * @param string $set What kind of data to load (normal, profile, minimal)
1217
 * @return array The ids of the members loaded
1218
 */
1219
function loadMemberData($users, $is_name = false, $set = 'normal')
1220
{
1221
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
1222
	global $image_proxy_enabled, $user_info;
1223
1224
	// Can't just look for no users :P.
1225
	if (empty($users))
1226
		return array();
1227
1228
	// Pass the set value
1229
	$context['loadMemberContext_set'] = $set;
1230
1231
	// Make sure it's an array.
1232
	$users = !is_array($users) ? array($users) : array_unique($users);
1233
	$loaded_ids = array();
1234
1235
	if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1236
	{
1237
		$users = array_values($users);
1238
		for ($i = 0, $n = count($users); $i < $n; $i++)
1239
		{
1240
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1241
			if ($data == null)
1242
				continue;
1243
1244
			$loaded_ids[] = $data['id_member'];
1245
			$user_profile[$data['id_member']] = $data;
1246
			unset($users[$i]);
1247
		}
1248
	}
1249
1250
	// Used by default
1251
	$select_columns = '
1252
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1253
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1254
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1255
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.id_post_group, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online,
1256
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1257
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1258
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1259
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1260
	$select_tables = '
1261
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1262
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1263
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1264
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1265
1266
	// We add or replace according the the set
1267
	switch ($set)
1268
	{
1269
		case 'normal':
1270
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1271
			break;
1272
		case 'profile':
1273
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1274
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1275
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1276
			break;
1277
		case 'minimal':
1278
			$select_columns = '
1279
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1280
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1281
			$select_tables = '';
1282
			break;
1283
		default:
1284
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1285
	}
1286
1287
	// Allow mods to easily add to the selected member data
1288
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1289
1290
	if (!empty($users))
1291
	{
1292
		// Load the member's data.
1293
		$request = $smcFunc['db_query']('', '
1294
			SELECT' . $select_columns . '
1295
			FROM {db_prefix}members AS mem' . $select_tables . '
1296
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1297
			array(
1298
				'blank_string' => '',
1299
				'users' => $users,
1300
			)
1301
		);
1302
		$new_loaded_ids = array();
1303
		while ($row = $smcFunc['db_fetch_assoc']($request))
1304
		{
1305
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1306
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1307
1308
			// Take care of proxying avatar if required, do this here for maximum reach
1309
			if ($image_proxy_enabled && !empty($row['avatar']) && stripos($row['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
1310
				$row['avatar'] = get_proxied_url($row['avatar']);
1311
1312
			// Keep track of the member's normal member group
1313
			$row['primary_group'] = !empty($row['member_group']) ? $row['member_group'] : '';
1314
1315
			if (isset($row['member_ip']))
1316
				$row['member_ip'] = inet_dtop($row['member_ip']);
1317
			if (isset($row['member_ip2']))
1318
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1319
			$row['id_member'] = (int) $row['id_member'];
1320
			$new_loaded_ids[] = $row['id_member'];
1321
			$loaded_ids[] = $row['id_member'];
1322
			$row['options'] = array();
1323
			$user_profile[$row['id_member']] = $row;
1324
		}
1325
		$smcFunc['db_free_result']($request);
1326
	}
1327
1328
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1329
	{
1330
		$request = $smcFunc['db_query']('', '
1331
			SELECT id_member, variable, value
1332
			FROM {db_prefix}themes
1333
			WHERE id_member IN ({array_int:loaded_ids})',
1334
			array(
1335
				'loaded_ids' => $new_loaded_ids,
1336
			)
1337
		);
1338
		while ($row = $smcFunc['db_fetch_assoc']($request))
1339
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1340
		$smcFunc['db_free_result']($request);
1341
	}
1342
1343
	$additional_mods = array();
1344
1345
	// Are any of these users in groups assigned to moderate this board?
1346
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1347
	{
1348
		foreach ($loaded_ids as $a_member)
1349
		{
1350
			if (!empty($user_profile[$a_member]['additional_groups']))
1351
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1352
			else
1353
				$groups = array($user_profile[$a_member]['id_group']);
1354
1355
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1356
1357
			if (!empty($temp))
1358
			{
1359
				$additional_mods[] = $a_member;
1360
			}
1361
		}
1362
	}
1363
1364
	if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1365
	{
1366
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1367
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1368
	}
1369
1370
	// Are we loading any moderators?  If so, fix their group data...
1371
	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)
1372
	{
1373
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1374
		{
1375
			$request = $smcFunc['db_query']('', '
1376
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1377
				FROM {db_prefix}membergroups
1378
				WHERE id_group = {int:moderator_group}
1379
				LIMIT 1',
1380
				array(
1381
					'moderator_group' => 3,
1382
				)
1383
			);
1384
			$row = $smcFunc['db_fetch_assoc']($request);
1385
			$smcFunc['db_free_result']($request);
1386
1387
			cache_put_data('moderator_group_info', $row, 480);
1388
		}
1389
1390
		foreach ($temp_mods as $id)
1391
		{
1392
			// By popular demand, don't show admins or global moderators as moderators.
1393
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1394
				$user_profile[$id]['member_group'] = $row['member_group'];
1395
1396
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1397
			if (!empty($row['icons']))
1398
				$user_profile[$id]['icons'] = $row['icons'];
1399
			if (!empty($row['member_group_color']))
1400
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1401
		}
1402
	}
1403
1404
	return $loaded_ids;
1405
}
1406
1407
/**
1408
 * Loads the user's basic values... meant for template/theme usage.
1409
 *
1410
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1411
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1412
 * @return boolean Whether or not the data was loaded successfully
1413
 */
1414
function loadMemberContext($user, $display_custom_fields = false)
1415
{
1416
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1417
	global $context, $modSettings, $settings, $smcFunc;
1418
	static $dataLoaded = array();
1419
	static $loadedLanguages = array();
1420
1421
	// If this person's data is already loaded, skip it.
1422
	if (isset($dataLoaded[$user]))
1423
		return true;
1424
1425
	// We can't load guests or members not loaded by loadMemberData()!
1426
	if ($user == 0)
1427
		return false;
1428
	if (!isset($user_profile[$user]))
1429
	{
1430
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1431
		return false;
1432
	}
1433
1434
	// Well, it's loaded now anyhow.
1435
	$dataLoaded[$user] = true;
1436
	$profile = $user_profile[$user];
1437
1438
	// Censor everything.
1439
	censorText($profile['signature']);
1440
	censorText($profile['personal_text']);
1441
1442
	// Set things up to be used before hand.
1443
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1444
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1445
1446
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
0 ignored issues
show
introduced by
Consider adding parentheses for clarity. Current Interpretation: $profile['is_online'] = ...ofile['is_online'] > 0), Probably Intended Meaning: $profile['is_online'] = ...ofile['is_online'] > 0)
Loading history...
1447
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1448
	// Setup the buddy status here (One whole in_array call saved :P)
1449
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1450
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1451
1452
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1453
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1454
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1455
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1456
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1457
	else //not set, bye bye
1458
		$group_icon_url = '';
1459
1460
	// These minimal values are always loaded
1461
	$memberContext[$user] = array(
1462
		'username' => $profile['member_name'],
1463
		'name' => $profile['real_name'],
1464
		'id' => $profile['id_member'],
1465
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1466
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($modSettings['onlineEnable']) ? 'class="pm_icon"' : '') . '>' . $profile['real_name'] . '</a>',
1467
		'email' => $profile['email_address'],
1468
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1469
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1470
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1471
	);
1472
1473
	// If the set isn't minimal then load the monstrous array.
1474
	if ($context['loadMemberContext_set'] != 'minimal')
1475
	{
1476
		// Go the extra mile and load the user's native language name.
1477
		if (empty($loadedLanguages))
1478
			$loadedLanguages = getLanguages();
1479
1480
		$memberContext[$user] += array(
1481
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1482
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1483
			'link_color' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . $profile['real_name'] . '" ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</a>',
1484
			'is_buddy' => $profile['buddy'],
1485
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1486
			'buddies' => $buddy_list,
1487
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1488
			'blurb' => $profile['personal_text'],
1489
			'website' => array(
1490
				'title' => $profile['website_title'],
1491
				'url' => $profile['website_url'],
1492
			),
1493
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1494
			'signature' => $profile['signature'],
1495
			'real_posts' => $profile['posts'],
1496
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1497
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1498
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
1499
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1500
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1501
			'online' => array(
1502
				'is_online' => $profile['is_online'],
1503
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1504
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1505
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1506
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1507
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1508
			),
1509
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1510
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1511
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1512
			'options' => $profile['options'],
1513
			'is_guest' => false,
1514
			'primary_group' => $profile['primary_group'],
1515
			'group' => $profile['member_group'],
1516
			'group_color' => $profile['member_group_color'],
1517
			'group_id' => $profile['id_group'],
1518
			'post_group' => $profile['post_group'],
1519
			'post_group_color' => $profile['post_group_color'],
1520
			'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]),
1521
			'warning' => $profile['warning'],
1522
			'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' : (''))),
1523
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1524
			'custom_fields' => array(),
1525
		);
1526
	}
1527
1528
	// If the set isn't minimal then load their avatar as well.
1529
	if ($context['loadMemberContext_set'] != 'minimal')
1530
	{
1531
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1532
		{
1533
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1534
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1535
			else
1536
				$image = get_gravatar_url($profile['email_address']);
1537
		}
1538
		else
1539
		{
1540
			// So it's stored in the member table?
1541
			if (!empty($profile['avatar']))
1542
			{
1543
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1544
			}
1545
			elseif (!empty($profile['filename']))
1546
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1547
			// Right... no avatar...use the default one
1548
			else
1549
				$image = $modSettings['avatar_url'] . '/default.png';
1550
		}
1551
		if (!empty($image))
1552
			$memberContext[$user]['avatar'] = array(
1553
				'name' => $profile['avatar'],
1554
				'image' => '<img class="avatar" src="' . $image . '" alt="avatar_' . $profile['member_name'] . '">',
1555
				'href' => $image,
1556
				'url' => $image,
1557
			);
1558
	}
1559
1560
	// Are we also loading the members custom fields into context?
1561
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1562
	{
1563
		$memberContext[$user]['custom_fields'] = array();
1564
1565
		if (!isset($context['display_fields']))
1566
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1567
1568
		foreach ($context['display_fields'] as $custom)
1569
		{
1570
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1571
				continue;
1572
1573
			$value = $profile['options'][$custom['col_name']];
1574
1575
			$fieldOptions = array();
1576
			$currentKey = 0;
1577
1578
			// Create a key => value array for multiple options fields
1579
			if (!empty($custom['options']))
1580
				foreach ($custom['options'] as $k => $v)
1581
				{
1582
					$fieldOptions[] = $v;
1583
					if (empty($currentKey))
1584
						$currentKey = $v == $value ? $k : 0;
1585
				}
1586
1587
			// BBC?
1588
			if ($custom['bbc'])
1589
				$value = parse_bbc($value);
1590
1591
			// ... or checkbox?
1592
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1593
				$value = $value ? $txt['yes'] : $txt['no'];
1594
1595
			// Enclosing the user input within some other text?
1596
			if (!empty($custom['enclose']))
1597
				$value = strtr($custom['enclose'], array(
1598
					'{SCRIPTURL}' => $scripturl,
1599
					'{IMAGES_URL}' => $settings['images_url'],
1600
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1601
					'{INPUT}' => $value,
1602
					'{KEY}' => $currentKey,
1603
				));
1604
1605
			$memberContext[$user]['custom_fields'][] = array(
1606
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1607
				'col_name' => $custom['col_name'],
1608
				'value' => un_htmlspecialchars($value),
1609
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1610
			);
1611
		}
1612
	}
1613
1614
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1615
	return true;
1616
}
1617
1618
/**
1619
 * Loads the user's custom profile fields
1620
 *
1621
 * @param integer|array $users A single user ID or an array of user IDs
1622
 * @param string|array $params Either a string or an array of strings with profile field names
1623
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
1624
 */
1625
function loadMemberCustomFields($users, $params)
1626
{
1627
	global $smcFunc, $txt, $scripturl, $settings;
1628
1629
	// Do not waste my time...
1630
	if (empty($users) || empty($params))
1631
		return false;
1632
1633
	// Make sure it's an array.
1634
	$users = !is_array($users) ? array($users) : array_unique($users);
1635
	$params = !is_array($params) ? array($params) : array_unique($params);
1636
	$return = array();
1637
1638
	$request = $smcFunc['db_query']('', '
1639
		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,
1640
		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
1641
		FROM {db_prefix}themes AS t
1642
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1643
		WHERE id_member IN ({array_int:loaded_ids})
1644
			AND variable IN ({array_string:params})
1645
		ORDER BY field_order',
1646
		array(
1647
			'loaded_ids' => $users,
1648
			'params' => $params,
1649
		)
1650
	);
1651
1652
	while ($row = $smcFunc['db_fetch_assoc']($request))
1653
	{
1654
		$fieldOptions = array();
1655
		$currentKey = 0;
1656
1657
		// Create a key => value array for multiple options fields
1658
		if (!empty($row['field_options']))
1659
			foreach (explode(',', $row['field_options']) as $k => $v)
1660
			{
1661
				$fieldOptions[] = $v;
1662
				if (empty($currentKey))
1663
					$currentKey = $v == $row['value'] ? $k : 0;
1664
			}
1665
1666
		// BBC?
1667
		if (!empty($row['bbc']))
1668
			$row['value'] = parse_bbc($row['value']);
1669
1670
		// ... or checkbox?
1671
		elseif (isset($row['type']) && $row['type'] == 'check')
1672
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1673
1674
		// Enclosing the user input within some other text?
1675
		if (!empty($row['enclose']))
1676
			$row['value'] = strtr($row['enclose'], array(
1677
				'{SCRIPTURL}' => $scripturl,
1678
				'{IMAGES_URL}' => $settings['images_url'],
1679
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1680
				'{INPUT}' => un_htmlspecialchars($row['value']),
1681
				'{KEY}' => $currentKey,
1682
			));
1683
1684
		// Send a simple array if there is just 1 param
1685
		if (count($params) == 1)
1686
			$return[$row['id_member']] = $row;
1687
1688
		// More than 1? knock yourself out...
1689
		else
1690
		{
1691
			if (!isset($return[$row['id_member']]))
1692
				$return[$row['id_member']] = array();
1693
1694
			$return[$row['id_member']][$row['variable']] = $row;
1695
		}
1696
	}
1697
1698
	$smcFunc['db_free_result']($request);
1699
1700
	return !empty($return) ? $return : false;
1701
}
1702
1703
/**
1704
 * Loads information about what browser the user is viewing with and places it in $context
1705
 *  - uses the class from {@link Class-BrowserDetect.php}
1706
 */
1707
function detectBrowser()
1708
{
1709
	// Load the current user's browser of choice
1710
	$detector = new browser_detector;
1711
	$detector->detectBrowser();
1712
}
1713
1714
/**
1715
 * Are we using this browser?
1716
 *
1717
 * Wrapper function for detectBrowser
1718
 * @param string $browser The browser we are checking for.
1719
 * @return bool Whether or not the current browser is what we're looking for
1720
*/
1721
function isBrowser($browser)
1722
{
1723
	global $context;
1724
1725
	// Don't know any browser!
1726
	if (empty($context['browser']))
1727
		detectBrowser();
1728
1729
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1730
}
1731
1732
/**
1733
 * Load a theme, by ID.
1734
 *
1735
 * @param int $id_theme The ID of the theme to load
1736
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1737
 */
1738
function loadTheme($id_theme = 0, $initialize = true)
1739
{
1740
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
1741
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
1742
	global $context, $settings, $options, $sourcedir, $ssi_theme, $smcFunc, $language, $board, $image_proxy_enabled;
1743
1744
	// The theme was specified by parameter.
1745
	if (!empty($id_theme))
1746
		$id_theme = (int) $id_theme;
1747
	// The theme was specified by REQUEST.
1748
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1749
	{
1750
		$id_theme = (int) $_REQUEST['theme'];
1751
		$_SESSION['id_theme'] = $id_theme;
1752
	}
1753
	// The theme was specified by REQUEST... previously.
1754
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1755
		$id_theme = (int) $_SESSION['id_theme'];
1756
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1757
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']))
1758
		$id_theme = $user_info['theme'];
1759
	// The theme was specified by the board.
1760
	elseif (!empty($board_info['theme']))
1761
		$id_theme = $board_info['theme'];
1762
	// The theme is the forum's default.
1763
	else
1764
		$id_theme = $modSettings['theme_guests'];
1765
1766
	// Verify the id_theme... no foul play.
1767
	// Always allow the board specific theme, if they are overriding.
1768
	if (!empty($board_info['theme']) && $board_info['override_theme'])
1769
		$id_theme = $board_info['theme'];
1770
	// If they have specified a particular theme to use with SSI allow it to be used.
1771
	elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1772
		$id_theme = (int) $id_theme;
1773
	elseif (!empty($modSettings['enableThemes']) && !allowedTo('admin_forum'))
1774
	{
1775
		$themes = explode(',', $modSettings['enableThemes']);
1776
		if (!in_array($id_theme, $themes))
1777
			$id_theme = $modSettings['theme_guests'];
1778
		else
1779
			$id_theme = (int) $id_theme;
1780
	}
1781
		
1782
	// Allow mod authors the option to override the theme id for custom page themes
1783
	call_integration_hook('integrate_pre_load_theme', array(&$id_theme));
1784
1785
	// We already load the basic stuff?
1786
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme )
1787
	{
1788
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1789
1790
		// Disable image proxy if we don't have SSL enabled
1791
		if (empty($modSettings['force_ssl']))
1792
			$image_proxy_enabled = false;
1793
1794
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2 && ($temp = cache_get_data('theme_settings-' . $id_theme . ':' . $member, 60)) != null && time() - 60 > $modSettings['settings_updated'])
1795
		{
1796
			$themeData = $temp;
1797
			$flag = true;
1798
		}
1799
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1800
			$themeData = $temp + array($member => array());
1801
		else
1802
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1803
1804
		if (empty($flag))
1805
		{
1806
			// Load variables from the current or default theme, global or this user's.
1807
			$result = $smcFunc['db_query']('', '
1808
				SELECT variable, value, id_member, id_theme
1809
				FROM {db_prefix}themes
1810
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1811
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)') .'
1812
				ORDER BY id_theme asc',
1813
				array(
1814
					'id_theme' => $id_theme,
1815
					'id_member' => $member,
1816
				)
1817
			);
1818
			// Pick between $settings and $options depending on whose data it is.
1819
			while ($row = $smcFunc['db_fetch_assoc']($result))
1820
			{
1821
				// There are just things we shouldn't be able to change as members.
1822
				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')))
1823
					continue;
1824
1825
				// If this is the theme_dir of the default theme, store it.
1826
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1827
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1828
1829
				// If this isn't set yet, is a theme option, or is not the default theme..
1830
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1831
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1832
			}
1833
			$smcFunc['db_free_result']($result);
1834
1835
			if (!empty($themeData[-1]))
1836
				foreach ($themeData[-1] as $k => $v)
1837
				{
1838
					if (!isset($themeData[$member][$k]))
1839
						$themeData[$member][$k] = $v;
1840
				}
1841
1842
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1843
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1844
			// Only if we didn't already load that part of the cache...
1845
			elseif (!isset($temp))
1846
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1847
		}
1848
1849
		$settings = $themeData[0];
1850
		$options = $themeData[$member];
1851
1852
		$settings['theme_id'] = $id_theme;
1853
1854
		$settings['actual_theme_url'] = $settings['theme_url'];
1855
		$settings['actual_images_url'] = $settings['images_url'];
1856
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1857
1858
		$settings['template_dirs'] = array();
1859
		// This theme first.
1860
		$settings['template_dirs'][] = $settings['theme_dir'];
1861
1862
		// Based on theme (if there is one).
1863
		if (!empty($settings['base_theme_dir']))
1864
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1865
1866
		// Lastly the default theme.
1867
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1868
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1869
	}
1870
1871
1872
	if (!$initialize)
1873
		return;
1874
1875
	// Check to see if we're forcing SSL
1876
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
1877
		!httpsOn() && SMF != 'SSI')
0 ignored issues
show
introduced by
The condition SMF != 'SSI' is always false.
Loading history...
1878
	{
1879
		if (isset($_GET['sslRedirect']))
1880
		{
1881
			loadLanguage('Errors');
1882
			fatal_lang_error($txt['login_ssl_required']);
1883
		}
1884
1885
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
1886
	}
1887
1888
	// Check to see if they're accessing it from the wrong place.
1889
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1890
	{
1891
		$detected_url = httpsOn() ? 'https://' : 'http://';
1892
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1893
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1894
		if ($temp != '/')
1895
			$detected_url .= $temp;
1896
	}
1897
	if (isset($detected_url) && $detected_url != $boardurl)
1898
	{
1899
		// Try #1 - check if it's in a list of alias addresses.
1900
		if (!empty($modSettings['forum_alias_urls']))
1901
		{
1902
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1903
1904
			foreach ($aliases as $alias)
1905
			{
1906
				// Rip off all the boring parts, spaces, etc.
1907
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1908
					$do_fix = true;
1909
			}
1910
		}
1911
1912
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1913
		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...
1914
		{
1915
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1916
			if (empty($_GET))
1917
				redirectexit('wwwRedirect');
1918
			else
1919
			{
1920
				$k = key($_GET);
1921
				$v = current($_GET);
1922
1923
				if ($k != 'wwwRedirect')
1924
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1925
			}
1926
		}
1927
1928
		// #3 is just a check for SSL...
1929
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1930
			$do_fix = true;
1931
1932
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1933
		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...
1934
		{
1935
			// Caching is good ;).
1936
			$oldurl = $boardurl;
1937
1938
			// Fix $boardurl and $scripturl.
1939
			$boardurl = $detected_url;
1940
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1941
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1942
1943
			// Fix the theme urls...
1944
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1945
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1946
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1947
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1948
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1949
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1950
1951
			// And just a few mod settings :).
1952
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1953
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1954
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
1955
1956
			// Clean up after loadBoard().
1957
			if (isset($board_info['moderators']))
1958
			{
1959
				foreach ($board_info['moderators'] as $k => $dummy)
1960
				{
1961
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1962
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1963
				}
1964
			}
1965
			foreach ($context['linktree'] as $k => $dummy)
1966
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1967
		}
1968
	}
1969
	// Set up the contextual user array.
1970
	if (!empty($user_info))
1971
	{
1972
		$context['user'] = array(
1973
			'id' => $user_info['id'],
1974
			'is_logged' => !$user_info['is_guest'],
1975
			'is_guest' => &$user_info['is_guest'],
1976
			'is_admin' => &$user_info['is_admin'],
1977
			'is_mod' => &$user_info['is_mod'],
1978
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1979
			'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'])))),
1980
			'name' => $user_info['username'],
1981
			'language' => $user_info['language'],
1982
			'email' => $user_info['email'],
1983
			'ignoreusers' => $user_info['ignoreusers'],
1984
		);
1985
		if (!$context['user']['is_guest'])
1986
			$context['user']['name'] = $user_info['name'];
1987
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1988
			$context['user']['name'] = $txt['guest_title'];
1989
1990
		// Determine the current smiley set.
1991
		$smiley_sets_known = explode(',', $modSettings['smiley_sets_known']);
1992
		$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'];
1993
		$context['user']['smiley_set'] = $user_info['smiley_set'];
1994
1995
		// Determine current smiley set extension
1996
		$smiley_sets_exts = explode(',', $modSettings['smiley_sets_exts']);
1997
		$user_info['smiley_set_ext'] = $smiley_sets_exts[array_search($user_info['smiley_set'], $smiley_sets_known)];
1998
		$context['user']['smiley_set_ext'] = $user_info['smiley_set_ext'];
1999
2000
		// Determine global default smiley set extension
2001
		$context['user']['smiley_set_default_ext'] = $smiley_sets_exts[array_search($modSettings['smiley_sets_default'], $smiley_sets_known)];
2002
	}
2003
	else
2004
	{
2005
		// What to do when there is no $user_info (e.g., an error very early in the login process)
2006
		$context['user'] = array(
2007
			'id' => -1,
2008
			'is_logged' => false,
2009
			'is_guest' => true,
2010
			'is_mod' => false,
2011
			'can_mod' => false,
2012
			'name' => $txt['guest_title'],
2013
			'language' => $language,
2014
			'email' => '',
2015
			'ignoreusers' => array(),
2016
		);
2017
		// Note we should stuff $user_info with some guest values also...
2018
		$user_info = array(
2019
			'id' => 0,
2020
			'is_guest' => true,
2021
			'is_admin' => false,
2022
			'is_mod' => false,
2023
			'username' => $txt['guest_title'],
2024
			'language' => $language,
2025
			'email' => '',
2026
			'smiley_set' => '',
2027
			'permissions' => array(),
2028
			'groups' => array(),
2029
			'ignoreusers' => array(),
2030
			'possibly_robot' => true,
2031
			'time_offset' => 0,
2032
			'time_format' => $modSettings['time_format'],
2033
		);
2034
	}
2035
2036
	// Some basic information...
2037
	if (!isset($context['html_headers']))
2038
		$context['html_headers'] = '';
2039
	if (!isset($context['javascript_files']))
2040
		$context['javascript_files'] = array();
2041
	if (!isset($context['css_files']))
2042
		$context['css_files'] = array();
2043
	if (!isset($context['css_header']))
2044
		$context['css_header'] = array();
2045
	if (!isset($context['javascript_inline']))
2046
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2047
	if (!isset($context['javascript_vars']))
2048
		$context['javascript_vars'] = array();
2049
2050
	$context['login_url'] =  $scripturl . '?action=login2';
2051
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2052
	$context['session_var'] = $_SESSION['session_var'];
2053
	$context['session_id'] = $_SESSION['session_value'];
2054
	$context['forum_name'] = $mbname;
2055
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2056
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2057
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2058
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2059
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2060
	if (isset($modSettings['load_average']))
2061
		$context['load_average'] = $modSettings['load_average'];
2062
2063
	// Detect the browser. This is separated out because it's also used in attachment downloads
2064
	detectBrowser();
2065
2066
	// Set the top level linktree up.
2067
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2068
	if (empty($context['linktree']))
2069
		$context['linktree'] = array();
2070
	array_unshift($context['linktree'], array(
2071
		'url' => $scripturl,
2072
		'name' => $context['forum_name_html_safe']
2073
	));
2074
2075
	// This allows sticking some HTML on the page output - useful for controls.
2076
	$context['insert_after_template'] = '';
2077
2078
	if (!isset($txt))
2079
		$txt = array();
2080
2081
	$simpleActions = array(
2082
		'findmember',
2083
		'helpadmin',
2084
		'printpage',
2085
		'spellcheck',
2086
	);
2087
2088
	// Parent action => array of areas
2089
	$simpleAreas = array(
2090
		'profile' => array('popup', 'alerts_popup',),
2091
	);
2092
2093
	// Parent action => array of subactions
2094
	$simpleSubActions = array(
2095
		'pm' => array('popup',),
2096
		'signup' => array('usernamecheck'),
2097
	);
2098
2099
	// Extra params like ;preview ;js, etc.
2100
	$extraParams = array(
2101
		'preview',
2102
		'splitjs',
2103
	);
2104
2105
	// Actions that specifically uses XML output.
2106
	$xmlActions = array(
2107
		'quotefast',
2108
		'jsmodify',
2109
		'xmlhttp',
2110
		'post2',
2111
		'suggest',
2112
		'stats',
2113
		'notifytopic',
2114
		'notifyboard',
2115
	);
2116
2117
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2118
2119
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2120
	(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2121
	(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2122
2123
	// See if theres any extra param to check.
2124
	$requiresXML = false;
2125
	foreach ($extraParams as $key => $extra)
2126
		if (isset($_REQUEST[$extra]))
2127
			$requiresXML = true;
2128
2129
	// Output is fully XML, so no need for the index template.
2130
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2131
	{
2132
		loadLanguage('index+Modifications');
2133
		loadTemplate('Xml');
2134
		$context['template_layers'] = array();
2135
	}
2136
2137
	// These actions don't require the index template at all.
2138
	elseif (!empty($context['simple_action']))
2139
	{
2140
		loadLanguage('index+Modifications');
2141
		$context['template_layers'] = array();
2142
	}
2143
2144
	else
2145
	{
2146
		// Custom templates to load, or just default?
2147
		if (isset($settings['theme_templates']))
2148
			$templates = explode(',', $settings['theme_templates']);
2149
		else
2150
			$templates = array('index');
2151
2152
		// Load each template...
2153
		foreach ($templates as $template)
2154
			loadTemplate($template);
2155
2156
		// ...and attempt to load their associated language files.
2157
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2158
		loadLanguage($required_files, '', false);
2159
2160
		// Custom template layers?
2161
		if (isset($settings['theme_layers']))
2162
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2163
		else
2164
			$context['template_layers'] = array('html', 'body');
2165
	}
2166
2167
	// Initialize the theme.
2168
	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

2168
	loadSubTemplate('init', /** @scrutinizer ignore-type */ 'ignore');
Loading history...
2169
2170
	// Allow overriding the board wide time/number formats.
2171
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2172
		$user_info['time_format'] = $txt['time_format'];
2173
2174
	// Set the character set from the template.
2175
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2176
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2177
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2178
2179
	// Guests may still need a name.
2180
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2181
		$context['user']['name'] = $txt['guest_title'];
2182
2183
	// Any theme-related strings that need to be loaded?
2184
	if (!empty($settings['require_theme_strings']))
2185
		loadLanguage('ThemeStrings', '', false);
2186
2187
	// Make a special URL for the language.
2188
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2189
2190
	// And of course, let's load the default CSS file.
2191
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2192
2193
	// Here is my luvly Responsive CSS
2194
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2195
2196
	if ($context['right_to_left'])
2197
		loadCSSFile('rtl.css', array('order_pos' => 200), 'smf_rtl');
2198
2199
	// We allow theme variants, because we're cool.
2200
	$context['theme_variant'] = '';
2201
	$context['theme_variant_url'] = '';
2202
	if (!empty($settings['theme_variants']))
2203
	{
2204
		// Overriding - for previews and that ilk.
2205
		if (!empty($_REQUEST['variant']))
2206
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2207
		// User selection?
2208
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2209
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2210
		// If not a user variant, select the default.
2211
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2212
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2213
2214
		// Do this to keep things easier in the templates.
2215
		$context['theme_variant'] = '_' . $context['theme_variant'];
2216
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2217
2218
		if (!empty($context['theme_variant']))
2219
		{
2220
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2221
			if ($context['right_to_left'])
2222
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 400), 'smf_rtl' . $context['theme_variant']);
2223
		}
2224
	}
2225
2226
	// Let's be compatible with old themes!
2227
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2228
		$context['template_layers'] = array('main');
2229
2230
	$context['tabindex'] = 1;
2231
2232
	// Compatibility.
2233
	if (!isset($settings['theme_version']))
2234
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2235
2236
	// Default JS variables for use in every theme
2237
	$context['javascript_vars'] = array(
2238
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2239
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2240
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2241
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2242
		'smf_smiley_sets_default' => '"' . $modSettings['smiley_sets_default'] . '"',
2243
		'smf_smiley_sets_exts' => '"' . $modSettings['smiley_sets_exts'] . '"',
2244
		'smf_smiley_sets_default_ext' => '"' . $context['user']['smiley_set_default_ext'] . '"',
2245
		'smf_scripturl' => '"' . $scripturl . '"',
2246
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2247
		'smf_charset' => '"' . $context['character_set'] . '"',
2248
		'smf_session_id' => '"' . $context['session_id'] . '"',
2249
		'smf_session_var' => '"' . $context['session_var'] . '"',
2250
		'smf_member_id' => $context['user']['id'],
2251
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2252
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2253
		'banned_text' => JavaScriptEscape(sprintf($txt['your_ban'], $context['user']['name'])),
2254
	);
2255
2256
	// Add the JQuery library to the list of files to load.
2257
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2258
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2259
2260
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2261
		loadJavaScriptFile('jquery-3.2.1.min.js', array('seed' => false), 'smf_jquery');
2262
2263
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2264
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2265
2266
	// Auto loading? template_javascript() will take care of the local half of this.
2267
	else
2268
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2269
2270
	// Queue our JQuery plugins!
2271
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2272
	if (!$user_info['is_guest'])
2273
	{
2274
		loadJavaScriptFile('jquery.custom-scrollbar.js', array('minimize' => true), 'smf_jquery_scrollbar');
2275
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2276
	}
2277
2278
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2279
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2280
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2281
2282
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2283
	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())
2284
	{
2285
		if (isBrowser('possibly_robot'))
2286
		{
2287
			// @todo Maybe move this somewhere better?!
2288
			require_once($sourcedir . '/ScheduledTasks.php');
2289
2290
			// What to do, what to do?!
2291
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2292
				AutoTask();
2293
			else
2294
				ReduceMailQueue();
2295
		}
2296
		else
2297
		{
2298
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2299
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
2300
2301
			addInlineJavaScript('
2302
		function smfAutoTask()
2303
		{
2304
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2305
		}
2306
		window.setTimeout("smfAutoTask();", 1);');
2307
		}
2308
	}
2309
2310
	// And we should probably trigger the cron too.
2311
	if (empty($modSettings['cron_is_real_cron']))
2312
	{
2313
		$ts = time();
2314
		$ts -= $ts % 15;
2315
		addInlineJavaScript('
2316
	function triggerCron()
2317
	{
2318
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2319
	}
2320
	window.setTimeout(triggerCron, 1);', true);
2321
	}
2322
2323
	// Filter out the restricted boards from the linktree
2324
	if (!$user_info['is_admin'] && !empty($board))
2325
	{
2326
		foreach ($context['linktree'] as $k => $element)
2327
		{
2328
			if (!empty($element['groups']) &&
2329
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2330
				(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2331
			{
2332
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2333
				$context['linktree'][$k]['extra_before'] = '<i>';
2334
				$context['linktree'][$k]['extra_after'] = '</i>';
2335
				unset($context['linktree'][$k]['url']);
2336
			}
2337
		}
2338
	}
2339
2340
	// Any files to include at this point?
2341
	if (!empty($modSettings['integrate_theme_include']))
2342
	{
2343
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2344
		foreach ($theme_includes as $include)
2345
		{
2346
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2347
			if (file_exists($include))
2348
				require_once($include);
2349
		}
2350
	}
2351
2352
	// Call load theme integration functions.
2353
	call_integration_hook('integrate_load_theme');
2354
2355
	// We are ready to go.
2356
	$context['theme_loaded'] = true;
2357
}
2358
2359
/**
2360
 * Load a template - if the theme doesn't include it, use the default.
2361
 * What this function does:
2362
 *  - loads a template file with the name template_name from the current, default, or base theme.
2363
 *  - detects a wrong default theme directory and tries to work around it.
2364
 *
2365
 * @uses the template_include() function to include the file.
2366
 * @param string $template_name The name of the template to load
2367
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2368
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2369
 * @return boolean Whether or not the template was loaded
2370
 */
2371
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2372
{
2373
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
2374
2375
	// Do any style sheets first, cause we're easy with those.
2376
	if (!empty($style_sheets))
2377
	{
2378
		if (!is_array($style_sheets))
2379
			$style_sheets = array($style_sheets);
2380
2381
		foreach ($style_sheets as $sheet)
2382
			loadCSSFile($sheet . '.css', array(), $sheet);
2383
	}
2384
2385
	// No template to load?
2386
	if ($template_name === false)
0 ignored issues
show
introduced by
The condition $template_name === false is always false.
Loading history...
2387
		return true;
2388
2389
	$loaded = false;
2390
	foreach ($settings['template_dirs'] as $template_dir)
2391
	{
2392
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2393
		{
2394
			$loaded = true;
2395
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2396
			break;
2397
		}
2398
	}
2399
2400
	if ($loaded)
0 ignored issues
show
introduced by
The condition $loaded is always false.
Loading history...
2401
	{
2402
		if ($db_show_debug === true)
2403
			$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 2390. Are you sure the iterator is never empty, otherwise this variable is not defined?
Loading history...
2404
2405
		// If they have specified an initialization function for this template, go ahead and call it now.
2406
		if (function_exists('template_' . $template_name . '_init'))
2407
			call_user_func('template_' . $template_name . '_init');
2408
	}
2409
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2410
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2411
	{
2412
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2413
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2414
2415
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2416
		{
2417
			loadLanguage('Errors');
2418
			echo '
2419
<div class="alert errorbox">
2420
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2421
</div>';
2422
		}
2423
2424
		loadTemplate($template_name);
2425
	}
2426
	// Cause an error otherwise.
2427
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2428
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2429
	elseif ($fatal)
2430
		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...
2431
	else
2432
		return false;
2433
}
2434
2435
/**
2436
 * Load a sub-template.
2437
 * What it does:
2438
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2439
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2440
 *	for debugging purposes.
2441
 *
2442
 * @todo get rid of reading $_REQUEST directly
2443
 *
2444
 * @param string $sub_template_name The name of the sub-template to load
2445
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2446
 */
2447
function loadSubTemplate($sub_template_name, $fatal = false)
2448
{
2449
	global $context, $txt, $db_show_debug;
2450
2451
	if ($db_show_debug === true)
2452
		$context['debug']['sub_templates'][] = $sub_template_name;
2453
2454
	// Figure out what the template function is named.
2455
	$theme_function = 'template_' . $sub_template_name;
2456
	if (function_exists($theme_function))
2457
		$theme_function();
2458
	elseif ($fatal === false)
2459
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2460
	elseif ($fatal !== 'ignore')
0 ignored issues
show
introduced by
The condition $fatal !== 'ignore' is always true.
Loading history...
2461
		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...
2462
2463
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2464
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2465
	{
2466
		echo '
2467
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2468
	}
2469
}
2470
2471
/**
2472
 * Add a CSS file for output later
2473
 *
2474
 * @param string $fileName The name of the file to load
2475
 * @param array $params An array of parameters
2476
 * Keys are the following:
2477
 * 	- ['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
2478
 * 	- ['default_theme'] (true/false): force use of default theme url
2479
 * 	- ['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
2480
 *  - ['validate'] (true/false): if true script will validate the local file exists
2481
 *  - ['rtl'] (string): additional file to load in RTL mode
2482
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2483
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2484
 *  - ['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
2485
 * @param string $id An ID to stick on the end of the filename for caching purposes
2486
 */
2487
function loadCSSFile($fileName, $params = array(), $id = '')
2488
{
2489
	global $settings, $context, $modSettings;
2490
2491
	if (empty($context['css_files_order']))
2492
		$context['css_files_order'] = array();
2493
2494
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $modSettings) ? $modSettings['browser_cache'] : '') : (is_string($params['seed']) ? ($params['seed'] = $params['seed'][0] === '?' ? $params['seed'] : '?' . $params['seed']) : '');
2495
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2496
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2497
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2498
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2499
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2500
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2501
2502
	// If this is an external file, automatically set this to false.
2503
	if (!empty($params['external']))
2504
		$params['minimize'] = false;
2505
2506
	// Account for shorthand like admin.css?alp21 filenames
2507
	$has_seed = strpos($fileName, '.css?');
2508
	$id = empty($id) ? strtr(basename(str_replace('.css', '', $fileName)), '?', '_') : $id;
2509
2510
	// Is this a local file?
2511
	if (empty($params['external']))
2512
	{
2513
		// Are we validating the the file exists?
2514
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/css/' . $fileName))
2515
		{
2516
			// Maybe the default theme has it?
2517
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/css/' . $fileName))
2518
			{
2519
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2520
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2521
			}
2522
2523
			else
2524
			{
2525
				$fileUrl = false;
2526
				$filePath = false;
2527
			}
2528
		}
2529
2530
		else
2531
		{
2532
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2533
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2534
		}
2535
	}
2536
2537
	// An external file doesn't have a filepath. Mock one for simplicity.
2538
	else
2539
	{
2540
		$fileUrl = $fileName;
2541
		$filePath = $fileName;
2542
	}
2543
2544
	// Add it to the array for use in the template
2545
	if (!empty($fileName))
2546
	{
2547
		// find a free number/position
2548
		while (isset($context['css_files_order'][$params['order_pos']]))
2549
			$params['order_pos']++;
2550
		$context['css_files_order'][$params['order_pos']] = $id;
2551
2552
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2553
	}
2554
2555
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2556
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2557
}
2558
2559
/**
2560
 * Add a block of inline css code to be executed later
2561
 *
2562
 * - only use this if you have to, generally external css files are better, but for very small changes
2563
 *   or for scripts that require help from PHP/whatever, this can be useful.
2564
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2565
 *
2566
 * @param string $css Some css code
2567
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
2568
 */
2569
function addInlineCss($css)
2570
{
2571
	global $context;
2572
2573
	// Gotta add something...
2574
	if (empty($css))
2575
		return false;
2576
2577
	$context['css_header'][] = $css;
2578
}
2579
2580
/**
2581
 * Add a Javascript file for output later
2582
 *
2583
 * @param string $fileName The name of the file to load
2584
 * @param array $params An array of parameter info
2585
 * Keys are the following:
2586
 * 	- ['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
2587
 * 	- ['default_theme'] (true/false): force use of default theme url
2588
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2589
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2590
 *	default theme if not found in the current theme
2591
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2592
 *  - ['validate'] (true/false): if true script will validate the local file exists
2593
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2594
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2595
 *
2596
 * @param string $id An ID to stick on the end of the filename
2597
 */
2598
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2599
{
2600
	global $settings, $context, $modSettings;
2601
2602
	$params['seed'] = (!array_key_exists('seed', $params) || (array_key_exists('seed', $params) && $params['seed'] === true)) ? (array_key_exists('browser_cache', $modSettings) ? $modSettings['browser_cache'] : '') : (is_string($params['seed']) ? ($params['seed'] = $params['seed'][0] === '?' ? $params['seed'] : '?' . $params['seed']) : '');
2603
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2604
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2605
	$params['async'] = isset($params['async']) ? $params['async'] : false;
2606
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2607
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2608
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2609
2610
	// If this is an external file, automatically set this to false.
2611
	if (!empty($params['external']))
2612
		$params['minimize'] = false;
2613
2614
	// Account for shorthand like admin.js?alp21 filenames
2615
	$has_seed = strpos($fileName, '.js?');
2616
	$id = empty($id) ? strtr(basename(str_replace('.js', '', $fileName)), '?', '_') : $id;
2617
2618
	// Is this a local file?
2619
	if (empty($params['external']))
2620
	{
2621
		// Are we validating it exists on disk?
2622
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/scripts/' . $fileName))
2623
		{
2624
			// Can't find it in this theme, how about the default?
2625
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/scripts/' . $fileName))
2626
			{
2627
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2628
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2629
			}
2630
2631
			else
2632
			{
2633
				$fileUrl = false;
2634
				$filePath = false;
2635
			}
2636
		}
2637
2638
		else
2639
		{
2640
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2641
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2642
		}
2643
	}
2644
2645
	// An external file doesn't have a filepath. Mock one for simplicity.
2646
	else
2647
	{
2648
		$fileUrl = $fileName;
2649
		$filePath = $fileName;
2650
	}
2651
2652
	// Add it to the array for use in the template
2653
	if (!empty($fileName))
2654
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2655
}
2656
2657
/**
2658
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2659
 * Cleaner and easier (for modders) than to use the function below.
2660
 *
2661
 * @param string $key The key for this variable
2662
 * @param string $value The value
2663
 * @param bool $escape Whether or not to escape the value
2664
 */
2665
function addJavaScriptVar($key, $value, $escape = false)
2666
{
2667
	global $context;
2668
2669
	if (!empty($key) && (!empty($value) || $value === '0'))
2670
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2671
}
2672
2673
/**
2674
 * Add a block of inline Javascript code to be executed later
2675
 *
2676
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2677
 *   or for scripts that require help from PHP/whatever, this can be useful.
2678
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2679
 *
2680
 * @param string $javascript Some JS code
2681
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2682
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
2683
 */
2684
function addInlineJavaScript($javascript, $defer = false)
2685
{
2686
	global $context;
2687
2688
	if (empty($javascript))
2689
		return false;
2690
2691
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2692
}
2693
2694
/**
2695
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2696
 *
2697
 * @param string $template_name The name of a template file
2698
 * @param string $lang A specific language to load this file from
2699
 * @param bool $fatal Whether to die with an error if it can't be loaded
2700
 * @param bool $force_reload Whether to load the file again if it's already loaded
2701
 * @return string The language actually loaded.
2702
 */
2703
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2704
{
2705
	global $user_info, $language, $settings, $context, $modSettings;
2706
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
2707
	static $already_loaded = array();
2708
2709
	// Default to the user's language.
2710
	if ($lang == '')
2711
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2712
2713
	// Do we want the English version of language file as fallback?
2714
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2715
		loadLanguage($template_name, 'english', false);
2716
2717
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2718
		return $lang;
2719
2720
	// Make sure we have $settings - if not we're in trouble and need to find it!
2721
	if (empty($settings['default_theme_dir']))
2722
	{
2723
		require_once($sourcedir . '/ScheduledTasks.php');
2724
		loadEssentialThemeData();
2725
	}
2726
2727
	// What theme are we in?
2728
	$theme_name = basename($settings['theme_url']);
2729
	if (empty($theme_name))
2730
		$theme_name = 'unknown';
2731
2732
	// For each file open it up and write it out!
2733
	foreach (explode('+', $template_name) as $template)
2734
	{
2735
		// Obviously, the current theme is most important to check.
2736
		$attempts = array(
2737
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2738
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2739
		);
2740
2741
		// Do we have a base theme to worry about?
2742
		if (isset($settings['base_theme_dir']))
2743
		{
2744
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2745
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2746
		}
2747
2748
		// Fall back on the default theme if necessary.
2749
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2750
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2751
2752
		// Fall back on the English language if none of the preferred languages can be found.
2753
		if (!in_array('english', array($lang, $language)))
2754
		{
2755
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2756
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2757
		}
2758
2759
		// Try to find the language file.
2760
		$found = false;
2761
		foreach ($attempts as $k => $file)
2762
		{
2763
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2764
			{
2765
				// Include it!
2766
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2767
2768
				// Note that we found it.
2769
				$found = true;
2770
2771
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2772
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2773
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2774
2775
				break;
2776
			}
2777
		}
2778
2779
		// That couldn't be found!  Log the error, but *try* to continue normally.
2780
		if (!$found && $fatal)
2781
		{
2782
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2783
			break;
2784
		}
2785
2786
		// For the sake of backward compatibility
2787
		if (!empty($txt['emails']))
2788
		{
2789
			foreach ($txt['emails'] as $key => $value)
2790
			{
2791
				$txt[$key . '_subject'] = $value['subject'];
2792
				$txt[$key . '_body'] = $value['body'];
2793
			}
2794
			$txt['emails'] = array();
2795
		}
2796
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2797
		// empty in a normal install. If it isn't it means the forum is using
2798
		// something "old" (it may be the translation, it may be a mod) and this
2799
		// code (like the piece above) takes care of converting it to the new format
2800
		if (!empty($birthdayEmails))
2801
		{
2802
			foreach ($birthdayEmails as $key => $value)
2803
			{
2804
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2805
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2806
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2807
			}
2808
			$birthdayEmails = array();
2809
		}
2810
	}
2811
2812
	// Keep track of what we're up to soldier.
2813
	if ($db_show_debug === true)
2814
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2815
2816
	// Remember what we have loaded, and in which language.
2817
	$already_loaded[$template_name] = $lang;
2818
2819
	// Return the language actually loaded.
2820
	return $lang;
2821
}
2822
2823
/**
2824
 * Get all parent boards (requires first parent as parameter)
2825
 * It finds all the parents of id_parent, and that board itself.
2826
 * Additionally, it detects the moderators of said boards.
2827
 *
2828
 * @param int $id_parent The ID of the parent board
2829
 * @return array An array of information about the boards found.
2830
 */
2831
function getBoardParents($id_parent)
2832
{
2833
	global $scripturl, $smcFunc;
2834
2835
	// First check if we have this cached already.
2836
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
0 ignored issues
show
introduced by
The condition $boards = cache_get_data...d_parent, 480) === null is always false.
Loading history...
2837
	{
2838
		$boards = array();
2839
		$original_parent = $id_parent;
2840
2841
		// Loop while the parent is non-zero.
2842
		while ($id_parent != 0)
2843
		{
2844
			$result = $smcFunc['db_query']('', '
2845
				SELECT
2846
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2847
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2848
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2849
				FROM {db_prefix}boards AS b
2850
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2851
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2852
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2853
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2854
				WHERE b.id_board = {int:board_parent}',
2855
				array(
2856
					'board_parent' => $id_parent,
2857
				)
2858
			);
2859
			// In the EXTREMELY unlikely event this happens, give an error message.
2860
			if ($smcFunc['db_num_rows']($result) == 0)
2861
				fatal_lang_error('parent_not_found', 'critical');
2862
			while ($row = $smcFunc['db_fetch_assoc']($result))
2863
			{
2864
				if (!isset($boards[$row['id_board']]))
2865
				{
2866
					$id_parent = $row['id_parent'];
2867
					$boards[$row['id_board']] = array(
2868
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2869
						'name' => $row['name'],
2870
						'level' => $row['child_level'],
2871
						'groups' => explode(',', $row['member_groups']),
2872
						'deny_groups' => explode(',', $row['deny_member_groups']),
2873
						'moderators' => array(),
2874
						'moderator_groups' => array()
2875
					);
2876
				}
2877
				// If a moderator exists for this board, add that moderator for all children too.
2878
				if (!empty($row['id_moderator']))
2879
					foreach ($boards as $id => $dummy)
2880
					{
2881
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2882
							'id' => $row['id_moderator'],
2883
							'name' => $row['real_name'],
2884
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2885
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2886
						);
2887
					}
2888
2889
				// If a moderator group exists for this board, add that moderator group for all children too
2890
				if (!empty($row['id_moderator_group']))
2891
					foreach ($boards as $id => $dummy)
2892
					{
2893
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
2894
							'id' => $row['id_moderator_group'],
2895
							'name' => $row['group_name'],
2896
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
2897
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
2898
						);
2899
					}
2900
			}
2901
			$smcFunc['db_free_result']($result);
2902
		}
2903
2904
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
2905
	}
2906
2907
	return $boards;
2908
}
2909
2910
/**
2911
 * Attempt to reload our known languages.
2912
 * It will try to choose only utf8 or non-utf8 languages.
2913
 *
2914
 * @param bool $use_cache Whether or not to use the cache
2915
 * @return array An array of information about available languages
2916
 */
2917
function getLanguages($use_cache = true)
2918
{
2919
	global $context, $smcFunc, $settings, $modSettings;
2920
2921
	// Either we don't use the cache, or its expired.
2922
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null)
2923
	{
2924
		// If we don't have our ucwords function defined yet, let's load the settings data.
2925
		if (empty($smcFunc['ucwords']))
2926
			reloadSettings();
2927
2928
		// If we don't have our theme information yet, let's get it.
2929
		if (empty($settings['default_theme_dir']))
2930
			loadTheme(0, false);
2931
2932
		// Default language directories to try.
2933
		$language_directories = array(
2934
			$settings['default_theme_dir'] . '/languages',
2935
		);
2936
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
2937
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
2938
2939
		// We possibly have a base theme directory.
2940
		if (!empty($settings['base_theme_dir']))
2941
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2942
2943
		// Remove any duplicates.
2944
		$language_directories = array_unique($language_directories);
2945
2946
		// Get a list of languages.
2947
		$langList = !empty($modSettings['langList']) ? $smcFunc['json_decode']($modSettings['langList'], true) : array();
2948
		$langList = is_array($langList) ? $langList : false;
2949
2950
		$catchLang = array();
2951
2952
		foreach ($language_directories as $language_dir)
2953
		{
2954
			// Can't look in here... doesn't exist!
2955
			if (!file_exists($language_dir))
2956
				continue;
2957
2958
			$dir = dir($language_dir);
2959
			while ($entry = $dir->read())
2960
			{
2961
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
2962
				if (!preg_match('~^index\.(.+[^-utf8])\.php$~', $entry, $matches))
2963
					continue;
2964
2965
				if (!empty($langList) && !empty($langList[$matches[1]]))
2966
					$langName = $langList[$matches[1]];
2967
2968
				else
2969
				{
2970
					$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
2971
2972
					// Get the line we need.
2973
					$fp = @fopen($language_dir . '/' . $entry);
0 ignored issues
show
Bug introduced by
The call to fopen() has too few arguments starting with mode. ( Ignorable by Annotation )

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

2973
					$fp = @/** @scrutinizer ignore-call */ fopen($language_dir . '/' . $entry);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
2974
2975
					// Yay!
2976
					if ($fp)
2977
					{
2978
						while (($line = fgets($fp)) !== false)
2979
						{
2980
							preg_match('~\$txt\[\'native_name\'\] = \'(.+)\'\;~', $line, $matchNative);
2981
2982
							// Set the language's name.
2983
							if (!empty($matchNative) && !empty($matchNative[1]))
2984
							{
2985
								$langName = un_htmlspecialchars($matchNative[1]);
2986
								break;
2987
							}
2988
						}
2989
2990
						fclose($fp);
2991
					}
2992
2993
					// Catch the language name.
2994
					$catchLang[$matches[1]] = $langName;
2995
				}
2996
2997
				// Build this language entry.
2998
				$context['languages'][$matches[1]] = array(
2999
					'name' => $langName,
3000
					'selected' => false,
3001
					'filename' => $matches[1],
3002
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
3003
				);
3004
			}
3005
			$dir->close();
3006
		}
3007
3008
		// Do we need to store the lang list?
3009
		if (empty($langList))
3010
			updateSettings(array('langList' => $smcFunc['json_encode']($catchLang)));
3011
3012
		// Let's cash in on this deal.
3013
		if (!empty($modSettings['cache_enable']))
3014
			cache_put_data('known_languages', $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
3015
	}
3016
3017
	return $context['languages'];
3018
}
3019
3020
/**
3021
 * Replace all vulgar words with respective proper words. (substring or whole words..)
3022
 * What this function does:
3023
 *  - it censors the passed string.
3024
 *  - if the theme setting allow_no_censored is on, and the theme option
3025
 *	show_no_censored is enabled, does not censor, unless force is also set.
3026
 *  - it caches the list of censored words to reduce parsing.
3027
 *
3028
 * @param string &$text The text to censor
3029
 * @param bool $force Whether to censor the text regardless of settings
3030
 * @return string The censored text
3031
 */
3032
function censorText(&$text, $force = false)
3033
{
3034
	global $modSettings, $options, $txt;
3035
	static $censor_vulgar = null, $censor_proper;
3036
3037
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
3038
		return $text;
3039
3040
	// If they haven't yet been loaded, load them.
3041
	if ($censor_vulgar == null)
3042
	{
3043
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
3044
		$censor_proper = explode("\n", $modSettings['censor_proper']);
3045
3046
		// Quote them for use in regular expressions.
3047
		if (!empty($modSettings['censorWholeWord']))
3048
		{
3049
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3050
3051
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3052
			{
3053
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3054
3055
				// Use the faster \b if we can, or something more complex if we can't
3056
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3057
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3058
3059
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3060
			}
3061
		}
3062
	}
3063
3064
	// Censoring isn't so very complicated :P.
3065
	if (empty($modSettings['censorWholeWord']))
3066
	{
3067
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3068
		$text = $func($censor_vulgar, $censor_proper, $text);
3069
	}
3070
	else
3071
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3072
3073
	return $text;
3074
}
3075
3076
/**
3077
 * Load the template/language file using require
3078
 * 	- loads the template or language file specified by filename.
3079
 * 	- uses eval unless disableTemplateEval is enabled.
3080
 * 	- outputs a parse error if the file did not exist or contained errors.
3081
 * 	- attempts to detect the error and line, and show detailed information.
3082
 *
3083
 * @param string $filename The name of the file to include
3084
 * @param bool $once If true only includes the file once (like include_once)
3085
 */
3086
function template_include($filename, $once = false)
3087
{
3088
	global $context, $txt, $scripturl, $modSettings;
3089
	global $boardurl, $boarddir;
3090
	global $maintenance, $mtitle, $mmessage;
3091
	static $templates = array();
3092
3093
	// We want to be able to figure out any errors...
3094
	@ini_set('track_errors', '1');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ini_set(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

3094
	/** @scrutinizer ignore-unhandled */ @ini_set('track_errors', '1');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3095
3096
	// Don't include the file more than once, if $once is true.
3097
	if ($once && in_array($filename, $templates))
3098
		return;
3099
	// Add this file to the include list, whether $once is true or not.
3100
	else
3101
		$templates[] = $filename;
3102
3103
3104
	$file_found = file_exists($filename);
3105
3106
	if ($once && $file_found)
3107
		require_once($filename);
3108
	elseif ($file_found)
3109
		require($filename);
3110
3111
	if ($file_found !== true)
3112
	{
3113
		ob_end_clean();
3114
		if (!empty($modSettings['enableCompressedOutput']))
3115
			@ob_start('ob_gzhandler');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition for ob_start(). This can introduce security issues, and is generally not recommended. ( Ignorable by Annotation )

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

3115
			/** @scrutinizer ignore-unhandled */ @ob_start('ob_gzhandler');

If you suppress an error, we recommend checking for the error condition explicitly:

// For example instead of
@mkdir($dir);

// Better use
if (@mkdir($dir) === false) {
    throw new \RuntimeException('The directory '.$dir.' could not be created.');
}
Loading history...
3116
		else
3117
			ob_start();
3118
3119
		if (isset($_GET['debug']))
3120
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3121
3122
		// Don't cache error pages!!
3123
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3124
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3125
		header('cache-control: no-cache');
3126
3127
		if (!isset($txt['template_parse_error']))
3128
		{
3129
			$txt['template_parse_error'] = 'Template Parse Error!';
3130
			$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>.';
3131
			$txt['template_parse_error_details'] = 'There was a problem loading the <pre><strong>%1$s</strong></pre> template or language file.  Please check the syntax and try again - remember, single quotes (<pre>\'</pre>) often have to be escaped with a slash (<pre>\\</pre>).  To see more specific error information from PHP, try <a href="' . $boardurl . '%1$s" class="extern">accessing the file directly</a>.<br><br>You may want to try to <a href="javascript:location.reload();">refresh this page</a> or <a href="' . $scripturl . '?theme=1">use the default theme</a>.';
3132
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3133
		}
3134
3135
		// First, let's get the doctype and language information out of the way.
3136
		echo '<!DOCTYPE html>
3137
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3138
	<head>';
3139
		if (isset($context['character_set']))
3140
			echo '
3141
		<meta charset="', $context['character_set'], '">';
3142
3143
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3144
			echo '
3145
		<title>', $mtitle, '</title>
3146
	</head>
3147
	<body>
3148
		<h3>', $mtitle, '</h3>
3149
		', $mmessage, '
3150
	</body>
3151
</html>';
3152
		elseif (!allowedTo('admin_forum'))
3153
			echo '
3154
		<title>', $txt['template_parse_error'], '</title>
3155
	</head>
3156
	<body>
3157
		<h3>', $txt['template_parse_error'], '</h3>
3158
		', $txt['template_parse_error_message'], '
3159
	</body>
3160
</html>';
3161
		else
3162
		{
3163
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3164
			$error_array = error_get_last();
3165
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3166
				$error = $error_array['message'];
3167
			if (empty($error))
3168
				$error = $txt['template_parse_errmsg'];
3169
3170
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3171
3172
			echo '
3173
		<title>', $txt['template_parse_error'], '</title>
3174
	</head>
3175
	<body>
3176
		<h3>', $txt['template_parse_error'], '</h3>
3177
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3178
3179
			if (!empty($error))
3180
				echo '
3181
		<hr>
3182
3183
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3184
3185
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3186
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3187
			{
3188
				$data = file($filename);
3189
				$data2 = highlight_php_code(implode('', $data));
0 ignored issues
show
Bug introduced by
It seems like $data can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

3189
				$data2 = highlight_php_code(implode('', /** @scrutinizer ignore-type */ $data));
Loading history...
3190
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3191
3192
				// Fix the PHP code stuff...
3193
				if (!isBrowser('gecko'))
3194
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3195
				else
3196
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3197
3198
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3199
				$j = -1;
3200
				foreach ($data as $line)
3201
				{
3202
					$j++;
3203
3204
					if (substr_count($line, '<br>') == 0)
3205
						continue;
3206
3207
					$n = substr_count($line, '<br>');
3208
					for ($i = 0; $i < $n; $i++)
3209
					{
3210
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3211
						unset($data2[$j + $i + 1]);
3212
					}
3213
					$j += $n;
3214
				}
3215
				$data2 = array_values($data2);
3216
				array_unshift($data2, '');
3217
3218
				echo '
3219
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3220
3221
				// Figure out what the color coding was before...
3222
				$line = max($match[1] - 9, 1);
3223
				$last_line = '';
3224
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3225
					if (strpos($data2[$line2], '<') !== false)
3226
					{
3227
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3228
							$last_line = $color_match[1];
3229
						break;
3230
					}
3231
3232
				// Show the relevant lines...
3233
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3234
				{
3235
					if ($line == $match[1])
3236
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3237
3238
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3239
					if (isset($data2[$line]) && $data2[$line] != '')
3240
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3241
3242
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3243
					{
3244
						$last_line = $color_match[1];
3245
						echo '</', substr($last_line, 1, 4), '>';
3246
					}
3247
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3248
						$last_line = '';
3249
					elseif ($last_line != '' && $data2[$line] != '')
3250
						echo '</', substr($last_line, 1, 4), '>';
3251
3252
					if ($line == $match[1])
3253
						echo '</pre></div><pre style="margin: 0;">';
3254
					else
3255
						echo "\n";
3256
				}
3257
3258
				echo '</pre></div>';
3259
			}
3260
3261
			echo '
3262
	</body>
3263
</html>';
3264
		}
3265
3266
		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...
3267
	}
3268
}
3269
3270
/**
3271
 * Initialize a database connection.
3272
 */
3273
function loadDatabase()
3274
{
3275
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
3276
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port, $db_mb4;
3277
3278
	// Figure out what type of database we are using.
3279
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3280
		$db_type = 'mysql';
3281
3282
	// Load the file for the database.
3283
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3284
3285
	$db_options = array();
3286
3287
	// Add in the port if needed
3288
	if (!empty($db_port))
3289
		$db_options['port'] = $db_port;
3290
3291
	if (!empty($db_mb4))
3292
		$db_options['db_mb4'] = $db_mb4;
3293
3294
	// 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.
3295
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3296
	{
3297
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3298
3299
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $db_connection is correct as smf_db_initiate($db_serv..., $db_prefix, $options) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

Loading history...
3300
	}
3301
3302
	// Either we aren't in SSI mode, or it failed.
3303
	if (empty($db_connection))
3304
	{
3305
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3306
3307
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $db_connection is correct as smf_db_initiate($db_serv..., $db_prefix, $options) seems to always return null.

This check looks for function or method calls that always return null and whose return value is assigned to a variable.

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

}

$a = new A();
$object = $a->getObject();

The method getObject() can return nothing but null, so it makes no sense to assign that value to a variable.

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

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