Completed
Push — release-2.1 ( 996344...c049e9 )
by Mert
16s queued 10s
created

Load.php ➔ set_avatar_data()   D

Complexity

Conditions 22
Paths 43

Size

Total Lines 82
Code Lines 42

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 22
eloc 42
nc 43
nop 1
dl 0
loc 82
rs 4.9058
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
25
	global $cache_enable, $sourcedir, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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 View Code Duplication
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
57
			$modSettings['defaultMaxTopics'] = 20;
58 View Code Duplication
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
59
			$modSettings['defaultMaxMessages'] = 15;
60 View Code Duplication
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
61
			$modSettings['defaultMaxMembers'] = 30;
62 View Code Duplication
		if (empty($modSettings['defaultMaxListItems']) || $modSettings['defaultMaxListItems'] <= 0 || $modSettings['defaultMaxListItems'] > 999)
63
			$modSettings['defaultMaxListItems'] = 15;
64
65
		// We excpiclity 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 View Code Duplication
			elseif ($ord < 224)
105
			{
106
				$new_string .= $string[$i] . $string[$i + 1];
107
				$i += 2;
108
			}
109 View Code Duplication
			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)
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));
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);
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)
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));
179
		},
180 View Code Duplication
		'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;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
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)
219
				$words[$i] = $smcFunc['ucfirst']($words[$i]);
220
			return implode('', $words);
221
		} : 'ucwords',
222
		'json_decode' => 'smf_json_decode',
223
		'json_encode' => 'json_encode',
224
	);
225
226
	// Setting the timezone is a requirement for some functions.
227
	if (isset($modSettings['default_timezone']) && in_array($modSettings['default_timezone'], timezone_identifiers_list()))
228
		date_default_timezone_set($modSettings['default_timezone']);
229
	else
230
	{
231
		// Get PHP's default timezone, if set
232
		$ini_tz = ini_get('date.timezone');
233
		if (!empty($ini_tz))
234
			$modSettings['default_timezone'] = $ini_tz;
235
		else
236
			$modSettings['default_timezone'] = '';
237
238
		// If date.timezone is unset, invalid, or just plain weird, make a best guess
239 View Code Duplication
		if (!in_array($modSettings['default_timezone'], timezone_identifiers_list()))
240
		{
241
			$server_offset = @mktime(0, 0, 0, 1, 1, 1970);
242
			$modSettings['default_timezone'] = timezone_name_from_abbr('', $server_offset, 0);
243
		}
244
245
		date_default_timezone_set($modSettings['default_timezone']);
246
	}
247
248
	// Check the load averages?
249
	if (!empty($modSettings['loadavg_enable']))
250
	{
251
		if (($modSettings['load_average'] = cache_get_data('loadavg', 90)) == null)
252
		{
253
			$modSettings['load_average'] = @file_get_contents('/proc/loadavg');
254
			if (!empty($modSettings['load_average']) && preg_match('~^([^ ]+?) ([^ ]+?) ([^ ]+)~', $modSettings['load_average'], $matches) != 0)
255
				$modSettings['load_average'] = (float) $matches[1];
256 View Code Duplication
			elseif (($modSettings['load_average'] = @`uptime`) != null && preg_match('~load average[s]?: (\d+\.\d+), (\d+\.\d+), (\d+\.\d+)~i', $modSettings['load_average'], $matches) != 0)
257
				$modSettings['load_average'] = (float) $matches[1];
258
			else
259
				unset($modSettings['load_average']);
260
261
			if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
262
				cache_put_data('loadavg', $modSettings['load_average'], 90);
263
		}
264
265
		if (!empty($modSettings['load_average']) || $modSettings['load_average'] === 0.0)
266
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
267
268
		if (!empty($modSettings['loadavg_forum']) && !empty($modSettings['load_average']) && $modSettings['load_average'] >= $modSettings['loadavg_forum'])
269
			display_loadavg_error();
270
	}
271
272
	// Is post moderation alive and well? Everywhere else assumes this has been defined, so let's make sure it is.
273
	$modSettings['postmod_active'] = !empty($modSettings['postmod_active']);
274
275
	// Here to justify the name of this function. :P
276
	// It should be added to the install and upgrade scripts.
277
	// But since the converters need to be updated also. This is easier.
278
	if (empty($modSettings['currentAttachmentUploadDir']))
279
	{
280
		updateSettings(array(
281
			'attachmentUploadDir' => $smcFunc['json_encode'](array(1 => $modSettings['attachmentUploadDir'])),
282
			'currentAttachmentUploadDir' => 1,
283
		));
284
	}
285
286
	// Integration is cool.
287
	if (defined('SMF_INTEGRATION_SETTINGS'))
288
	{
289
		$integration_settings = $smcFunc['json_decode'](SMF_INTEGRATION_SETTINGS, true);
290
		foreach ($integration_settings as $hook => $function)
291
			add_integration_function($hook, $function, '', false);
0 ignored issues
show
Documentation introduced by
'' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
Documentation introduced by
false is of type boolean, but the function expects a string.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
292
	}
293
294
	// Any files to pre include?
295 View Code Duplication
	if (!empty($modSettings['integrate_pre_include']))
296
	{
297
		$pre_includes = explode(',', $modSettings['integrate_pre_include']);
298
		foreach ($pre_includes as $include)
299
		{
300
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir));
301
			if (file_exists($include))
302
				require_once($include);
303
		}
304
	}
305
306
	// This determines the server... not used in many places, except for login fixing.
307
	$context['server'] = array(
308
		'is_iis' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Microsoft-IIS') !== false,
309
		'is_apache' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'Apache') !== false,
310
		'is_litespeed' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'LiteSpeed') !== false,
311
		'is_lighttpd' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'lighttpd') !== false,
312
		'is_nginx' => isset($_SERVER['SERVER_SOFTWARE']) && strpos($_SERVER['SERVER_SOFTWARE'], 'nginx') !== false,
313
		'is_cgi' => isset($_SERVER['SERVER_SOFTWARE']) && strpos(php_sapi_name(), 'cgi') !== false,
314
		'is_windows' => strpos(PHP_OS, 'WIN') === 0,
315
		'iso_case_folding' => ord(strtolower(chr(138))) === 154,
316
	);
317
	// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
318
	$context['server']['needs_login_fix'] = $context['server']['is_cgi'] && $context['server']['is_iis'];
319
320
	// Define a list of icons used across multiple places.
321
	$context['stable_icons'] = array('xx', 'thumbup', 'thumbdown', 'exclamation', 'question', 'lamp', 'smiley', 'angry', 'cheesy', 'grin', 'sad', 'wink', 'poll', 'moved', 'recycled', 'clip');
322
323
	// Define an array for custom profile fields placements.
324
	$context['cust_profile_fields_placement'] = array(
325
		'standard',
326
		'icons',
327
		'above_signature',
328
		'below_signature',
329
		'below_avatar',
330
		'above_member',
331
		'bottom_poster',
332
		'before_member',
333
		'after_member',
334
	);
335
336
	// Define an array for content-related <meta> elements (e.g. description, keywords, Open Graph) for the HTML head.
337
	$context['meta_tags'] = array();
338
339
	// Define an array of allowed HTML tags.
340
	$context['allowed_html_tags'] = array(
341
		'<img>',
342
		'<div>',
343
	);
344
345
	// These are the only valid image types for SMF, by default anyway.
346
	$context['validImageTypes'] = array(
347
		1 => 'gif',
348
		2 => 'jpeg',
349
		3 => 'png',
350
		5 => 'psd',
351
		6 => 'bmp',
352
		7 => 'tiff',
353
		8 => 'tiff',
354
		9 => 'jpeg',
355
		14 => 'iff'
356
	);
357
358
	// Define a list of allowed tags for descriptions.
359
	$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',);
360
361
	// Call pre load integration functions.
362
	call_integration_hook('integrate_pre_load');
363
}
364
365
/**
366
 * Load all the important user information.
367
 * What it does:
368
 * 	- sets up the $user_info array
369
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
370
 * 	- first checks for cookie or integration validation.
371
 * 	- uses the current session if no integration function or cookie is found.
372
 * 	- checks password length, if member is activated and the login span isn't over.
373
 * 		- if validation fails for the user, $id_member is set to 0.
374
 * 		- updates the last visit time when needed.
375
 */
376
function loadUserSettings()
377
{
378
	global $modSettings, $user_settings, $sourcedir, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
379
	global $cookiename, $user_info, $language, $context, $image_proxy_enabled, $image_proxy_url, $image_proxy_secret, $boardurl;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
380
381
	// Check first the integration, then the cookie, and last the session.
382
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
383
	{
384
		$id_member = 0;
385
		foreach ($integration_ids as $integration_id)
386
		{
387
			$integration_id = (int) $integration_id;
388
			if ($integration_id > 0)
389
			{
390
				$id_member = $integration_id;
391
				$already_verified = true;
392
				break;
393
			}
394
		}
395
	}
396
	else
397
		$id_member = 0;
398
399
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
400
	{
401
		// First try 2.1 json-format cookie
402
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
403
404
		// Legacy format (for recent 2.0 --> 2.1 upgrades)
405
		if (empty($cookie_data))
406
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
407
408
		list($id_member, $password, $login_span, $cookie_domain, $cookie_path) = array_pad((array) $cookie_data, 5, '');
409
410
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
411
412
		// Make sure the cookie is set to the correct domain and path
413
		require_once($sourcedir . '/Subs-Auth.php');
414
		if (array($cookie_domain, $cookie_path) !== url_parts(!empty($modSettings['localCookies']), !empty($modSettings['globalCookies'])))
415
			setLoginCookie((int) $login_span - time(), $id_member);
416
	}
417
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
418
	{
419
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
420
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename], true);
421
422
		if (empty($cookie_data))
423
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
424
425
		list($id_member, $password, $login_span) = array_pad((array) $cookie_data, 3, '');
426
		$id_member = !empty($id_member) && strlen($password) == 128 && (int) $login_span > time() ? (int) $id_member : 0;
427
	}
428
429
	// Only load this stuff if the user isn't a guest.
430
	if ($id_member != 0)
431
	{
432
		// Is the member data cached?
433
		if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
434
		{
435
			$request = $smcFunc['db_query']('', '
436
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
437
				FROM {db_prefix}members AS mem
438
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
439
				WHERE mem.id_member = {int:id_member}
440
				LIMIT 1',
441
				array(
442
					'id_member' => $id_member,
443
				)
444
			);
445
			$user_settings = $smcFunc['db_fetch_assoc']($request);
446
			$smcFunc['db_free_result']($request);
447
448 View Code Duplication
			if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($user_settings['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
449
				if ($image_proxy_enabled === 2 && !empty($image_proxy_url))
450
					$user_settings['avatar'] = $image_proxy_url . urlencode($user_settings['avatar']);
451
				else
452
					$user_settings['avatar'] = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($user_settings['avatar']) . '&hash=' . md5($user_settings['avatar'] . $image_proxy_secret);
453
454
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
455
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
456
		}
457
458
		// Did we find 'im?  If not, junk it.
459
		if (!empty($user_settings))
460
		{
461
			// As much as the password should be right, we can assume the integration set things up.
462
			if (!empty($already_verified) && $already_verified === true)
463
				$check = true;
464
			// SHA-512 hash should be 128 characters long.
465
			elseif (strlen($password) == 128)
0 ignored issues
show
Bug introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
466
				$check = hash_salt($user_settings['passwd'], $user_settings['password_salt']) == $password;
467
			else
468
				$check = false;
469
470
			// Wrong password or not activated - either way, you're going nowhere.
471
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
472
		}
473
		else
474
			$id_member = 0;
475
476
		// If we no longer have the member maybe they're being all hackey, stop brute force!
477
		if (!$id_member)
478
		{
479
			require_once($sourcedir . '/LogInOut.php');
480
			validatePasswordFlood(
481
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
482
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
483
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
484
				$id_member != 0
485
			);
486
		}
487
		// Validate for Two Factor Authentication
488
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
489
		{
490
			$tfacookie = $cookiename . '_tfa';
491
			$tfasecret = null;
492
493
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
494
495
			if (empty($verified) || !in_array(true, $verified))
496
			{
497
				if (!empty($_COOKIE[$tfacookie]))
498
				{
499
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
500
501
					list ($tfamember, $tfasecret) = array_pad((array) $tfa_data, 2, '');
502
503
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
504
						$tfasecret = null;
505
				}
506
507
				// They didn't finish logging in before coming here? Then they're no one to us.
508
				if (empty($tfasecret) || hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']) != $tfasecret)
509
				{
510
					setLoginCookie(-3600, $id_member);
511
					$id_member = 0;
512
					$user_settings = array();
513
				}
514
			}
515
		}
516
		// When authenticating their two factor code, make sure to reset their ID for security
517
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
518
		{
519
			$id_member = 0;
520
			$context['tfa_member'] = $user_settings;
521
			$user_settings = array();
522
		}
523
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
524
		elseif (!empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']))
525
		{
526
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
527
			{
528
				//Build an array of ALL user membergroups.
529
				$full_groups = array($user_settings['id_group']);
530
				if (!empty($user_settings['additional_groups']))
531
				{
532
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
533
					$full_groups = array_unique($full_groups); //duplicates, maybe?
534
				}
535
536
				//Find out if any group requires 2FA
537
				$request = $smcFunc['db_query']('', '
538
					SELECT COUNT(id_group) AS total
539
					FROM {db_prefix}membergroups
540
					WHERE tfa_required = {int:tfa_required}
541
						AND id_group IN ({array_int:full_groups})',
542
					array(
543
						'tfa_required' => 1,
544
						'full_groups' => $full_groups,
545
					)
546
				);
547
				$row = $smcFunc['db_fetch_assoc']($request);
548
				$smcFunc['db_free_result']($request);
549
			}
550
			else
551
				$row['total'] = 1; //simplifies logics in the next "if"
0 ignored issues
show
Coding Style Comprehensibility introduced by
$row was never initialized. Although not strictly required by PHP, it is generally a good practice to add $row = array(); before regardless.

Adding an explicit array definition is generally preferable to implicit array definition as it guarantees a stable state of the code.

Let’s take a look at an example:

foreach ($collection as $item) {
    $myArray['foo'] = $item->getFoo();

    if ($item->hasBar()) {
        $myArray['bar'] = $item->getBar();
    }

    // do something with $myArray
}

As you can see in this example, the array $myArray is initialized the first time when the foreach loop is entered. You can also see that the value of the bar key is only written conditionally; thus, its value might result from a previous iteration.

This might or might not be intended. To make your intention clear, your code more readible and to avoid accidental bugs, we recommend to add an explicit initialization $myArray = array() either outside or inside the foreach loop.

Loading history...
552
553
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
554
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
555
556
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
557
				redirectexit('action=profile;area=tfasetup;forced');
558
		}
559
	}
560
561
	// Found 'im, let's set up the variables.
562
	if ($id_member != 0)
563
	{
564
		// Let's not update the last visit time in these cases...
565
		// 1. SSI doesn't count as visiting the forum.
566
		// 2. RSS feeds and XMLHTTP requests don't count either.
567
		// 3. If it was set within this session, no need to set it again.
568
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
569
		// 5. We're still logging in or authenticating
570
		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))
571
		{
572
			// @todo can this be cached?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
573
			// Do a quick query to make sure this isn't a mistake.
574
			$result = $smcFunc['db_query']('', '
575
				SELECT poster_time
576
				FROM {db_prefix}messages
577
				WHERE id_msg = {int:id_msg}
578
				LIMIT 1',
579
				array(
580
					'id_msg' => $user_settings['id_msg_last_visit'],
581
				)
582
			);
583
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
584
			$smcFunc['db_free_result']($result);
585
586
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
587
588
			// If it was *at least* five hours ago...
589
			if ($visitTime < time() - 5 * 3600)
590
			{
591
				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']));
592
				$user_settings['last_login'] = time();
593
594
				if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
595
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
596
597
				if (!empty($modSettings['cache_enable']))
598
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
599
			}
600
		}
601
		elseif (empty($_SESSION['id_msg_last_visit']))
602
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
603
604
		$username = $user_settings['member_name'];
605
606
		if (empty($user_settings['additional_groups']))
607
			$user_info = array(
608
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
609
			);
610
		else
611
			$user_info = array(
612
				'groups' => array_merge(
613
					array($user_settings['id_group'], $user_settings['id_post_group']),
614
					explode(',', $user_settings['additional_groups'])
615
				)
616
			);
617
618
		// Because history has proven that it is possible for groups to go bad - clean up in case.
619
		foreach ($user_info['groups'] as $k => $v)
620
			$user_info['groups'][$k] = (int) $v;
621
622
		// This is a logged in user, so definitely not a spider.
623
		$user_info['possibly_robot'] = false;
624
625
		// Figure out the new time offset.
626
		if (!empty($user_settings['timezone']))
627
		{
628
			// Get the offsets from UTC for the server, then for the user.
629
			$tz_system = new DateTimeZone(@date_default_timezone_get());
630
			$tz_user = new DateTimeZone($user_settings['timezone']);
631
			$time_system = new DateTime('now', $tz_system);
632
			$time_user = new DateTime('now', $tz_user);
633
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
634
		}
635
		else
636
		{
637
			// !!! Compatibility.
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
638
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
639
		}
640
	}
641
	// If the user is a guest, initialize all the critical user settings.
642
	else
643
	{
644
		// This is what a guest's variables should be.
645
		$username = '';
646
		$user_info = array('groups' => array(-1));
647
		$user_settings = array();
648
649
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
650
			$_COOKIE[$cookiename] = '';
651
652
		// Expire the 2FA cookie
653
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
654
		{
655
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
656
657
			list (,, $exp) = array_pad((array) $tfa_data, 3, 0);
658
659
			if (time() > $exp)
660
			{
661
				$_COOKIE[$cookiename . '_tfa'] = '';
662
				setTFACookie(-3600, 0, '');
663
			}
664
		}
665
666
		// Create a login token if it doesn't exist yet.
667
		if (!isset($_SESSION['token']['post-login']))
668
			createToken('login');
669
		else
670
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
671
672
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
673
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
674
		{
675
			require_once($sourcedir . '/ManageSearchEngines.php');
676
			$user_info['possibly_robot'] = SpiderCheck();
677
		}
678
		elseif (!empty($modSettings['spider_mode']))
679
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
680
		// If we haven't turned on proper spider hunts then have a guess!
681
		else
682
		{
683
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
684
			$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;
685
		}
686
687
		// We don't know the offset...
688
		$user_info['time_offset'] = 0;
689
	}
690
691
	// Set up the $user_info array.
692
	$user_info += array(
693
		'id' => $id_member,
694
		'username' => $username,
695
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
696
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
697
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
698
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
699
		'is_guest' => $id_member == 0,
700
		'is_admin' => in_array(1, $user_info['groups']),
701
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
702
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
703
		'ip' => $_SERVER['REMOTE_ADDR'],
704
		'ip2' => $_SERVER['BAN_CHECK_IP'],
705
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
706
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
707
		'avatar' => array(
708
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
709
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
710
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
711
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
712
		),
713
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
714
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
715
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
716
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
717
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
718
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
719
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
720
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
721
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
722
		'permissions' => array(),
723
	);
724
	$user_info['groups'] = array_unique($user_info['groups']);
725
726
	// 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.
727
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
728
		unset($user_info['ignoreboards'][$tmp]);
729
730
	// Allow the user to change their language.
731
	if (!empty($modSettings['userLanguage']))
732
	{
733
		$languages = getLanguages();
734
735
		// Is it valid?
736
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
737
		{
738
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
739
740
			// Make it permanent for members.
741
			if (!empty($user_info['id']))
742
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
743
			else
744
				$_SESSION['language'] = $user_info['language'];
745
		}
746
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
747
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
748
	}
749
750
	$temp = build_query_board($user_info['id']);
751
	$user_info['query_see_board'] = $temp['query_see_board'];
752
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
753
754
	call_integration_hook('integrate_user_info');
755
}
756
757
/**
758
 * Check for moderators and see if they have access to the board.
759
 * What it does:
760
 * - sets up the $board_info array for current board information.
761
 * - if cache is enabled, the $board_info array is stored in cache.
762
 * - redirects to appropriate post if only message id is requested.
763
 * - is only used when inside a topic or board.
764
 * - determines the local moderators for the board.
765
 * - adds group id 3 if the user is a local moderator for the board they are in.
766
 * - prevents access if user is not in proper group nor a local moderator of the board.
767
 */
768
function loadBoard()
769
{
770
	global $txt, $scripturl, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
771
	global $board_info, $board, $topic, $user_info, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
772
773
	// Assume they are not a moderator.
774
	$user_info['is_mod'] = false;
775
	$context['user']['is_mod'] = &$user_info['is_mod'];
776
777
	// Start the linktree off empty..
778
	$context['linktree'] = array();
779
780
	// Have they by chance specified a message id but nothing else?
781
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
782
	{
783
		// Make sure the message id is really an int.
784
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
785
786
		// Looking through the message table can be slow, so try using the cache first.
787
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
788
		{
789
			$request = $smcFunc['db_query']('', '
790
				SELECT id_topic
791
				FROM {db_prefix}messages
792
				WHERE id_msg = {int:id_msg}
793
				LIMIT 1',
794
				array(
795
					'id_msg' => $_REQUEST['msg'],
796
				)
797
			);
798
799
			// So did it find anything?
800
			if ($smcFunc['db_num_rows']($request))
801
			{
802
				list ($topic) = $smcFunc['db_fetch_row']($request);
803
				$smcFunc['db_free_result']($request);
804
				// Save save save.
805
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
806
			}
807
		}
808
809
		// Remember redirection is the key to avoiding fallout from your bosses.
810
		if (!empty($topic))
811
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
812
		else
813
		{
814
			loadPermissions();
815
			loadTheme();
816
			fatal_lang_error('topic_gone', false);
817
		}
818
	}
819
820
	// Load this board only if it is specified.
821
	if (empty($board) && empty($topic))
822
	{
823
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
824
		return;
825
	}
826
827
	if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
828
	{
829
		// @todo SLOW?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
830
		if (!empty($topic))
831
			$temp = cache_get_data('topic_board-' . $topic, 120);
832
		else
833
			$temp = cache_get_data('board-' . $board, 120);
834
835
		if (!empty($temp))
836
		{
837
			$board_info = $temp;
838
			$board = $board_info['id'];
839
		}
840
	}
841
842
	if (empty($temp))
843
	{
844
		$request = $smcFunc['db_query']('load_board_info', '
845
			SELECT
846
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
847
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
848
				COALESCE(mem.id_member, 0) AS id_moderator,
849
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
850
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
851
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
852
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
853
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
854
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
855
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
856
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
857
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
858
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
859
			WHERE b.id_board = {raw:board_link}',
860
			array(
861
				'current_topic' => $topic,
862
				'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
863
			)
864
		);
865
		// If there aren't any, skip.
866
		if ($smcFunc['db_num_rows']($request) > 0)
867
		{
868
			$row = $smcFunc['db_fetch_assoc']($request);
869
870
			// Set the current board.
871
			if (!empty($row['id_board']))
872
				$board = $row['id_board'];
873
874
			// Basic operating information. (globals... :/)
875
			$board_info = array(
876
				'id' => $board,
877
				'moderators' => array(),
878
				'moderator_groups' => array(),
879
				'cat' => array(
880
					'id' => $row['id_cat'],
881
					'name' => $row['cname']
882
				),
883
				'name' => $row['bname'],
884
				'description' => $row['description'],
885
				'num_topics' => $row['num_topics'],
886
				'unapproved_topics' => $row['unapproved_topics'],
887
				'unapproved_posts' => $row['unapproved_posts'],
888
				'unapproved_user_topics' => 0,
889
				'parent_boards' => getBoardParents($row['id_parent']),
890
				'parent' => $row['id_parent'],
891
				'child_level' => $row['child_level'],
892
				'theme' => $row['id_theme'],
893
				'override_theme' => !empty($row['override_theme']),
894
				'profile' => $row['id_profile'],
895
				'redirect' => $row['redirect'],
896
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
897
				'posts_count' => empty($row['count_posts']),
898
				'cur_topic_approved' => empty($topic) || $row['approved'],
899
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
900
			);
901
902
			// Load the membergroups allowed, and check permissions.
903
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
904
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
905
906
			do
907
			{
908 View Code Duplication
				if (!empty($row['id_moderator']))
909
					$board_info['moderators'][$row['id_moderator']] = array(
910
						'id' => $row['id_moderator'],
911
						'name' => $row['real_name'],
912
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
913
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
914
					);
915
916 View Code Duplication
				if (!empty($row['id_moderator_group']))
917
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
918
						'id' => $row['id_moderator_group'],
919
						'name' => $row['group_name'],
920
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
921
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
922
					);
923
			}
924
			while ($row = $smcFunc['db_fetch_assoc']($request));
925
926
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
927
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
928
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
929
			{
930
				// Free the previous result
931
				$smcFunc['db_free_result']($request);
932
933
				// @todo why is this using id_topic?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
934
				// @todo Can this get cached?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
935
				$request = $smcFunc['db_query']('', '
936
					SELECT COUNT(id_topic)
937
					FROM {db_prefix}topics
938
					WHERE id_member_started={int:id_member}
939
						AND approved = {int:unapproved}
940
						AND id_board = {int:board}',
941
					array(
942
						'id_member' => $user_info['id'],
943
						'unapproved' => 0,
944
						'board' => $board,
945
					)
946
				);
947
948
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
949
			}
950
951
			if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
952
			{
953
				// @todo SLOW?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
954
				if (!empty($topic))
955
					cache_put_data('topic_board-' . $topic, $board_info, 120);
956
				cache_put_data('board-' . $board, $board_info, 120);
957
			}
958
		}
959
		else
960
		{
961
			// Otherwise the topic is invalid, there are no moderators, etc.
962
			$board_info = array(
963
				'moderators' => array(),
964
				'moderator_groups' => array(),
965
				'error' => 'exist'
966
			);
967
			$topic = null;
968
			$board = 0;
969
		}
970
		$smcFunc['db_free_result']($request);
971
	}
972
973
	if (!empty($topic))
974
		$_GET['board'] = (int) $board;
975
976
	if (!empty($board))
977
	{
978
		// Get this into an array of keys for array_intersect
979
		$moderator_groups = array_keys($board_info['moderator_groups']);
980
981
		// Now check if the user is a moderator.
982
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
983
984 View Code Duplication
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
985
			$board_info['error'] = 'access';
986 View Code Duplication
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
987
			$board_info['error'] = 'access';
988
989
		// Build up the linktree.
990
		$context['linktree'] = array_merge(
991
			$context['linktree'],
992
			array(array(
993
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
994
				'name' => $board_info['cat']['name']
995
			)),
996
			array_reverse($board_info['parent_boards']),
997
			array(array(
998
				'url' => $scripturl . '?board=' . $board . '.0',
999
				'name' => $board_info['name']
1000
			))
1001
		);
1002
	}
1003
1004
	// Set the template contextual information.
1005
	$context['user']['is_mod'] = &$user_info['is_mod'];
1006
	$context['current_topic'] = $topic;
1007
	$context['current_board'] = $board;
1008
1009
	// No posting in redirection boards!
1010
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1011
		$board_info['error'] == 'post_in_redirect';
1012
1013
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1014
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1015
	{
1016
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1017
		loadPermissions();
1018
		loadTheme();
1019
1020
		$_GET['board'] = '';
1021
		$_GET['topic'] = '';
1022
1023
		// The linktree should not give the game away mate!
1024
		$context['linktree'] = array(
1025
			array(
1026
				'url' => $scripturl,
1027
				'name' => $context['forum_name_html_safe']
1028
			)
1029
		);
1030
1031
		// If it's a prefetching agent or we're requesting an attachment.
1032
		if ((isset($_SERVER['HTTP_X_MOZ']) && $_SERVER['HTTP_X_MOZ'] == 'prefetch') || (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach'))
0 ignored issues
show
Unused Code Bug introduced by
The strict comparison === seems to always evaluate to false as the types of $_REQUEST['action'] (integer) and 'dlattach' (string) can never be identical. Maybe you want to use a loose comparison == instead?
Loading history...
1033
		{
1034
			ob_end_clean();
1035
			header('HTTP/1.1 403 Forbidden');
1036
			die;
1037
		}
1038
		elseif ($board_info['error'] == 'post_in_redirect')
1039
		{
1040
			// Slightly different error message here...
1041
			fatal_lang_error('cannot_post_redirect', false);
1042
		}
1043
		elseif ($user_info['is_guest'])
1044
		{
1045
			loadLanguage('Errors');
1046
			is_not_guest($txt['topic_gone']);
1047
		}
1048
		else
1049
			fatal_lang_error('topic_gone', false);
1050
	}
1051
1052
	if ($user_info['is_mod'])
1053
		$user_info['groups'][] = 3;
1054
}
1055
1056
/**
1057
 * Load this user's permissions.
1058
 */
1059
function loadPermissions()
1060
{
1061
	global $user_info, $board, $board_info, $modSettings, $smcFunc, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1062
1063
	if ($user_info['is_admin'])
1064
	{
1065
		banPermissions();
1066
		return;
1067
	}
1068
1069
	if (!empty($modSettings['cache_enable']))
1070
	{
1071
		$cache_groups = $user_info['groups'];
1072
		asort($cache_groups);
1073
		$cache_groups = implode(',', $cache_groups);
1074
		// If it's a spider then cache it different.
1075
		if ($user_info['possibly_robot'])
1076
			$cache_groups .= '-spider';
1077
1078
		if ($modSettings['cache_enable'] >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1079
		{
1080
			list ($user_info['permissions']) = $temp;
1081
			banPermissions();
1082
1083
			return;
1084
		}
1085
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1086
			list ($user_info['permissions'], $removals) = $temp;
1087
	}
1088
1089
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1090
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1091
1092
	if (empty($user_info['permissions']))
1093
	{
1094
		// Get the general permissions.
1095
		$request = $smcFunc['db_query']('', '
1096
			SELECT permission, add_deny
1097
			FROM {db_prefix}permissions
1098
			WHERE id_group IN ({array_int:member_groups})
1099
				' . $spider_restrict,
1100
			array(
1101
				'member_groups' => $user_info['groups'],
1102
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1103
			)
1104
		);
1105
		$removals = array();
1106 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1107
		{
1108
			if (empty($row['add_deny']))
1109
				$removals[] = $row['permission'];
1110
			else
1111
				$user_info['permissions'][] = $row['permission'];
1112
		}
1113
		$smcFunc['db_free_result']($request);
1114
1115
		if (isset($cache_groups))
1116
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1117
	}
1118
1119
	// Get the board permissions.
1120
	if (!empty($board))
1121
	{
1122
		// Make sure the board (if any) has been loaded by loadBoard().
1123
		if (!isset($board_info['profile']))
1124
			fatal_lang_error('no_board');
1125
1126
		$request = $smcFunc['db_query']('', '
1127
			SELECT permission, add_deny
1128
			FROM {db_prefix}board_permissions
1129
			WHERE (id_group IN ({array_int:member_groups})
1130
				' . $spider_restrict . ')
1131
				AND id_profile = {int:id_profile}',
1132
			array(
1133
				'member_groups' => $user_info['groups'],
1134
				'id_profile' => $board_info['profile'],
1135
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1136
			)
1137
		);
1138 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1139
		{
1140
			if (empty($row['add_deny']))
1141
				$removals[] = $row['permission'];
0 ignored issues
show
Bug introduced by
The variable $removals does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
1142
			else
1143
				$user_info['permissions'][] = $row['permission'];
1144
		}
1145
		$smcFunc['db_free_result']($request);
1146
	}
1147
1148
	// Remove all the permissions they shouldn't have ;).
1149
	if (!empty($modSettings['permission_enable_deny']))
1150
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
1151
1152
	if (isset($cache_groups) && !empty($board) && $modSettings['cache_enable'] >= 2)
1153
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1154
1155
	// Banned?  Watch, don't touch..
1156
	banPermissions();
1157
1158
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1159
	if (!$user_info['is_guest'])
1160
	{
1161
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1162
		{
1163
			require_once($sourcedir . '/Subs-Auth.php');
1164
			rebuildModCache();
1165
		}
1166
		else
1167
			$user_info['mod_cache'] = $_SESSION['mc'];
1168
1169
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1170
		// For example this drastically simplifies certain changes to the profile area.
1171
		$user_info['permissions'][] = 'is_not_guest';
1172
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1173
		$user_info['permissions'][] = 'profile_view_own';
1174
		if (in_array('profile_view', $user_info['permissions']))
1175
			$user_info['permissions'][] = 'profile_view_any';
1176
	}
1177
}
1178
1179
/**
1180
 * Loads an array of users' data by ID or member_name.
1181
 *
1182
 * @param array|string $users An array of users by id or name or a single username/id
1183
 * @param bool $is_name Whether $users contains names
1184
 * @param string $set What kind of data to load (normal, profile, minimal)
1185
 * @return array The ids of the members loaded
1186
 */
1187
function loadMemberData($users, $is_name = false, $set = 'normal')
1188
{
1189
	global $user_profile, $modSettings, $board_info, $smcFunc, $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1190
	global $image_proxy_enabled, $image_proxy_url, $image_proxy_secret, $boardurl, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1191
1192
	// Can't just look for no users :P.
1193
	if (empty($users))
1194
		return array();
1195
1196
	// Pass the set value
1197
	$context['loadMemberContext_set'] = $set;
1198
1199
	// Make sure it's an array.
1200
	$users = !is_array($users) ? array($users) : array_unique($users);
1201
	$loaded_ids = array();
1202
1203
	if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1204
	{
1205
		$users = array_values($users);
1206
		for ($i = 0, $n = count($users); $i < $n; $i++)
1207
		{
1208
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1209
			if ($data == null)
1210
				continue;
1211
1212
			$loaded_ids[] = $data['id_member'];
1213
			$user_profile[$data['id_member']] = $data;
1214
			unset($users[$i]);
1215
		}
1216
	}
1217
1218
	// Used by default
1219
	$select_columns = '
1220
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1221
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1222
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1223
			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,
1224
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1225
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1226
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1227
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1228
	$select_tables = '
1229
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1230
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1231
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1232
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1233
1234
	// We add or replace according the the set
1235
	switch ($set)
1236
	{
1237
		case 'normal':
1238
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1239
			break;
1240
		case 'profile':
1241
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1242
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1243
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1244
			break;
1245
		case 'minimal':
1246
			$select_columns = '
1247
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1248
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1249
			$select_tables = '';
1250
			break;
1251
		default:
1252
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1253
	}
1254
1255
	// Allow mods to easily add to the selected member data
1256
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1257
1258
	if (!empty($users))
1259
	{
1260
		// Load the member's data.
1261
		$request = $smcFunc['db_query']('', '
1262
			SELECT' . $select_columns . '
1263
			FROM {db_prefix}members AS mem' . $select_tables . '
1264
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1265
			array(
1266
				'blank_string' => '',
1267
				'users' => $users,
1268
			)
1269
		);
1270
		$new_loaded_ids = array();
1271
		while ($row = $smcFunc['db_fetch_assoc']($request))
1272
		{
1273
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1274
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1275
1276
			// Take care of proxying avatar if required, do this here for maximum reach
1277 View Code Duplication
			if ($image_proxy_enabled && !empty($row['avatar']) && stripos($row['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
1278
				if ($image_proxy_enabled === 2 && !empty($image_proxy_url))
1279
					$row['avatar'] = $image_proxy_url . urlencode($row['avatar']);
1280
				else
1281
					$row['avatar'] = $boardurl . '/proxy.php?request=' . urlencode($row['avatar']) . '&hash=' . md5($row['avatar'] . $image_proxy_secret);
1282
1283
			// Keep track of the member's normal member group
1284
			$row['primary_group'] = $row['member_group'];
1285
1286
			if (isset($row['member_ip']))
1287
				$row['member_ip'] = inet_dtop($row['member_ip']);
1288
			if (isset($row['member_ip2']))
1289
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1290
			$new_loaded_ids[] = $row['id_member'];
1291
			$loaded_ids[] = $row['id_member'];
1292
			$row['options'] = array();
1293
			$user_profile[$row['id_member']] = $row;
1294
		}
1295
		$smcFunc['db_free_result']($request);
1296
	}
1297
1298 View Code Duplication
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1299
	{
1300
		$request = $smcFunc['db_query']('', '
1301
			SELECT id_member, variable, value
1302
			FROM {db_prefix}themes
1303
			WHERE id_member IN ({array_int:loaded_ids})',
1304
			array(
1305
				'loaded_ids' => $new_loaded_ids,
1306
			)
1307
		);
1308
		while ($row = $smcFunc['db_fetch_assoc']($request))
1309
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1310
		$smcFunc['db_free_result']($request);
1311
	}
1312
1313
	$additional_mods = array();
1314
1315
	// Are any of these users in groups assigned to moderate this board?
1316
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1317
	{
1318
		foreach ($loaded_ids as $a_member)
1319
		{
1320
			if (!empty($user_profile[$a_member]['additional_groups']))
1321
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1322
			else
1323
				$groups = array($user_profile[$a_member]['id_group']);
1324
1325
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1326
1327
			if (!empty($temp))
1328
			{
1329
				$additional_mods[] = $a_member;
1330
			}
1331
		}
1332
	}
1333
1334
	if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1335
	{
1336
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1337
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1338
	}
1339
1340
	// Are we loading any moderators?  If so, fix their group data...
1341
	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)
1342
	{
1343 View Code Duplication
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1344
		{
1345
			$request = $smcFunc['db_query']('', '
1346
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1347
				FROM {db_prefix}membergroups
1348
				WHERE id_group = {int:moderator_group}
1349
				LIMIT 1',
1350
				array(
1351
					'moderator_group' => 3,
1352
				)
1353
			);
1354
			$row = $smcFunc['db_fetch_assoc']($request);
1355
			$smcFunc['db_free_result']($request);
1356
1357
			cache_put_data('moderator_group_info', $row, 480);
1358
		}
1359
1360
		foreach ($temp_mods as $id)
1361
		{
1362
			// By popular demand, don't show admins or global moderators as moderators.
1363
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1364
				$user_profile[$id]['member_group'] = $row['member_group'];
1365
1366
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1367
			if (!empty($row['icons']))
1368
				$user_profile[$id]['icons'] = $row['icons'];
1369
			if (!empty($row['member_group_color']))
1370
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1371
		}
1372
	}
1373
1374
	return $loaded_ids;
1375
}
1376
1377
/**
1378
 * Loads the user's basic values... meant for template/theme usage.
1379
 *
1380
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1381
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1382
 * @return boolean Whether or not the data was loaded successfully
1383
 */
1384
function loadMemberContext($user, $display_custom_fields = false)
1385
{
1386
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1387
	global $context, $modSettings, $settings, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1388
	static $dataLoaded = array();
1389
	static $loadedLanguages = array();
1390
1391
	// If this person's data is already loaded, skip it.
1392
	if (isset($dataLoaded[$user]))
1393
		return true;
1394
1395
	// We can't load guests or members not loaded by loadMemberData()!
1396
	if ($user == 0)
1397
		return false;
1398
	if (!isset($user_profile[$user]))
1399
	{
1400
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1401
		return false;
1402
	}
1403
1404
	// Well, it's loaded now anyhow.
1405
	$dataLoaded[$user] = true;
1406
	$profile = $user_profile[$user];
1407
1408
	// Censor everything.
1409
	censorText($profile['signature']);
1410
	censorText($profile['personal_text']);
1411
1412
	// Set things up to be used before hand.
1413
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1414
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1415
1416
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1417
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1418
	// Setup the buddy status here (One whole in_array call saved :P)
1419
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1420
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1421
1422
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1423
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1424
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1425
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1426
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1427
	else //not set, bye bye
1428
		$group_icon_url = '';
1429
1430
	// These minimal values are always loaded
1431
	$memberContext[$user] = array(
1432
		'username' => $profile['member_name'],
1433
		'name' => $profile['real_name'],
1434
		'id' => $profile['id_member'],
1435
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1436
		'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>',
1437
		'email' => $profile['email_address'],
1438
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1439
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1440
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1441
	);
1442
1443
	// If the set isn't minimal then load the monstrous array.
1444
	if ($context['loadMemberContext_set'] != 'minimal')
1445
	{
1446
		// Go the extra mile and load the user's native language name.
1447
		if (empty($loadedLanguages))
1448
			$loadedLanguages = getLanguages();
1449
1450
		$memberContext[$user] += array(
1451
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1452
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1453
			'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>',
1454
			'is_buddy' => $profile['buddy'],
1455
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1456
			'buddies' => $buddy_list,
1457
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1458
			'blurb' => $profile['personal_text'],
1459
			'website' => array(
1460
				'title' => $profile['website_title'],
1461
				'url' => $profile['website_url'],
1462
			),
1463
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1464
			'signature' => $profile['signature'],
1465
			'real_posts' => $profile['posts'],
1466
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1467
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1468
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(0, $profile['last_login']),
0 ignored issues
show
Documentation introduced by
0 is of type integer, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
1469
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1470
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1471
			'online' => array(
1472
				'is_online' => $profile['is_online'],
1473
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1474
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1475
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1476
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1477
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1478
			),
1479
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1480
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1481
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1482
			'options' => $profile['options'],
1483
			'is_guest' => false,
1484
			'primary_group' => $profile['primary_group'],
1485
			'group' => $profile['member_group'],
1486
			'group_color' => $profile['member_group_color'],
1487
			'group_id' => $profile['id_group'],
1488
			'post_group' => $profile['post_group'],
1489
			'post_group_color' => $profile['post_group_color'],
1490
			'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]),
1491
			'warning' => $profile['warning'],
1492
			'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' : (''))),
1493
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1494
			'custom_fields' => array(),
1495
		);
1496
	}
1497
1498
	// If the set isn't minimal then load their avatar as well.
1499
	if ($context['loadMemberContext_set'] != 'minimal')
1500
	{
1501
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1502
		{
1503
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1504
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1505
			else
1506
				$image = get_gravatar_url($profile['email_address']);
1507
		}
1508
		else
1509
		{
1510
			// So it's stored in the member table?
1511
			if (!empty($profile['avatar']))
1512
			{
1513
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1514
			}
1515
			elseif (!empty($profile['filename']))
1516
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1517
			// Right... no avatar...use the default one
1518
			else
1519
				$image = $modSettings['avatar_url'] . '/default.png';
1520
		}
1521
		if (!empty($image))
1522
			$memberContext[$user]['avatar'] = array(
1523
				'name' => $profile['avatar'],
1524
				'image' => '<img class="avatar" src="' . $image . '" alt="avatar_' . $profile['member_name'] . '">',
1525
				'href' => $image,
1526
				'url' => $image,
1527
			);
1528
	}
1529
1530
	// Are we also loading the members custom fields into context?
1531
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1532
	{
1533
		$memberContext[$user]['custom_fields'] = array();
1534
1535
		if (!isset($context['display_fields']))
1536
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1537
1538
		foreach ($context['display_fields'] as $custom)
1539
		{
1540
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1541
				continue;
1542
1543
			$value = $profile['options'][$custom['col_name']];
1544
1545
			$fieldOptions = array();
1546
			$currentKey = 0;
1547
1548
			// Create a key => value array for multiple options fields
1549 View Code Duplication
			if (!empty($custom['options']))
1550
				foreach ($custom['options'] as $k => $v)
1551
				{
1552
					$fieldOptions[] = $v;
1553
					if (empty($currentKey))
1554
						$currentKey = $v == $value ? $k : 0;
1555
				}
1556
1557
			// BBC?
1558
			if ($custom['bbc'])
1559
				$value = parse_bbc($value);
1560
1561
			// ... or checkbox?
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1562
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1563
				$value = $value ? $txt['yes'] : $txt['no'];
1564
1565
			// Enclosing the user input within some other text?
1566
			if (!empty($custom['enclose']))
1567
				$value = strtr($custom['enclose'], array(
1568
					'{SCRIPTURL}' => $scripturl,
1569
					'{IMAGES_URL}' => $settings['images_url'],
1570
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1571
					'{INPUT}' => $value,
1572
					'{KEY}' => $currentKey,
1573
				));
1574
1575
			$memberContext[$user]['custom_fields'][] = array(
1576
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1577
				'col_name' => $custom['col_name'],
1578
				'value' => un_htmlspecialchars($value),
1579
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1580
			);
1581
		}
1582
	}
1583
1584
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1585
	return true;
1586
}
1587
1588
/**
1589
 * Loads the user's custom profile fields
1590
 *
1591
 * @param integer|array $users A single user ID or an array of user IDs
1592
 * @param string|array $params Either a string or an array of strings with profile field names
1593
 * @return array|boolean An array of data about the fields and their values or false if nothing was loaded
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|array.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
1594
 */
1595
function loadMemberCustomFields($users, $params)
1596
{
1597
	global $smcFunc, $txt, $scripturl, $settings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1598
1599
	// Do not waste my time...
1600
	if (empty($users) || empty($params))
1601
		return false;
1602
1603
	// Make sure it's an array.
1604
	$users = !is_array($users) ? array($users) : array_unique($users);
1605
	$params = !is_array($params) ? array($params) : array_unique($params);
1606
	$return = array();
1607
1608
	$request = $smcFunc['db_query']('', '
1609
		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,
1610
		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
1611
		FROM {db_prefix}themes AS t
1612
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1613
		WHERE id_member IN ({array_int:loaded_ids})
1614
			AND variable IN ({array_string:params})
1615
		ORDER BY field_order',
1616
		array(
1617
			'loaded_ids' => $users,
1618
			'params' => $params,
1619
		)
1620
	);
1621
1622
	while ($row = $smcFunc['db_fetch_assoc']($request))
1623
	{
1624
		$fieldOptions = array();
1625
		$currentKey = 0;
1626
1627
		// Create a key => value array for multiple options fields
1628 View Code Duplication
		if (!empty($row['field_options']))
1629
			foreach (explode(',', $row['field_options']) as $k => $v)
1630
			{
1631
				$fieldOptions[] = $v;
1632
				if (empty($currentKey))
1633
					$currentKey = $v == $row['value'] ? $k : 0;
1634
			}
1635
1636
		// BBC?
1637
		if (!empty($row['bbc']))
1638
			$row['value'] = parse_bbc($row['value']);
1639
1640
		// ... or checkbox?
0 ignored issues
show
Unused Code Comprehensibility introduced by
43% of this comment could be valid code. Did you maybe forget this after debugging?

Sometimes obsolete code just ends up commented out instead of removed. In this case it is better to remove the code once you have checked you do not need it.

The code might also have been commented out for debugging purposes. In this case it is vital that someone uncomments it again or your project may behave in very unexpected ways in production.

This check looks for comments that seem to be mostly valid code and reports them.

Loading history...
1641
		elseif (isset($row['type']) && $row['type'] == 'check')
1642
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1643
1644
		// Enclosing the user input within some other text?
1645
		if (!empty($row['enclose']))
1646
			$row['value'] = strtr($row['enclose'], array(
1647
				'{SCRIPTURL}' => $scripturl,
1648
				'{IMAGES_URL}' => $settings['images_url'],
1649
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1650
				'{INPUT}' => un_htmlspecialchars($row['value']),
1651
				'{KEY}' => $currentKey,
1652
			));
1653
1654
		// Send a simple array if there is just 1 param
1655
		if (count($params) == 1)
1656
			$return[$row['id_member']] = $row;
1657
1658
		// More than 1? knock yourself out...
1659
		else
1660
		{
1661
			if (!isset($return[$row['id_member']]))
1662
				$return[$row['id_member']] = array();
1663
1664
			$return[$row['id_member']][$row['variable']] = $row;
1665
		}
1666
	}
1667
1668
	$smcFunc['db_free_result']($request);
1669
1670
	return !empty($return) ? $return : false;
1671
}
1672
1673
/**
1674
 * Loads information about what browser the user is viewing with and places it in $context
1675
 *  - uses the class from {@link Class-BrowserDetect.php}
1676
 */
1677
function detectBrowser()
1678
{
1679
	// Load the current user's browser of choice
1680
	$detector = new browser_detector;
1681
	$detector->detectBrowser();
1682
}
1683
1684
/**
1685
 * Are we using this browser?
1686
 *
1687
 * Wrapper function for detectBrowser
1688
 * @param string $browser The browser we are checking for.
1689
 * @return bool Whether or not the current browser is what we're looking for
1690
*/
1691
function isBrowser($browser)
1692
{
1693
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1694
1695
	// Don't know any browser!
1696
	if (empty($context['browser']))
1697
		detectBrowser();
1698
1699
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1700
}
1701
1702
/**
1703
 * Load a theme, by ID.
1704
 *
1705
 * @param int $id_theme The ID of the theme to load
1706
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1707
 */
1708
function loadTheme($id_theme = 0, $initialize = true)
1709
{
1710
	global $user_info, $user_settings, $board_info, $boarddir, $maintenance;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1711
	global $txt, $boardurl, $scripturl, $mbname, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1712
	global $context, $settings, $options, $sourcedir, $ssi_theme, $smcFunc, $language, $board, $image_proxy_enabled;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
1713
1714
	// The theme was specified by parameter.
1715
	if (!empty($id_theme))
1716
		$id_theme = (int) $id_theme;
1717
	// The theme was specified by REQUEST.
1718 View Code Duplication
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1719
	{
1720
		$id_theme = (int) $_REQUEST['theme'];
1721
		$_SESSION['id_theme'] = $id_theme;
1722
	}
1723
	// The theme was specified by REQUEST... previously.
1724 View Code Duplication
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1725
		$id_theme = (int) $_SESSION['id_theme'];
1726
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1727
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']))
1728
		$id_theme = $user_info['theme'];
1729
	// The theme was specified by the board.
1730
	elseif (!empty($board_info['theme']))
1731
		$id_theme = $board_info['theme'];
1732
	// The theme is the forum's default.
1733
	else
1734
		$id_theme = $modSettings['theme_guests'];
1735
1736
	// We already load the basic stuff?
1737
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme )
1738
	{
1739
		// Verify the id_theme... no foul play.
1740
		// Always allow the board specific theme, if they are overriding.
1741
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1742
			$id_theme = $board_info['theme'];
1743
		// If they have specified a particular theme to use with SSI allow it to be used.
1744
		elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1745
			$id_theme = (int) $id_theme;
1746
		elseif (!empty($modSettings['enableThemes']) && !allowedTo('admin_forum'))
1747
		{
1748
			$themes = explode(',', $modSettings['enableThemes']);
1749
			if (!in_array($id_theme, $themes))
1750
				$id_theme = $modSettings['theme_guests'];
1751
			else
1752
				$id_theme = (int) $id_theme;
1753
		}
1754
		else
1755
			$id_theme = (int) $id_theme;
1756
1757
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1758
1759
		// Disable image proxy if we don't have SSL enabled
1760
		if (empty($modSettings['force_ssl']))
1761
			$image_proxy_enabled = false;
1762
1763
		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'])
1764
		{
1765
			$themeData = $temp;
1766
			$flag = true;
1767
		}
1768
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1769
			$themeData = $temp + array($member => array());
1770
		else
1771
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1772
1773
		if (empty($flag))
1774
		{
1775
			// Load variables from the current or default theme, global or this user's.
1776
			$result = $smcFunc['db_query']('', '
1777
				SELECT variable, value, id_member, id_theme
1778
				FROM {db_prefix}themes
1779
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1780
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1781
				array(
1782
					'id_theme' => $id_theme,
1783
					'id_member' => $member,
1784
				)
1785
			);
1786
			// Pick between $settings and $options depending on whose data it is.
1787
			while ($row = $smcFunc['db_fetch_assoc']($result))
1788
			{
1789
				// There are just things we shouldn't be able to change as members.
1790
				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')))
1791
					continue;
1792
1793
				// If this is the theme_dir of the default theme, store it.
1794
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1795
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1796
1797
				// If this isn't set yet, is a theme option, or is not the default theme..
1798
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1799
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1800
			}
1801
			$smcFunc['db_free_result']($result);
1802
1803
			if (!empty($themeData[-1]))
1804
				foreach ($themeData[-1] as $k => $v)
1805
				{
1806
					if (!isset($themeData[$member][$k]))
1807
						$themeData[$member][$k] = $v;
1808
				}
1809
1810
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1811
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1812
			// Only if we didn't already load that part of the cache...
1813
			elseif (!isset($temp))
1814
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1815
		}
1816
1817
		$settings = $themeData[0];
1818
		$options = $themeData[$member];
1819
1820
		$settings['theme_id'] = $id_theme;
1821
1822
		$settings['actual_theme_url'] = $settings['theme_url'];
1823
		$settings['actual_images_url'] = $settings['images_url'];
1824
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1825
1826
		$settings['template_dirs'] = array();
1827
		// This theme first.
1828
		$settings['template_dirs'][] = $settings['theme_dir'];
1829
1830
		// Based on theme (if there is one).
1831
		if (!empty($settings['base_theme_dir']))
1832
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1833
1834
		// Lastly the default theme.
1835 View Code Duplication
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1836
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1837
	}
1838
1839
1840
	if (!$initialize)
1841
		return;
1842
1843
	// Check to see if we're forcing SSL
1844
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
1845
		!httpsOn() && SMF != 'SSI')
1846
	{
1847
		if (isset($_GET['sslRedirect']))
1848
		{
1849
			loadLanguage('Errors');
1850
			fatal_lang_error($txt['login_ssl_required']);
1851
		}
1852
1853
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
1854
	}
1855
1856
	// Check to see if they're accessing it from the wrong place.
1857
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1858
	{
1859
		$detected_url = httpsOn() ? 'https://' : 'http://';
1860
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1861
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1862
		if ($temp != '/')
1863
			$detected_url .= $temp;
1864
	}
1865
	if (isset($detected_url) && $detected_url != $boardurl)
1866
	{
1867
		// Try #1 - check if it's in a list of alias addresses.
1868
		if (!empty($modSettings['forum_alias_urls']))
1869
		{
1870
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1871
1872
			foreach ($aliases as $alias)
1873
			{
1874
				// Rip off all the boring parts, spaces, etc.
1875
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1876
					$do_fix = true;
1877
			}
1878
		}
1879
1880
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1881
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
1882
		{
1883
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1884
			if (empty($_GET))
1885
				redirectexit('wwwRedirect');
1886
			else
1887
			{
1888
				$k = key($_GET);
1889
				$v = current($_GET);
1890
1891
				if ($k != 'wwwRedirect')
1892
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1893
			}
1894
		}
1895
1896
		// #3 is just a check for SSL...
1897
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1898
			$do_fix = true;
1899
1900
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1901
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1902
		{
1903
			// Caching is good ;).
1904
			$oldurl = $boardurl;
1905
1906
			// Fix $boardurl and $scripturl.
1907
			$boardurl = $detected_url;
1908
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1909
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1910
1911
			// Fix the theme urls...
1912
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1913
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1914
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1915
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1916
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1917
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1918
1919
			// And just a few mod settings :).
1920
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1921
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1922
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
1923
1924
			// Clean up after loadBoard().
1925
			if (isset($board_info['moderators']))
1926
			{
1927
				foreach ($board_info['moderators'] as $k => $dummy)
1928
				{
1929
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1930
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1931
				}
1932
			}
1933
			foreach ($context['linktree'] as $k => $dummy)
1934
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1935
		}
1936
	}
1937
	// Set up the contextual user array.
1938
	if (!empty($user_info))
1939
	{
1940
		$context['user'] = array(
1941
			'id' => $user_info['id'],
1942
			'is_logged' => !$user_info['is_guest'],
1943
			'is_guest' => &$user_info['is_guest'],
1944
			'is_admin' => &$user_info['is_admin'],
1945
			'is_mod' => &$user_info['is_mod'],
1946
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1947
			'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'])))),
1948
			'name' => $user_info['username'],
1949
			'language' => $user_info['language'],
1950
			'email' => $user_info['email'],
1951
			'ignoreusers' => $user_info['ignoreusers'],
1952
		);
1953
		if (!$context['user']['is_guest'])
1954
			$context['user']['name'] = $user_info['name'];
1955 View Code Duplication
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1956
			$context['user']['name'] = $txt['guest_title'];
1957
1958
		// Determine the current smiley set.
1959
		$user_info['smiley_set'] = (!in_array($user_info['smiley_set'], explode(',', $modSettings['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'];
1960
		$context['user']['smiley_set'] = $user_info['smiley_set'];
1961
	}
1962
	else
1963
	{
1964
		// What to do when there is no $user_info (e.g., an error very early in the login process)
1965
		$context['user'] = array(
1966
			'id' => -1,
1967
			'is_logged' => false,
1968
			'is_guest' => true,
1969
			'is_mod' => false,
1970
			'can_mod' => false,
1971
			'name' => $txt['guest_title'],
1972
			'language' => $language,
1973
			'email' => '',
1974
			'ignoreusers' => array(),
1975
		);
1976
		// Note we should stuff $user_info with some guest values also...
1977
		$user_info = array(
1978
			'id' => 0,
1979
			'is_guest' => true,
1980
			'is_admin' => false,
1981
			'is_mod' => false,
1982
			'username' => $txt['guest_title'],
1983
			'language' => $language,
1984
			'email' => '',
1985
			'smiley_set' => '',
1986
			'permissions' => array(),
1987
			'groups' => array(),
1988
			'ignoreusers' => array(),
1989
			'possibly_robot' => true,
1990
			'time_offset' => 0,
1991
			'time_format' => $modSettings['time_format'],
1992
		);
1993
	}
1994
1995
	// Some basic information...
1996
	if (!isset($context['html_headers']))
1997
		$context['html_headers'] = '';
1998
	if (!isset($context['javascript_files']))
1999
		$context['javascript_files'] = array();
2000
	if (!isset($context['css_files']))
2001
		$context['css_files'] = array();
2002
	if (!isset($context['css_header']))
2003
		$context['css_header'] = array();
2004
	if (!isset($context['javascript_inline']))
2005
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2006
	if (!isset($context['javascript_vars']))
2007
		$context['javascript_vars'] = array();
2008
2009
	$context['login_url'] =  $scripturl . '?action=login2';
2010
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2011
	$context['session_var'] = $_SESSION['session_var'];
2012
	$context['session_id'] = $_SESSION['session_value'];
2013
	$context['forum_name'] = $mbname;
2014
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2015
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2016
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2017
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2018
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2019
	if (isset($modSettings['load_average']))
2020
		$context['load_average'] = $modSettings['load_average'];
2021
2022
	// Detect the browser. This is separated out because it's also used in attachment downloads
2023
	detectBrowser();
2024
2025
	// Set the top level linktree up.
2026
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2027
	if (empty($context['linktree']))
2028
		$context['linktree'] = array();
2029
	array_unshift($context['linktree'], array(
2030
		'url' => $scripturl,
2031
		'name' => $context['forum_name_html_safe']
2032
	));
2033
2034
	// This allows sticking some HTML on the page output - useful for controls.
2035
	$context['insert_after_template'] = '';
2036
2037
	if (!isset($txt))
2038
		$txt = array();
2039
2040
	$simpleActions = array(
2041
		'findmember',
2042
		'helpadmin',
2043
		'printpage',
2044
		'spellcheck',
2045
	);
2046
2047
	// Parent action => array of areas
2048
	$simpleAreas = array(
2049
		'profile' => array('popup', 'alerts_popup',),
2050
	);
2051
2052
	// Parent action => array of subactions
2053
	$simpleSubActions = array(
2054
		'pm' => array('popup',),
2055
		'signup' => array('usernamecheck'),
2056
	);
2057
2058
	// Extra params like ;preview ;js, etc.
2059
	$extraParams = array(
2060
		'preview',
2061
		'splitjs',
2062
	);
2063
2064
	// Actions that specifically uses XML output.
2065
	$xmlActions = array(
2066
		'quotefast',
2067
		'jsmodify',
2068
		'xmlhttp',
2069
		'post2',
2070
		'suggest',
2071
		'stats',
2072
		'notifytopic',
2073
		'notifyboard',
2074
	);
2075
2076
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2077
2078
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2079
	(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2080
	(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2081
2082
	// See if theres any extra param to check.
2083
	$requiresXML = false;
2084
	foreach ($extraParams as $key => $extra)
2085
		if (isset($_REQUEST[$extra]))
2086
			$requiresXML = true;
2087
2088
	// Output is fully XML, so no need for the index template.
2089
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2090
	{
2091
		loadLanguage('index+Modifications');
2092
		loadTemplate('Xml');
2093
		$context['template_layers'] = array();
2094
	}
2095
2096
	// These actions don't require the index template at all.
2097
	elseif (!empty($context['simple_action']))
2098
	{
2099
		loadLanguage('index+Modifications');
2100
		$context['template_layers'] = array();
2101
	}
2102
2103
	else
2104
	{
2105
		// Custom templates to load, or just default?
2106
		if (isset($settings['theme_templates']))
2107
			$templates = explode(',', $settings['theme_templates']);
2108
		else
2109
			$templates = array('index');
2110
2111
		// Load each template...
2112
		foreach ($templates as $template)
2113
			loadTemplate($template);
2114
2115
		// ...and attempt to load their associated language files.
2116
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2117
		loadLanguage($required_files, '', false);
2118
2119
		// Custom template layers?
2120
		if (isset($settings['theme_layers']))
2121
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2122
		else
2123
			$context['template_layers'] = array('html', 'body');
2124
	}
2125
2126
	// Initialize the theme.
2127
	loadSubTemplate('init', 'ignore');
0 ignored issues
show
Documentation introduced by
'ignore' is of type string, but the function expects a boolean.

It seems like the type of the argument is not accepted by the function/method which you are calling.

In some cases, in particular if PHP’s automatic type-juggling kicks in this might be fine. In other cases, however this might be a bug.

We suggest to add an explicit type cast like in the following example:

function acceptsInteger($int) { }

$x = '123'; // string "123"

// Instead of
acceptsInteger($x);

// we recommend to use
acceptsInteger((integer) $x);
Loading history...
2128
2129
	// Allow overriding the board wide time/number formats.
2130
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2131
		$user_info['time_format'] = $txt['time_format'];
2132
2133
	// Set the character set from the template.
2134
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2135
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2136
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2137
2138
	// Guests may still need a name.
2139 View Code Duplication
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2140
		$context['user']['name'] = $txt['guest_title'];
2141
2142
	// Any theme-related strings that need to be loaded?
2143
	if (!empty($settings['require_theme_strings']))
2144
		loadLanguage('ThemeStrings', '', false);
2145
2146
	// Make a special URL for the language.
2147
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2148
2149
	// And of course, let's load the default CSS file.
2150
	loadCSSFile('index.css', array('minimize' => true, 'order_pos' => 1), 'smf_index');
2151
2152
	// Here is my luvly Responsive CSS
2153
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true, 'order_pos' => 9000), 'smf_responsive');
2154
2155
	if ($context['right_to_left'])
2156
		loadCSSFile('rtl.css', array('order_pos' => 200), 'smf_rtl');
2157
2158
	// We allow theme variants, because we're cool.
2159
	$context['theme_variant'] = '';
2160
	$context['theme_variant_url'] = '';
2161
	if (!empty($settings['theme_variants']))
2162
	{
2163
		// Overriding - for previews and that ilk.
2164
		if (!empty($_REQUEST['variant']))
2165
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2166
		// User selection?
2167
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2168
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2169
		// If not a user variant, select the default.
2170
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2171
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2172
2173
		// Do this to keep things easier in the templates.
2174
		$context['theme_variant'] = '_' . $context['theme_variant'];
2175
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2176
2177
		if (!empty($context['theme_variant']))
2178
		{
2179
			loadCSSFile('index' . $context['theme_variant'] . '.css', array('order_pos' => 300), 'smf_index' . $context['theme_variant']);
2180
			if ($context['right_to_left'])
2181
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array('order_pos' => 400), 'smf_rtl' . $context['theme_variant']);
2182
		}
2183
	}
2184
2185
	// Let's be compatible with old themes!
2186
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2187
		$context['template_layers'] = array('main');
2188
2189
	$context['tabindex'] = 1;
2190
2191
	// Compatibility.
2192
	if (!isset($settings['theme_version']))
2193
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2194
2195
	// Default JS variables for use in every theme
2196
	$context['javascript_vars'] = array(
2197
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2198
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2199
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2200
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2201
		'smf_scripturl' => '"' . $scripturl . '"',
2202
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2203
		'smf_charset' => '"' . $context['character_set'] . '"',
2204
		'smf_session_id' => '"' . $context['session_id'] . '"',
2205
		'smf_session_var' => '"' . $context['session_var'] . '"',
2206
		'smf_member_id' => $context['user']['id'],
2207
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2208
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2209
	);
2210
2211
	// Add the JQuery library to the list of files to load.
2212
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2213
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2214
2215
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2216
		loadJavaScriptFile('jquery-3.2.1.min.js', array('seed' => false), 'smf_jquery');
2217
2218
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2219
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2220
2221
	// Auto loading? template_javascript() will take care of the local half of this.
2222
	else
2223
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2224
2225
	// Queue our JQuery plugins!
2226
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2227
	if (!$user_info['is_guest'])
2228
	{
2229
		loadJavaScriptFile('jquery.custom-scrollbar.js', array(), 'smf_jquery_scrollbar');
2230
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2231
	}
2232
2233
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2234
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2235
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2236
2237
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2238
	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())
2239
	{
2240
		if (isBrowser('possibly_robot'))
2241
		{
2242
			// @todo Maybe move this somewhere better?!
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
2243
			require_once($sourcedir . '/ScheduledTasks.php');
2244
2245
			// What to do, what to do?!
2246
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2247
				AutoTask();
2248
			else
2249
				ReduceMailQueue();
2250
		}
2251
		else
2252
		{
2253
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2254
			$ts = $type == 'mailq' ? $modSettings['mail_next_send'] : $modSettings['next_task_time'];
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $ts. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
2255
2256
			addInlineJavaScript('
2257
		function smfAutoTask()
2258
		{
2259
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2260
		}
2261
		window.setTimeout("smfAutoTask();", 1);');
2262
		}
2263
	}
2264
2265
	// And we should probably trigger the cron too.
2266
	if (empty($modSettings['cron_is_real_cron']))
2267
	{
2268
		$ts = time();
2269
		$ts -= $ts % 15;
2270
		addInlineJavaScript('
2271
	function triggerCron()
2272
	{
2273
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2274
	}
2275
	window.setTimeout(triggerCron, 1);', true);
2276
	}
2277
2278
	// Filter out the restricted boards from the linktree
2279
	if (!$user_info['is_admin'] && !empty($board))
2280
	{
2281
		foreach ($context['linktree'] as $k => $element)
2282
		{
2283
			if (!empty($element['groups']) &&
2284
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2285
				(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2286
			{
2287
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2288
				$context['linktree'][$k]['extra_before'] = '<i>';
2289
				$context['linktree'][$k]['extra_after'] = '</i>';
2290
				unset($context['linktree'][$k]['url']);
2291
			}
2292
		}
2293
	}
2294
2295
	// Any files to include at this point?
2296 View Code Duplication
	if (!empty($modSettings['integrate_theme_include']))
2297
	{
2298
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2299
		foreach ($theme_includes as $include)
2300
		{
2301
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2302
			if (file_exists($include))
2303
				require_once($include);
2304
		}
2305
	}
2306
2307
	// Call load theme integration functions.
2308
	call_integration_hook('integrate_load_theme');
2309
2310
	// We are ready to go.
2311
	$context['theme_loaded'] = true;
2312
}
2313
2314
/**
2315
 * Load a template - if the theme doesn't include it, use the default.
2316
 * What this function does:
2317
 *  - loads a template file with the name template_name from the current, default, or base theme.
2318
 *  - detects a wrong default theme directory and tries to work around it.
2319
 *
2320
 * @uses the template_include() function to include the file.
2321
 * @param string $template_name The name of the template to load
2322
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2323
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2324
 * @return boolean Whether or not the template was loaded
0 ignored issues
show
Documentation introduced by
Should the return type not be boolean|null?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2325
 */
2326
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2327
{
2328
	global $context, $settings, $txt, $scripturl, $boarddir, $db_show_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2329
2330
	// Do any style sheets first, cause we're easy with those.
2331
	if (!empty($style_sheets))
2332
	{
2333
		if (!is_array($style_sheets))
2334
			$style_sheets = array($style_sheets);
2335
2336
		foreach ($style_sheets as $sheet)
2337
			loadCSSFile($sheet . '.css', array(), $sheet);
2338
	}
2339
2340
	// No template to load?
2341
	if ($template_name === false)
2342
		return true;
2343
2344
	$loaded = false;
2345
	foreach ($settings['template_dirs'] as $template_dir)
2346
	{
2347
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2348
		{
2349
			$loaded = true;
2350
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2351
			break;
2352
		}
2353
	}
2354
2355
	if ($loaded)
2356
	{
2357
		if ($db_show_debug === true)
2358
			$context['debug']['templates'][] = $template_name . ' (' . basename($template_dir) . ')';
0 ignored issues
show
Bug introduced by
The variable $template_dir seems to be defined by a foreach iteration on line 2345. Are you sure the iterator is never empty, otherwise this variable is not defined?

It seems like you are relying on a variable being defined by an iteration:

foreach ($a as $b) {
}

// $b is defined here only if $a has elements, for example if $a is array()
// then $b would not be defined here. To avoid that, we recommend to set a
// default value for $b.


// Better
$b = 0; // or whatever default makes sense in your context
foreach ($a as $b) {
}

// $b is now guaranteed to be defined here.
Loading history...
2359
2360
		// If they have specified an initialization function for this template, go ahead and call it now.
2361
		if (function_exists('template_' . $template_name . '_init'))
2362
			call_user_func('template_' . $template_name . '_init');
2363
	}
2364
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2365
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2366
	{
2367
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2368
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2369
2370 View Code Duplication
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2371
		{
2372
			loadLanguage('Errors');
2373
			echo '
2374
<div class="alert errorbox">
2375
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2376
</div>';
2377
		}
2378
2379
		loadTemplate($template_name);
2380
	}
2381
	// Cause an error otherwise.
2382
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2383
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2384
	elseif ($fatal)
2385
		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'));
2386
	else
2387
		return false;
2388
}
2389
2390
/**
2391
 * Load a sub-template.
2392
 * What it does:
2393
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2394
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2395
 *	for debugging purposes.
2396
 *
2397
 * @todo get rid of reading $_REQUEST directly
0 ignored issues
show
Coding Style introduced by
Comment refers to a TODO task

This check looks TODO comments that have been left in the code.

``TODO``s show that something is left unfinished and should be attended to.

Loading history...
2398
 *
2399
 * @param string $sub_template_name The name of the sub-template to load
2400
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2401
 */
2402
function loadSubTemplate($sub_template_name, $fatal = false)
2403
{
2404
	global $context, $txt, $db_show_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2405
2406
	if ($db_show_debug === true)
2407
		$context['debug']['sub_templates'][] = $sub_template_name;
2408
2409
	// Figure out what the template function is named.
2410
	$theme_function = 'template_' . $sub_template_name;
2411
	if (function_exists($theme_function))
2412
		$theme_function();
2413
	elseif ($fatal === false)
2414
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2415
	elseif ($fatal !== 'ignore')
2416
		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'));
2417
2418
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2419
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2420
	{
2421
		echo '
2422
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2423
	}
2424
}
2425
2426
/**
2427
 * Add a CSS file for output later
2428
 *
2429
 * @param string $fileName The name of the file to load
2430
 * @param array $params An array of parameters
2431
 * Keys are the following:
2432
 * 	- ['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
2433
 * 	- ['default_theme'] (true/false): force use of default theme url
2434
 * 	- ['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
2435
 *  - ['validate'] (true/false): if true script will validate the local file exists
2436
 *  - ['rtl'] (string): additional file to load in RTL mode
2437
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2438
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2439
 *  - ['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
2440
 * @param string $id An ID to stick on the end of the filename for caching purposes
2441
 */
2442
function loadCSSFile($fileName, $params = array(), $id = '')
2443
{
2444
	global $settings, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2445
2446
	if (empty($context['css_files_order']))
2447
		$context['css_files_order'] = array();
2448
2449
	$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']) : '');
2450
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2451
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2452
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : true;
2453
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2454
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2455
	$params['order_pos'] = isset($params['order_pos']) ? (int) $params['order_pos'] : 3000;
2456
2457
	// If this is an external file, automatically set this to false.
2458
	if (!empty($params['external']))
2459
		$params['minimize'] = false;
2460
2461
	// Account for shorthand like admin.css?alp21 filenames
2462
	$has_seed = strpos($fileName, '.css?');
2463
	$id = empty($id) ? strtr(basename(str_replace('.css', '', $fileName)), '?', '_') : $id;
2464
2465
	// Is this a local file?
2466 View Code Duplication
	if (empty($params['external']))
2467
	{
2468
		// Are we validating the the file exists?
2469
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/css/' . $fileName))
2470
		{
2471
			// Maybe the default theme has it?
2472
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/css/' . $fileName))
2473
			{
2474
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2475
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2476
			}
2477
2478
			else
2479
			{
2480
				$fileUrl = false;
2481
				$filePath = false;
2482
			}
2483
		}
2484
2485
		else
2486
		{
2487
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2488
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2489
		}
2490
	}
2491
2492
	// An external file doesn't have a filepath. Mock one for simplicity.
2493
	else
2494
	{
2495
		$fileUrl = $fileName;
2496
		$filePath = $fileName;
2497
	}
2498
2499
	// Add it to the array for use in the template
2500
	if (!empty($fileName))
2501
	{
2502
		// find a free number/position
2503
		while (isset($context['css_files_order'][$params['order_pos']]))
2504
			$params['order_pos']++;
2505
		$context['css_files_order'][$params['order_pos']] = $id;
2506
2507
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2508
	}
2509
2510
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2511
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2512
}
2513
2514
/**
2515
 * Add a block of inline css code to be executed later
2516
 *
2517
 * - only use this if you have to, generally external css files are better, but for very small changes
2518
 *   or for scripts that require help from PHP/whatever, this can be useful.
2519
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2520
 *
2521
 * @param string $css Some css code
2522
 * @return void|bool Adds the CSS to the $context['css_header'] array or returns if no CSS is specified
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2523
 */
2524
function addInlineCss($css)
2525
{
2526
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2527
2528
	// Gotta add something...
2529
	if (empty($css))
2530
		return false;
2531
2532
	$context['css_header'][] = $css;
2533
}
2534
2535
/**
2536
 * Add a Javascript file for output later
2537
 *
2538
 * @param string $fileName The name of the file to load
2539
 * @param array $params An array of parameter info
2540
 * Keys are the following:
2541
 * 	- ['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
2542
 * 	- ['default_theme'] (true/false): force use of default theme url
2543
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2544
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2545
 *	default theme if not found in the current theme
2546
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2547
 *  - ['validate'] (true/false): if true script will validate the local file exists
2548
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2549
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2550
 *
2551
 * @param string $id An ID to stick on the end of the filename
2552
 */
2553
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2554
{
2555
	global $settings, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2556
2557
	$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']) : '');
2558
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2559
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2560
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2561
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2562
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2563
2564
	// If this is an external file, automatically set this to false.
2565
	if (!empty($params['external']))
2566
		$params['minimize'] = false;
2567
2568
	// Account for shorthand like admin.js?alp21 filenames
2569
	$has_seed = strpos($fileName, '.js?');
2570
	$id = empty($id) ? strtr(basename(str_replace('.js', '', $fileName)), '?', '_') : $id;
2571
2572
	// Is this a local file?
2573 View Code Duplication
	if (empty($params['external']))
2574
	{
2575
		// Are we validating it exists on disk?
2576
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/scripts/' . $fileName))
2577
		{
2578
			// Can't find it in this theme, how about the default?
2579
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/scripts/' . $fileName))
2580
			{
2581
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2582
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2583
			}
2584
2585
			else
2586
			{
2587
				$fileUrl = false;
2588
				$filePath = false;
2589
			}
2590
		}
2591
2592
		else
2593
		{
2594
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2595
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2596
		}
2597
	}
2598
2599
	// An external file doesn't have a filepath. Mock one for simplicity.
2600
	else
2601
	{
2602
		$fileUrl = $fileName;
2603
		$filePath = $fileName;
2604
	}
2605
2606
	// Add it to the array for use in the template
2607
	if (!empty($fileName))
2608
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2609
}
2610
2611
/**
2612
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2613
 * Cleaner and easier (for modders) than to use the function below.
2614
 *
2615
 * @param string $key The key for this variable
2616
 * @param string $value The value
2617
 * @param bool $escape Whether or not to escape the value
2618
 */
2619
function addJavaScriptVar($key, $value, $escape = false)
2620
{
2621
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2622
2623
	if (!empty($key) && (!empty($value) || $value === '0'))
2624
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2625
}
2626
2627
/**
2628
 * Add a block of inline Javascript code to be executed later
2629
 *
2630
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2631
 *   or for scripts that require help from PHP/whatever, this can be useful.
2632
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2633
 *
2634
 * @param string $javascript Some JS code
2635
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2636
 * @return void|bool Adds the code to one of the $context['javascript_inline'] arrays or returns if no JS was specified
0 ignored issues
show
Documentation introduced by
Consider making the return type a bit more specific; maybe use false|null.

This check looks for the generic type array as a return type and suggests a more specific type. This type is inferred from the actual code.

Loading history...
2637
 */
2638
function addInlineJavaScript($javascript, $defer = false)
2639
{
2640
	global $context;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2641
2642
	if (empty($javascript))
2643
		return false;
2644
2645
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2646
}
2647
2648
/**
2649
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2650
 *
2651
 * @param string $template_name The name of a template file
2652
 * @param string $lang A specific language to load this file from
2653
 * @param bool $fatal Whether to die with an error if it can't be loaded
2654
 * @param bool $force_reload Whether to load the file again if it's already loaded
2655
 * @return string The language actually loaded.
2656
 */
2657
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2658
{
2659
	global $user_info, $language, $settings, $context, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2660
	global $db_show_debug, $sourcedir, $txt, $birthdayEmails, $txtBirthdayEmails;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2661
	static $already_loaded = array();
2662
2663
	// Default to the user's language.
2664
	if ($lang == '')
2665
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2666
2667
	// Do we want the English version of language file as fallback?
2668
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2669
		loadLanguage($template_name, 'english', false);
2670
2671
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2672
		return $lang;
2673
2674
	// Make sure we have $settings - if not we're in trouble and need to find it!
2675
	if (empty($settings['default_theme_dir']))
2676
	{
2677
		require_once($sourcedir . '/ScheduledTasks.php');
2678
		loadEssentialThemeData();
2679
	}
2680
2681
	// What theme are we in?
2682
	$theme_name = basename($settings['theme_url']);
2683
	if (empty($theme_name))
2684
		$theme_name = 'unknown';
2685
2686
	// For each file open it up and write it out!
2687
	foreach (explode('+', $template_name) as $template)
2688
	{
2689
		// Obviously, the current theme is most important to check.
2690
		$attempts = array(
2691
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2692
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2693
		);
2694
2695
		// Do we have a base theme to worry about?
2696
		if (isset($settings['base_theme_dir']))
2697
		{
2698
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2699
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2700
		}
2701
2702
		// Fall back on the default theme if necessary.
2703
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2704
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2705
2706
		// Fall back on the English language if none of the preferred languages can be found.
2707
		if (!in_array('english', array($lang, $language)))
2708
		{
2709
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2710
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2711
		}
2712
2713
		// Try to find the language file.
2714
		$found = false;
2715
		foreach ($attempts as $k => $file)
2716
		{
2717
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2718
			{
2719
				// Include it!
2720
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2721
2722
				// Note that we found it.
2723
				$found = true;
2724
2725
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2726
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2727
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2728
2729
				break;
2730
			}
2731
		}
2732
2733
		// That couldn't be found!  Log the error, but *try* to continue normally.
2734
		if (!$found && $fatal)
2735
		{
2736
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2737
			break;
2738
		}
2739
2740
		// For the sake of backward compatibility
2741
		if (!empty($txt['emails']))
2742
		{
2743
			foreach ($txt['emails'] as $key => $value)
2744
			{
2745
				$txt[$key . '_subject'] = $value['subject'];
2746
				$txt[$key . '_body'] = $value['body'];
2747
			}
2748
			$txt['emails'] = array();
2749
		}
2750
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2751
		// empty in a normal install. If it isn't it means the forum is using
2752
		// something "old" (it may be the translation, it may be a mod) and this
2753
		// code (like the piece above) takes care of converting it to the new format
2754
		if (!empty($birthdayEmails))
2755
		{
2756
			foreach ($birthdayEmails as $key => $value)
2757
			{
2758
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2759
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2760
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2761
			}
2762
			$birthdayEmails = array();
2763
		}
2764
	}
2765
2766
	// Keep track of what we're up to soldier.
2767
	if ($db_show_debug === true)
2768
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2769
2770
	// Remember what we have loaded, and in which language.
2771
	$already_loaded[$template_name] = $lang;
2772
2773
	// Return the language actually loaded.
2774
	return $lang;
2775
}
2776
2777
/**
2778
 * Get all parent boards (requires first parent as parameter)
2779
 * It finds all the parents of id_parent, and that board itself.
2780
 * Additionally, it detects the moderators of said boards.
2781
 *
2782
 * @param int $id_parent The ID of the parent board
2783
 * @return array An array of information about the boards found.
0 ignored issues
show
Documentation introduced by
Should the return type not be array|string? Also, consider making the array more specific, something like array<String>, or String[].

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

If the return type contains the type array, this check recommends the use of a more specific type like String[] or array<String>.

Loading history...
2784
 */
2785
function getBoardParents($id_parent)
2786
{
2787
	global $scripturl, $smcFunc;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2788
2789
	// First check if we have this cached already.
2790
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
2791
	{
2792
		$boards = array();
2793
		$original_parent = $id_parent;
2794
2795
		// Loop while the parent is non-zero.
2796
		while ($id_parent != 0)
2797
		{
2798
			$result = $smcFunc['db_query']('', '
2799
				SELECT
2800
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2801
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2802
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2803
				FROM {db_prefix}boards AS b
2804
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2805
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2806
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2807
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2808
				WHERE b.id_board = {int:board_parent}',
2809
				array(
2810
					'board_parent' => $id_parent,
2811
				)
2812
			);
2813
			// In the EXTREMELY unlikely event this happens, give an error message.
2814
			if ($smcFunc['db_num_rows']($result) == 0)
2815
				fatal_lang_error('parent_not_found', 'critical');
2816
			while ($row = $smcFunc['db_fetch_assoc']($result))
2817
			{
2818
				if (!isset($boards[$row['id_board']]))
2819
				{
2820
					$id_parent = $row['id_parent'];
2821
					$boards[$row['id_board']] = array(
2822
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2823
						'name' => $row['name'],
2824
						'level' => $row['child_level'],
2825
						'groups' => explode(',', $row['member_groups']),
2826
						'deny_groups' => explode(',', $row['deny_member_groups']),
2827
						'moderators' => array(),
2828
						'moderator_groups' => array()
2829
					);
2830
				}
2831
				// If a moderator exists for this board, add that moderator for all children too.
2832 View Code Duplication
				if (!empty($row['id_moderator']))
2833
					foreach ($boards as $id => $dummy)
2834
					{
2835
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2836
							'id' => $row['id_moderator'],
2837
							'name' => $row['real_name'],
2838
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2839
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2840
						);
2841
					}
2842
2843
				// If a moderator group exists for this board, add that moderator group for all children too
2844 View Code Duplication
				if (!empty($row['id_moderator_group']))
2845
					foreach ($boards as $id => $dummy)
2846
					{
2847
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
2848
							'id' => $row['id_moderator_group'],
2849
							'name' => $row['group_name'],
2850
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
2851
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
2852
						);
2853
					}
2854
			}
2855
			$smcFunc['db_free_result']($result);
2856
		}
2857
2858
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
2859
	}
2860
2861
	return $boards;
2862
}
2863
2864
/**
2865
 * Attempt to reload our known languages.
2866
 * It will try to choose only utf8 or non-utf8 languages.
2867
 *
2868
 * @param bool $use_cache Whether or not to use the cache
2869
 * @return array An array of information about available languages
0 ignored issues
show
Documentation introduced by
Should the return type not be string?

This check compares the return type specified in the @return annotation of a function or method doc comment with the types returned by the function and raises an issue if they mismatch.

Loading history...
2870
 */
2871
function getLanguages($use_cache = true)
2872
{
2873
	global $context, $smcFunc, $settings, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2874
2875
	// Either we don't use the cache, or its expired.
2876
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null)
2877
	{
2878
		// If we don't have our ucwords function defined yet, let's load the settings data.
2879
		if (empty($smcFunc['ucwords']))
2880
			reloadSettings();
2881
2882
		// If we don't have our theme information yet, let's get it.
2883
		if (empty($settings['default_theme_dir']))
2884
			loadTheme(0, false);
2885
2886
		// Default language directories to try.
2887
		$language_directories = array(
2888
			$settings['default_theme_dir'] . '/languages',
2889
		);
2890
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
2891
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
2892
2893
		// We possibly have a base theme directory.
2894
		if (!empty($settings['base_theme_dir']))
2895
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2896
2897
		// Remove any duplicates.
2898
		$language_directories = array_unique($language_directories);
2899
2900
		// Get a list of languages.
2901
		$langList = !empty($modSettings['langList']) ? $smcFunc['json_decode']($modSettings['langList'], true) : array();
2902
		$langList = is_array($langList) ? $langList : false;
2903
2904
		$catchLang = array();
2905
2906
		foreach ($language_directories as $language_dir)
2907
		{
2908
			// Can't look in here... doesn't exist!
2909
			if (!file_exists($language_dir))
2910
				continue;
2911
2912
			$dir = dir($language_dir);
2913
			while ($entry = $dir->read())
2914
			{
2915
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
2916
				if (!preg_match('~^index\.(.+[^-utf8])\.php$~', $entry, $matches))
2917
					continue;
2918
2919
				if (!empty($langList) && !empty($langList[$matches[1]]))
2920
					$langName = $langList[$matches[1]];
2921
2922
				else
2923
				{
2924
					$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
2925
2926
					// Get the line we need.
2927
					$fp = @fopen($language_dir . '/' . $entry);
2928
2929
					// Yay!
2930
					if ($fp)
2931
					{
2932
						while (($line = fgets($fp)) !== false)
2933
						{
2934
							preg_match('~\$txt\[\'native_name\'\] = \'(.+)\'\;~', $line, $matchNative);
2935
2936
							// Set the language's name.
2937
							if (!empty($matchNative) && !empty($matchNative[1]))
2938
							{
2939
								$langName = un_htmlspecialchars($matchNative[1]);
2940
								break;
2941
							}
2942
						}
2943
2944
						fclose($fp);
2945
					}
2946
2947
					// Catch the language name.
2948
					$catchLang[$matches[1]] = $langName;
2949
				}
2950
2951
				// Build this language entry.
2952
				$context['languages'][$matches[1]] = array(
2953
					'name' => $langName,
2954
					'selected' => false,
2955
					'filename' => $matches[1],
2956
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
2957
				);
2958
			}
2959
			$dir->close();
2960
		}
2961
2962
		// Do we need to store the lang list?
2963
		if (empty($langList))
2964
			updateSettings(array('langList' => $smcFunc['json_encode']($catchLang)));
2965
2966
		// Let's cash in on this deal.
2967 View Code Duplication
		if (!empty($modSettings['cache_enable']))
2968
			cache_put_data('known_languages', $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
2969
	}
2970
2971
	return $context['languages'];
2972
}
2973
2974
/**
2975
 * Replace all vulgar words with respective proper words. (substring or whole words..)
2976
 * What this function does:
2977
 *  - it censors the passed string.
2978
 *  - if the theme setting allow_no_censored is on, and the theme option
2979
 *	show_no_censored is enabled, does not censor, unless force is also set.
2980
 *  - it caches the list of censored words to reduce parsing.
2981
 *
2982
 * @param string &$text The text to censor
2983
 * @param bool $force Whether to censor the text regardless of settings
2984
 * @return string The censored text
2985
 */
2986
function censorText(&$text, $force = false)
2987
{
2988
	global $modSettings, $options, $txt;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
2989
	static $censor_vulgar = null, $censor_proper;
2990
2991
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
2992
		return $text;
2993
2994
	// If they haven't yet been loaded, load them.
2995
	if ($censor_vulgar == null)
2996
	{
2997
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
2998
		$censor_proper = explode("\n", $modSettings['censor_proper']);
2999
3000
		// Quote them for use in regular expressions.
3001
		if (!empty($modSettings['censorWholeWord']))
3002
		{
3003
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
3004
3005
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
3006
			{
3007
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
3008
3009
				// Use the faster \b if we can, or something more complex if we can't
3010
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
3011
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
3012
3013
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
3014
			}
3015
		}
3016
	}
3017
3018
	// Censoring isn't so very complicated :P.
3019
	if (empty($modSettings['censorWholeWord']))
3020
	{
3021
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
3022
		$text = $func($censor_vulgar, $censor_proper, $text);
3023
	}
3024
	else
3025
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3026
3027
	return $text;
3028
}
3029
3030
/**
3031
 * Load the template/language file using eval or require? (with eval we can show an error message!)
3032
 * 	- loads the template or language file specified by filename.
3033
 * 	- uses eval unless disableTemplateEval is enabled.
3034
 * 	- outputs a parse error if the file did not exist or contained errors.
3035
 * 	- attempts to detect the error and line, and show detailed information.
3036
 *
3037
 * @param string $filename The name of the file to include
3038
 * @param bool $once If true only includes the file once (like include_once)
3039
 */
3040
function template_include($filename, $once = false)
3041
{
3042
	global $context, $settings, $txt, $scripturl, $modSettings;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3043
	global $boardurl, $boarddir, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3044
	global $maintenance, $mtitle, $mmessage;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3045
	static $templates = array();
3046
3047
	// We want to be able to figure out any errors...
3048
	@ini_set('track_errors', '1');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
3049
3050
	// Don't include the file more than once, if $once is true.
3051
	if ($once && in_array($filename, $templates))
3052
		return;
3053
	// Add this file to the include list, whether $once is true or not.
3054
	else
3055
		$templates[] = $filename;
3056
3057
	// Are we going to use eval?
3058
	if (empty($modSettings['disableTemplateEval']))
3059
	{
3060
		$file_found = file_exists($filename) && eval('?' . '>' . rtrim(file_get_contents($filename))) !== false;
0 ignored issues
show
Coding Style introduced by
The function template_include() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
3061
		$settings['current_include_filename'] = $filename;
3062
	}
3063
	else
3064
	{
3065
		$file_found = file_exists($filename);
3066
3067
		if ($once && $file_found)
3068
			require_once($filename);
3069
		elseif ($file_found)
3070
			require($filename);
3071
	}
3072
3073
	if ($file_found !== true)
3074
	{
3075
		ob_end_clean();
3076
		if (!empty($modSettings['enableCompressedOutput']))
3077
			@ob_start('ob_gzhandler');
0 ignored issues
show
Security Best Practice introduced by
It seems like you do not handle an error condition here. This can introduce security issues, and is generally not recommended.

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...
3078
		else
3079
			ob_start();
3080
3081 View Code Duplication
		if (isset($_GET['debug']))
3082
			header('content-type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3083
3084
		// Don't cache error pages!!
3085
		header('expires: Mon, 26 Jul 1997 05:00:00 GMT');
3086
		header('last-modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3087
		header('cache-control: no-cache');
3088
3089
		if (!isset($txt['template_parse_error']))
3090
		{
3091
			$txt['template_parse_error'] = 'Template Parse Error!';
3092
			$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>.';
3093
			$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>.';
3094
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3095
		}
3096
3097
		// First, let's get the doctype and language information out of the way.
3098
		echo '<!DOCTYPE html>
3099
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3100
	<head>';
3101
		if (isset($context['character_set']))
3102
			echo '
3103
		<meta charset="', $context['character_set'], '">';
3104
3105
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3106
			echo '
3107
		<title>', $mtitle, '</title>
3108
	</head>
3109
	<body>
3110
		<h3>', $mtitle, '</h3>
3111
		', $mmessage, '
3112
	</body>
3113
</html>';
3114
		elseif (!allowedTo('admin_forum'))
3115
			echo '
3116
		<title>', $txt['template_parse_error'], '</title>
3117
	</head>
3118
	<body>
3119
		<h3>', $txt['template_parse_error'], '</h3>
3120
		', $txt['template_parse_error_message'], '
3121
	</body>
3122
</html>';
3123
		else
3124
		{
3125
			require_once($sourcedir . '/Subs-Package.php');
3126
3127
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3128
			$error_array = error_get_last();
3129
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3130
				$error = $error_array['message'];
3131
			if (empty($error))
3132
				$error = $txt['template_parse_errmsg'];
3133
3134
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3135
3136
			echo '
3137
		<title>', $txt['template_parse_error'], '</title>
3138
	</head>
3139
	<body>
3140
		<h3>', $txt['template_parse_error'], '</h3>
3141
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3142
3143
			if (!empty($error))
3144
				echo '
3145
		<hr>
3146
3147
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3148
3149
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3150
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3151
			{
3152
				$data = file($filename);
3153
				$data2 = highlight_php_code(implode('', $data));
3154
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3155
3156
				// Fix the PHP code stuff...
3157
				if (!isBrowser('gecko'))
3158
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3159
				else
3160
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3161
3162
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3163
				$j = -1;
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $j. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3164
				foreach ($data as $line)
3165
				{
3166
					$j++;
3167
3168
					if (substr_count($line, '<br>') == 0)
3169
						continue;
3170
3171
					$n = substr_count($line, '<br>');
3172
					for ($i = 0; $i < $n; $i++)
3173
					{
3174
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3175
						unset($data2[$j + $i + 1]);
3176
					}
3177
					$j += $n;
3178
				}
3179
				$data2 = array_values($data2);
3180
				array_unshift($data2, '');
3181
3182
				echo '
3183
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3184
3185
				// Figure out what the color coding was before...
3186
				$line = max($match[1] - 9, 1);
3187
				$last_line = '';
3188
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3189
					if (strpos($data2[$line2], '<') !== false)
3190
					{
3191
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3192
							$last_line = $color_match[1];
3193
						break;
3194
					}
3195
3196
				// Show the relevant lines...
3197
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3198
				{
3199
					if ($line == $match[1])
3200
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3201
3202
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3203
					if (isset($data2[$line]) && $data2[$line] != '')
3204
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3205
3206
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3207
					{
3208
						$last_line = $color_match[1];
3209
						echo '</', substr($last_line, 1, 4), '>';
3210
					}
3211
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3212
						$last_line = '';
3213
					elseif ($last_line != '' && $data2[$line] != '')
3214
						echo '</', substr($last_line, 1, 4), '>';
3215
3216
					if ($line == $match[1])
3217
						echo '</pre></div><pre style="margin: 0;">';
3218
					else
3219
						echo "\n";
3220
				}
3221
3222
				echo '</pre></div>';
3223
			}
3224
3225
			echo '
3226
	</body>
3227
</html>';
3228
		}
3229
3230
		die;
3231
	}
3232
}
3233
3234
/**
3235
 * Initialize a database connection.
3236
 */
3237
function loadDatabase()
3238
{
3239
	global $db_persist, $db_connection, $db_server, $db_user, $db_passwd;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3240
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $sourcedir, $db_prefix, $db_port;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3241
3242
	// Figure out what type of database we are using.
3243
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3244
		$db_type = 'mysql';
3245
3246
	// Load the file for the database.
3247
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3248
3249
	$db_options = array();
3250
3251
	// Add in the port if needed
3252
	if (!empty($db_port))
3253
		$db_options['port'] = $db_port;
3254
3255
	// 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.
3256
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3257
	{
3258
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3259
3260
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3261
	}
3262
3263
	// Either we aren't in SSI mode, or it failed.
3264
	if (empty($db_connection))
3265
	{
3266
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3267
3268
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3269
	}
3270
3271
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3272
	if (!$db_connection)
3273
		display_db_error();
3274
3275
	// If in SSI mode fix up the prefix.
3276
	if (SMF == 'SSI')
3277
		db_fix_prefix($db_prefix, $db_name);
0 ignored issues
show
Unused Code introduced by
The call to the function db_fix_prefix() seems unnecessary as the function has no side-effects.
Loading history...
3278
}
3279
3280
/**
3281
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3282
 *
3283
 * @param string $overrideCache Try to use a different cache method other than that defined in $cache_accelerator.
0 ignored issues
show
Documentation introduced by
Should the type for parameter $overrideCache not be string|null?

This check looks for @param annotations where the type inferred by our type inference engine differs from the declared type.

It makes a suggestion as to what type it considers more descriptive.

Most often this is a case of a parameter that can be null in addition to its declared types.

Loading history...
3284
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3285
 * @return object|false A object of $cacheAPI, or False on failure.
3286
*/
3287
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3288
{
3289
	global $sourcedir, $cacheAPI, $cache_accelerator;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3290
3291
	// Not overriding this and we have a cacheAPI, send it back.
3292
	if (empty($overrideCache) && is_object($cacheAPI))
3293
		return $cacheAPI;
3294
	elseif (is_null($cacheAPI))
3295
		$cacheAPI = false;
3296
3297
	// Make sure our class is in session.
3298
	require_once($sourcedir . '/Class-CacheAPI.php');
3299
3300
	// What accelerator we are going to try.
3301
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : !empty($cache_accelerator) ? $cache_accelerator : 'smf';
3302
	$tryAccelerator = strtolower($tryAccelerator);
3303
3304
	// Do some basic tests.
3305
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3306
	{
3307
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3308
3309
		$cache_class_name = $tryAccelerator . '_cache';
3310
		$testAPI = new $cache_class_name();
3311
3312
		// No Support?  NEXT!
3313
		if (!$testAPI->isSupported())
3314
		{
3315
			// Can we save ourselves?
3316
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3317
				return loadCacheAccelerator(null, false);
3318
			return false;
3319
		}
3320
3321
		// Connect up to the accelerator.
3322
		$testAPI->connect();
3323
3324
		// Don't set this if we are overriding the cache.
3325
		if (is_null($overrideCache))
3326
		{
3327
			$cacheAPI = $testAPI;
3328
			return $cacheAPI;
3329
		}
3330
		else
3331
			return $testAPI;
3332
	}
3333
}
3334
3335
/**
3336
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3337
 *
3338
 * @param string $key The key for this entry
3339
 * @param string $file The file associated with this entry
3340
 * @param string $function The function to call
3341
 * @param array $params Parameters to be passed to the specified function
3342
 * @param int $level The cache level
3343
 * @return string The cached data
3344
 */
3345
function cache_quick_get($key, $file, $function, $params, $level = 1)
3346
{
3347
	global $modSettings, $sourcedir;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3348
3349
	// @todo Why are we doing this if caching is disabled?
0 ignored issues
show
Coding Style Best Practice introduced by
Comments for TODO tasks are often forgotten in the code; it might be better to use a dedicated issue tracker.
Loading history...
3350
3351
	if (function_exists('call_integration_hook'))
3352
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3353
3354
	/* Refresh the cache if either:
3355
		1. Caching is disabled.
3356
		2. The cache level isn't high enough.
3357
		3. The item has not been cached or the cached item expired.
3358
		4. The cached item has a custom expiration condition evaluating to true.
3359
		5. The expire time set in the cache item has passed (needed for Zend).
3360
	*/
3361
	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
Coding Style introduced by
The function cache_quick_get() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
3362
	{
3363
		require_once($sourcedir . '/' . $file);
3364
		$cache_block = call_user_func_array($function, $params);
3365
3366
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= $level)
3367
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3368
	}
3369
3370
	// Some cached data may need a freshening up after retrieval.
3371
	if (!empty($cache_block['post_retri_eval']))
3372
		eval($cache_block['post_retri_eval']);
0 ignored issues
show
Coding Style introduced by
The function cache_quick_get() contains an eval expression.

On one hand, eval might be exploited by malicious users if they somehow manage to inject dynamic content. On the other hand, with the emergence of faster PHP runtimes like the HHVM, eval prevents some optimization that they perform.

Loading history...
3373
3374
	if (function_exists('call_integration_hook'))
3375
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3376
3377
	return $cache_block['data'];
3378
}
3379
3380
/**
3381
 * Puts value in the cache under key for ttl seconds.
3382
 *
3383
 * - It may "miss" so shouldn't be depended on
3384
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3385
 * - It supports:
3386
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3387
 *	 memcache: https://php.net/memcache
3388
 *	 APC: https://php.net/apc
3389
 *   APCu: https://php.net/book.apcu
3390
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3391
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3392
 *
3393
 * @param string $key A key for this value
3394
 * @param mixed $value The data to cache
3395
 * @param int $ttl How long (in seconds) the data should be cached for
3396
 */
3397
function cache_put_data($key, $value, $ttl = 120)
3398
{
3399
	global $smcFunc, $cache_enable, $cacheAPI;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3400
	global $cache_hits, $cache_count, $db_show_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3401
3402
	if (empty($cache_enable) || empty($cacheAPI))
3403
		return;
3404
3405
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3406
	if (isset($db_show_debug) && $db_show_debug === true)
3407
	{
3408
		$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)));
3409
		$st = microtime();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3410
	}
3411
3412
	// The API will handle the rest.
3413
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3414
	$cacheAPI->putData($key, $value, $ttl);
3415
3416
	if (function_exists('call_integration_hook'))
3417
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3418
3419 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
3420
		$cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
0 ignored issues
show
Bug introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3421
}
3422
3423
/**
3424
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3425
 * - It may often "miss", so shouldn't be depended on.
3426
 * - It supports the same as cache_put_data().
3427
 *
3428
 * @param string $key The key for the value to retrieve
3429
 * @param int $ttl The maximum age of the cached data
3430
 * @return string The cached data or null if nothing was loaded
3431
 */
3432
function cache_get_data($key, $ttl = 120)
3433
{
3434
	global $smcFunc, $cache_enable, $cacheAPI;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3435
	global $cache_hits, $cache_count, $cache_misses, $cache_count_misses, $db_show_debug;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3436
3437
	if (empty($cache_enable) || empty($cacheAPI))
3438
		return;
3439
3440
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3441
	if (isset($db_show_debug) && $db_show_debug === true)
3442
	{
3443
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3444
		$st = microtime();
0 ignored issues
show
Comprehensibility introduced by
Avoid variables with short names like $st. Configured minimum length is 3.

Short variable names may make your code harder to understand. Variable names should be self-descriptive. This check looks for variable names who are shorter than a configured minimum.

Loading history...
3445
		$original_key = $key;
3446
	}
3447
3448
	// Ask the API to get the data.
3449
	$value = $cacheAPI->getData($key, $ttl);
3450
3451
	if (isset($db_show_debug) && $db_show_debug === true)
3452
	{
3453
		$cache_hits[$cache_count]['t'] = array_sum(explode(' ', microtime())) - array_sum(explode(' ', $st));
0 ignored issues
show
Bug introduced by
The variable $st does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3454
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3455
3456
		if (empty($value))
3457
		{
3458
			if (!is_array($cache_misses))
3459
				$cache_misses = array();
3460
3461
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3462
			$cache_misses[$cache_count_misses] = array('k' => $original_key, 'd' => 'get');
0 ignored issues
show
Bug introduced by
The variable $original_key does not seem to be defined for all execution paths leading up to this point.

If you define a variable conditionally, it can happen that it is not defined for all execution paths.

Let’s take a look at an example:

function myFunction($a) {
    switch ($a) {
        case 'foo':
            $x = 1;
            break;

        case 'bar':
            $x = 2;
            break;
    }

    // $x is potentially undefined here.
    echo $x;
}

In the above example, the variable $x is defined if you pass “foo” or “bar” as argument for $a. However, since the switch statement has no default case statement, if you pass any other value, the variable $x would be undefined.

Available Fixes

  1. Check for existence of the variable explicitly:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        if (isset($x)) { // Make sure it's always set.
            echo $x;
        }
    }
    
  2. Define a default value for the variable:

    function myFunction($a) {
        $x = ''; // Set a default which gets overridden for certain paths.
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
        }
    
        echo $x;
    }
    
  3. Add a value for the missing path:

    function myFunction($a) {
        switch ($a) {
            case 'foo':
                $x = 1;
                break;
    
            case 'bar':
                $x = 2;
                break;
    
            // We add support for the missing case.
            default:
                $x = '';
                break;
        }
    
        echo $x;
    }
    
Loading history...
3463
		}
3464
	}
3465
3466
	if (function_exists('call_integration_hook') && isset($value))
3467
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3468
3469
	return empty($value) ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3470
}
3471
3472
/**
3473
 * Empty out the cache in use as best it can
3474
 *
3475
 * It may only remove the files of a certain type (if the $type parameter is given)
3476
 * Type can be user, data or left blank
3477
 * 	- user clears out user data
3478
 *  - data clears out system / opcode data
3479
 *  - If no type is specified will perform a complete cache clearing
3480
 * For cache engines that do not distinguish on types, a full cache flush will be done
3481
 *
3482
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3483
 */
3484
function clean_cache($type = '')
3485
{
3486
	global $cacheAPI;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3487
3488
	// If we can't get to the API, can't do this.
3489
	if (empty($cacheAPI))
3490
		return;
3491
3492
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3493
	$cacheAPI->cleanCache($type);
3494
3495
	call_integration_hook('integrate_clean_cache');
3496
	clearstatcache();
3497
}
3498
3499
/**
3500
 * Helper function to set an array of data for an user's avatar.
3501
 *
3502
 * Makes assumptions based on the data provided, the following keys are required:
3503
 * - avatar The raw "avatar" column in members table
3504
 * - email The user's email. Used to get the gravatar info
3505
 * - filename The attachment filename
3506
 *
3507
 * @param array $data An array of raw info
3508
 * @return array An array of avatar data
3509
 */
3510
function set_avatar_data($data = array())
3511
{
3512
	global $modSettings, $boardurl, $smcFunc, $image_proxy_enabled, $image_proxy_url, $image_proxy_secret, $user_info;
0 ignored issues
show
Compatibility Best Practice introduced by
Use of global functionality is not recommended; it makes your code harder to test, and less reusable.

Instead of relying on global state, we recommend one of these alternatives:

1. Pass all data via parameters

function myFunction($a, $b) {
    // Do something
}

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

    public function __construct($a, $b) {
        $this->a = $a;
        $this->b = $b;
    }

    public function myFunction() {
        // Do something
    }
}
Loading history...
3513
3514
	// Come on!
3515
	if (empty($data))
3516
		return array();
3517
3518
	// Set a nice default var.
3519
	$image = '';
3520
3521
	// Gravatar has been set as mandatory!
3522
	if (!empty($modSettings['gravatarOverride']))
3523
	{
3524
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3525
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3526
3527
		else if (!empty($data['email']))
3528
			$image = get_gravatar_url($data['email']);
3529
	}
3530
3531
	// Look if the user has a gravatar field or has set an external url as avatar.
3532
	else
3533
	{
3534
		// So it's stored in the member table?
3535
		if (!empty($data['avatar']))
3536
		{
3537
			// Gravatar.
3538
			if (stristr($data['avatar'], 'gravatar://'))
3539
			{
3540
				if ($data['avatar'] == 'gravatar://')
3541
					$image = get_gravatar_url($data['email']);
3542
3543
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3544
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3545
			}
3546
3547
			// External url.
3548
			else
3549
			{
3550
				// Using ssl?
3551
				if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($data['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
3552
					if ($image_proxy_enabled === 2 && !empty($image_proxy_url))
3553
						$image = $image_proxy_url . urlencode($data['avatar']);
3554
					else
3555
						$image = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($data['avatar']) . '&hash=' . md5($data['avatar'] . $image_proxy_secret);
3556
3557
				// Just a plain external url.
3558
				else
3559
					$image = (stristr($data['avatar'], 'http://') || stristr($data['avatar'], 'https://')) ? $data['avatar'] : $modSettings['avatar_url'] . '/' . $data['avatar'];
3560
			}
3561
		}
3562
3563
		// Perhaps this user has an attachment as avatar...
3564
		else if (!empty($data['filename']))
3565
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3566
3567
		// Right... no avatar... use our default image.
3568
		else
3569
			$image = $modSettings['avatar_url'] . '/default.png';
3570
	}
3571
3572
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3573
3574
	// 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.
3575
	if (!empty($image))
3576
		return array(
3577
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3578
			'image' => '<img class="avatar" src="' . $image . '" />',
3579
			'href' => $image,
3580
			'url' => $image,
3581
		);
3582
3583
	// Fallback to make life easier for everyone...
3584
	else
3585
		return array(
3586
			'name' => '',
3587
			'image' => '',
3588
			'href' => '',
3589
			'url' => '',
3590
		);
3591
}
3592
3593
?>