Completed
Push — release-2.1 ( b3b0ac...43492c )
by Mathias
11s
created

Load.php ➔ censorText()   D

Complexity

Conditions 18
Paths 13

Size

Total Lines 43
Code Lines 21

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 18
eloc 21
nc 13
nop 2
dl 0
loc 43
rs 4.947
c 0
b 0
f 0

How to fix   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}|quot|amp|lt|gt|nbsp);' : '&(#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
	// Preg_replace space characters depend on the character set in use
129
	$space_chars = $utf8 ? '\x{A0}\x{AD}\x{2000}-\x{200F}\x{201F}\x{202F}\x{3000}\x{FEFF}' : '\x00-\x08\x0B\x0C\x0E-\x19\xA0';
130
131
	// global array of anonymous helper functions, used mostly to properly handle multi byte strings
132
	$smcFunc += array(
133
		'entity_fix' => function($string)
134
		{
135
			$num = $string[0] === 'x' ? hexdec(substr($string, 1)) : (int) $string;
136
			return $num < 0x20 || $num > 0x10FFFF || ($num >= 0xD800 && $num <= 0xDFFF) || $num === 0x202E || $num === 0x202D ? '' : '&#' . $num . ';';
137
		},
138
		'htmlspecialchars' => function($string, $quote_style = ENT_COMPAT, $charset = 'ISO-8859-1') use ($ent_check, $utf8, $fix_utf8mb4)
139
		{
140
			return $fix_utf8mb4($ent_check(htmlspecialchars($string, $quote_style, $utf8 ? 'UTF-8' : $charset)));
141
		},
142
		'htmltrim' => function($string) use ($utf8, $space_chars, $ent_check)
143
		{
144
			return preg_replace('~^(?:[ \t\n\r\x0B\x00' . $space_chars . ']|&nbsp;)+|(?:[ \t\n\r\x0B\x00' . $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, $modSettings)
151
		{
152
			$haystack_arr = preg_split('~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($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('~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($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, $modSettings)
176
		{
177
			$ent_arr = preg_split('~(&#' . (empty($modSettings['disableEntityCheck']) ? '\d{1,7}' : '021') . ';|&quot;|&amp;|&lt;|&gt;|&nbsp;|.)~' . ($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_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
				$user_settings['avatar'] = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($user_settings['avatar']) . '&hash=' . md5($user_settings['avatar'] . $image_proxy_secret);
450
451
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
452
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
453
		}
454
455
		// Did we find 'im?  If not, junk it.
456
		if (!empty($user_settings))
457
		{
458
			// As much as the password should be right, we can assume the integration set things up.
459
			if (!empty($already_verified) && $already_verified === true)
460
				$check = true;
461
			// SHA-512 hash should be 128 characters long.
462
			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...
463
				$check = hash_salt($user_settings['passwd'], $user_settings['password_salt']) == $password;
464
			else
465
				$check = false;
466
467
			// Wrong password or not activated - either way, you're going nowhere.
468
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
469
		}
470
		else
471
			$id_member = 0;
472
473
		// If we no longer have the member maybe they're being all hackey, stop brute force!
474
		if (!$id_member)
475
		{
476
			require_once($sourcedir . '/LogInOut.php');
477
			validatePasswordFlood(
478
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
479
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
480
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
481
				$id_member != 0
482
			);
483
		}
484
		// Validate for Two Factor Authentication
485
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
486
		{
487
			$tfacookie = $cookiename . '_tfa';
488
			$tfasecret = null;
489
490
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
491
492
			if (empty($verified) || !in_array(true, $verified))
493
			{
494
				if (!empty($_COOKIE[$tfacookie]))
495
				{
496
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie], true);
497
498
					list ($tfamember, $tfasecret) = $tfa_data;
499
500
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
501
						$tfasecret = null;
502
				}
503
504
				if (empty($tfasecret) || hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']) != $tfasecret)
505
				{
506
					$id_member = 0;
507
					redirectexit('action=logintfa');
508
				}
509
			}
510
		}
511
		// When authenticating their two factor code, make sure to reset their ID for security
512
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
513
		{
514
			$id_member = 0;
515
			$context['tfa_member'] = $user_settings;
516
			$user_settings = array();
517
		}
518
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
519
		elseif (!empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']))
520
		{
521
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
522
			{
523
				//Build an array of ALL user membergroups.
524
				$full_groups = array($user_settings['id_group']);
525
				if (!empty($user_settings['additional_groups']))
526
				{
527
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
528
					$full_groups = array_unique($full_groups); //duplicates, maybe?
529
				}
530
531
				//Find out if any group requires 2FA
532
				$request = $smcFunc['db_query']('', '
533
					SELECT COUNT(id_group) AS total
534
					FROM {db_prefix}membergroups
535
					WHERE tfa_required = {int:tfa_required}
536
						AND id_group IN ({array_int:full_groups})',
537
					array(
538
						'tfa_required' => 1,
539
						'full_groups' => $full_groups,
540
					)
541
				);
542
				$row = $smcFunc['db_fetch_assoc']($request);
543
				$smcFunc['db_free_result']($request);
544
			}
545
			else
546
				$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...
547
548
			$area = !empty($_REQUEST['area']) ? $_REQUEST['area'] : '';
549
			$action = !empty($_REQUEST['action']) ? $_REQUEST['action'] : '';
550
551
			if ($row['total'] > 0 && !in_array($action, array('profile', 'logout')) || ($action == 'profile' && $area != 'tfasetup'))
552
				redirectexit('action=profile;area=tfasetup;forced');
553
		}
554
	}
555
556
	// Found 'im, let's set up the variables.
557
	if ($id_member != 0)
558
	{
559
		// Let's not update the last visit time in these cases...
560
		// 1. SSI doesn't count as visiting the forum.
561
		// 2. RSS feeds and XMLHTTP requests don't count either.
562
		// 3. If it was set within this session, no need to set it again.
563
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
564
		// 5. We're still logging in or authenticating
565
		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))
566
		{
567
			// @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...
568
			// Do a quick query to make sure this isn't a mistake.
569
			$result = $smcFunc['db_query']('', '
570
				SELECT poster_time
571
				FROM {db_prefix}messages
572
				WHERE id_msg = {int:id_msg}
573
				LIMIT 1',
574
				array(
575
					'id_msg' => $user_settings['id_msg_last_visit'],
576
				)
577
			);
578
			list ($visitTime) = $smcFunc['db_fetch_row']($result);
579
			$smcFunc['db_free_result']($result);
580
581
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
582
583
			// If it was *at least* five hours ago...
584
			if ($visitTime < time() - 5 * 3600)
585
			{
586
				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']));
587
				$user_settings['last_login'] = time();
588
589
				if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
590
					cache_put_data('user_settings-' . $id_member, $user_settings, 60);
591
592
				if (!empty($modSettings['cache_enable']))
593
					cache_put_data('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
594
			}
595
		}
596
		elseif (empty($_SESSION['id_msg_last_visit']))
597
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
598
599
		$username = $user_settings['member_name'];
600
601
		if (empty($user_settings['additional_groups']))
602
			$user_info = array(
603
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
604
			);
605
		else
606
			$user_info = array(
607
				'groups' => array_merge(
608
					array($user_settings['id_group'], $user_settings['id_post_group']),
609
					explode(',', $user_settings['additional_groups'])
610
				)
611
			);
612
613
		// Because history has proven that it is possible for groups to go bad - clean up in case.
614
		foreach ($user_info['groups'] as $k => $v)
615
			$user_info['groups'][$k] = (int) $v;
616
617
		// This is a logged in user, so definitely not a spider.
618
		$user_info['possibly_robot'] = false;
619
620
		// Figure out the new time offset.
621
		if (!empty($user_settings['timezone']))
622
		{
623
			// Get the offsets from UTC for the server, then for the user.
624
			$tz_system = new DateTimeZone(@date_default_timezone_get());
625
			$tz_user = new DateTimeZone($user_settings['timezone']);
626
			$time_system = new DateTime('now', $tz_system);
627
			$time_user = new DateTime('now', $tz_user);
628
			$user_info['time_offset'] = ($tz_user->getOffset($time_user) - $tz_system->getOffset($time_system)) / 3600;
629
		}
630
		else
631
		{
632
			// !!! 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...
633
			$user_info['time_offset'] = empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'];
634
		}
635
	}
636
	// If the user is a guest, initialize all the critical user settings.
637
	else
638
	{
639
		// This is what a guest's variables should be.
640
		$username = '';
641
		$user_info = array('groups' => array(-1));
642
		$user_settings = array();
643
644
		if (isset($_COOKIE[$cookiename]) && empty($context['tfa_member']))
645
			$_COOKIE[$cookiename] = '';
646
647
		// Expire the 2FA cookie
648
		if (isset($_COOKIE[$cookiename . '_tfa']) && empty($context['tfa_member']))
649
		{
650
			$tfa_data = $smcFunc['json_decode']($_COOKIE[$cookiename . '_tfa'], true);
651
652
			list ($id, $user, $exp, $domain, $path, $preserve) = $tfa_data;
653
654
			if (!isset($id, $user, $exp, $domain, $path, $preserve) || !$preserve || time() > $exp)
655
			{
656
				$_COOKIE[$cookiename . '_tfa'] = '';
657
				setTFACookie(-3600, 0, '');
658
			}
659
		}
660
661
		// Create a login token if it doesn't exist yet.
662
		if (!isset($_SESSION['token']['post-login']))
663
			createToken('login');
664
		else
665
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
666
667
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
668
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
669
		{
670
			require_once($sourcedir . '/ManageSearchEngines.php');
671
			$user_info['possibly_robot'] = SpiderCheck();
672
		}
673
		elseif (!empty($modSettings['spider_mode']))
674
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
675
		// If we haven't turned on proper spider hunts then have a guess!
676
		else
677
		{
678
			$ci_user_agent = strtolower($_SERVER['HTTP_USER_AGENT']);
679
			$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;
680
		}
681
682
		// We don't know the offset...
683
		$user_info['time_offset'] = 0;
684
	}
685
686
	// Set up the $user_info array.
687
	$user_info += array(
688
		'id' => $id_member,
689
		'username' => $username,
690
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
691
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
692
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
693
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
694
		'is_guest' => $id_member == 0,
695
		'is_admin' => in_array(1, $user_info['groups']),
696
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
697
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
698
		'ip' => $_SERVER['REMOTE_ADDR'],
699
		'ip2' => $_SERVER['BAN_CHECK_IP'],
700
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
701
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
702
		'avatar' => array(
703
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
704
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
705
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
706
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
707
		),
708
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
709
		'messages' => empty($user_settings['instant_messages']) ? 0 : $user_settings['instant_messages'],
710
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
711
		'alerts' => empty($user_settings['alerts']) ? 0 : $user_settings['alerts'],
712
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
713
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
714
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
715
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
716
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
717
		'permissions' => array(),
718
	);
719
	$user_info['groups'] = array_unique($user_info['groups']);
720
721
	// 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.
722
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
723
		unset($user_info['ignoreboards'][$tmp]);
724
725
	// Allow the user to change their language.
726
	if (!empty($modSettings['userLanguage']))
727
	{
728
		$languages = getLanguages();
729
730
		// Is it valid?
731
		if (!empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
732
		{
733
			$user_info['language'] = strtr($_GET['language'], './\\:', '____');
734
735
			// Make it permanent for members.
736
			if (!empty($user_info['id']))
737
				updateMemberData($user_info['id'], array('lngfile' => $user_info['language']));
738
			else
739
				$_SESSION['language'] = $user_info['language'];
740
		}
741
		elseif (!empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
742
			$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
743
	}
744
745
	$temp = build_query_board($user_info['id']);
746
	$user_info['query_see_board'] = $temp['query_see_board'];
747
	$user_info['query_wanna_see_board'] = $temp['query_wanna_see_board'];
748
749
	call_integration_hook('integrate_user_info');
750
}
751
752
/**
753
 * Check for moderators and see if they have access to the board.
754
 * What it does:
755
 * - sets up the $board_info array for current board information.
756
 * - if cache is enabled, the $board_info array is stored in cache.
757
 * - redirects to appropriate post if only message id is requested.
758
 * - is only used when inside a topic or board.
759
 * - determines the local moderators for the board.
760
 * - adds group id 3 if the user is a local moderator for the board they are in.
761
 * - prevents access if user is not in proper group nor a local moderator of the board.
762
 */
763
function loadBoard()
764
{
765
	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...
766
	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...
767
768
	// Assume they are not a moderator.
769
	$user_info['is_mod'] = false;
770
	$context['user']['is_mod'] = &$user_info['is_mod'];
771
772
	// Start the linktree off empty..
773
	$context['linktree'] = array();
774
775
	// Have they by chance specified a message id but nothing else?
776
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
777
	{
778
		// Make sure the message id is really an int.
779
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
780
781
		// Looking through the message table can be slow, so try using the cache first.
782
		if (($topic = cache_get_data('msg_topic-' . $_REQUEST['msg'], 120)) === null)
783
		{
784
			$request = $smcFunc['db_query']('', '
785
				SELECT id_topic
786
				FROM {db_prefix}messages
787
				WHERE id_msg = {int:id_msg}
788
				LIMIT 1',
789
				array(
790
					'id_msg' => $_REQUEST['msg'],
791
				)
792
			);
793
794
			// So did it find anything?
795
			if ($smcFunc['db_num_rows']($request))
796
			{
797
				list ($topic) = $smcFunc['db_fetch_row']($request);
798
				$smcFunc['db_free_result']($request);
799
				// Save save save.
800
				cache_put_data('msg_topic-' . $_REQUEST['msg'], $topic, 120);
801
			}
802
		}
803
804
		// Remember redirection is the key to avoiding fallout from your bosses.
805
		if (!empty($topic))
806
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
807
		else
808
		{
809
			loadPermissions();
810
			loadTheme();
811
			fatal_lang_error('topic_gone', false);
812
		}
813
	}
814
815
	// Load this board only if it is specified.
816
	if (empty($board) && empty($topic))
817
	{
818
		$board_info = array('moderators' => array(), 'moderator_groups' => array());
819
		return;
820
	}
821
822
	if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
823
	{
824
		// @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...
825
		if (!empty($topic))
826
			$temp = cache_get_data('topic_board-' . $topic, 120);
827
		else
828
			$temp = cache_get_data('board-' . $board, 120);
829
830
		if (!empty($temp))
831
		{
832
			$board_info = $temp;
833
			$board = $board_info['id'];
834
		}
835
	}
836
837
	if (empty($temp))
838
	{
839
		$request = $smcFunc['db_query']('load_board_info', '
840
			SELECT
841
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
842
				b.id_parent, c.name AS cname, COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name,
843
				COALESCE(mem.id_member, 0) AS id_moderator,
844
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
845
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
846
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . '
847
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
848
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . '
849
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
850
				LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = {raw:board_link})
851
				LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
852
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
853
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
854
			WHERE b.id_board = {raw:board_link}',
855
			array(
856
				'current_topic' => $topic,
857
				'board_link' => empty($topic) ? $smcFunc['db_quote']('{int:current_board}', array('current_board' => $board)) : 't.id_board',
858
			)
859
		);
860
		// If there aren't any, skip.
861
		if ($smcFunc['db_num_rows']($request) > 0)
862
		{
863
			$row = $smcFunc['db_fetch_assoc']($request);
864
865
			// Set the current board.
866
			if (!empty($row['id_board']))
867
				$board = $row['id_board'];
868
869
			// Basic operating information. (globals... :/)
870
			$board_info = array(
871
				'id' => $board,
872
				'moderators' => array(),
873
				'moderator_groups' => array(),
874
				'cat' => array(
875
					'id' => $row['id_cat'],
876
					'name' => $row['cname']
877
				),
878
				'name' => $row['bname'],
879
				'description' => $row['description'],
880
				'num_topics' => $row['num_topics'],
881
				'unapproved_topics' => $row['unapproved_topics'],
882
				'unapproved_posts' => $row['unapproved_posts'],
883
				'unapproved_user_topics' => 0,
884
				'parent_boards' => getBoardParents($row['id_parent']),
885
				'parent' => $row['id_parent'],
886
				'child_level' => $row['child_level'],
887
				'theme' => $row['id_theme'],
888
				'override_theme' => !empty($row['override_theme']),
889
				'profile' => $row['id_profile'],
890
				'redirect' => $row['redirect'],
891
				'recycle' => !empty($modSettings['recycle_enable']) && !empty($modSettings['recycle_board']) && $modSettings['recycle_board'] == $board,
892
				'posts_count' => empty($row['count_posts']),
893
				'cur_topic_approved' => empty($topic) || $row['approved'],
894
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
895
			);
896
897
			// Load the membergroups allowed, and check permissions.
898
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
899
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
900
901
			do
902
			{
903 View Code Duplication
				if (!empty($row['id_moderator']))
904
					$board_info['moderators'][$row['id_moderator']] = array(
905
						'id' => $row['id_moderator'],
906
						'name' => $row['real_name'],
907
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
908
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
909
					);
910
911 View Code Duplication
				if (!empty($row['id_moderator_group']))
912
					$board_info['moderator_groups'][$row['id_moderator_group']] = array(
913
						'id' => $row['id_moderator_group'],
914
						'name' => $row['group_name'],
915
						'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
916
						'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
917
					);
918
			}
919
			while ($row = $smcFunc['db_fetch_assoc']($request));
920
921
			// If the board only contains unapproved posts and the user isn't an approver then they can't see any topics.
922
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
923
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
924
			{
925
				// Free the previous result
926
				$smcFunc['db_free_result']($request);
927
928
				// @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...
929
				// @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...
930
				$request = $smcFunc['db_query']('', '
931
					SELECT COUNT(id_topic)
932
					FROM {db_prefix}topics
933
					WHERE id_member_started={int:id_member}
934
						AND approved = {int:unapproved}
935
						AND id_board = {int:board}',
936
					array(
937
						'id_member' => $user_info['id'],
938
						'unapproved' => 0,
939
						'board' => $board,
940
					)
941
				);
942
943
				list ($board_info['unapproved_user_topics']) = $smcFunc['db_fetch_row']($request);
944
			}
945
946
			if (!empty($modSettings['cache_enable']) && (empty($topic) || $modSettings['cache_enable'] >= 3))
947
			{
948
				// @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...
949
				if (!empty($topic))
950
					cache_put_data('topic_board-' . $topic, $board_info, 120);
951
				cache_put_data('board-' . $board, $board_info, 120);
952
			}
953
		}
954
		else
955
		{
956
			// Otherwise the topic is invalid, there are no moderators, etc.
957
			$board_info = array(
958
				'moderators' => array(),
959
				'moderator_groups' => array(),
960
				'error' => 'exist'
961
			);
962
			$topic = null;
963
			$board = 0;
964
		}
965
		$smcFunc['db_free_result']($request);
966
	}
967
968
	if (!empty($topic))
969
		$_GET['board'] = (int) $board;
970
971
	if (!empty($board))
972
	{
973
		// Get this into an array of keys for array_intersect
974
		$moderator_groups = array_keys($board_info['moderator_groups']);
975
976
		// Now check if the user is a moderator.
977
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]) || count(array_intersect($user_info['groups'], $moderator_groups)) != 0;
978
979 View Code Duplication
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
980
			$board_info['error'] = 'access';
981 View Code Duplication
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
982
			$board_info['error'] = 'access';
983
984
		// Build up the linktree.
985
		$context['linktree'] = array_merge(
986
			$context['linktree'],
987
			array(array(
988
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
989
				'name' => $board_info['cat']['name']
990
			)),
991
			array_reverse($board_info['parent_boards']),
992
			array(array(
993
				'url' => $scripturl . '?board=' . $board . '.0',
994
				'name' => $board_info['name']
995
			))
996
		);
997
	}
998
999
	// Set the template contextual information.
1000
	$context['user']['is_mod'] = &$user_info['is_mod'];
1001
	$context['current_topic'] = $topic;
1002
	$context['current_board'] = $board;
1003
1004
	// No posting in redirection boards!
1005
	if (!empty($_REQUEST['action']) && $_REQUEST['action'] == 'post' && !empty($board_info['redirect']))
1006
		$board_info['error'] == 'post_in_redirect';
1007
1008
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
1009
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_mod']))
1010
	{
1011
		// The permissions and theme need loading, just to make sure everything goes smoothly.
1012
		loadPermissions();
1013
		loadTheme();
1014
1015
		$_GET['board'] = '';
1016
		$_GET['topic'] = '';
1017
1018
		// The linktree should not give the game away mate!
1019
		$context['linktree'] = array(
1020
			array(
1021
				'url' => $scripturl,
1022
				'name' => $context['forum_name_html_safe']
1023
			)
1024
		);
1025
1026
		// If it's a prefetching agent or we're requesting an attachment.
1027
		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...
1028
		{
1029
			ob_end_clean();
1030
			header('HTTP/1.1 403 Forbidden');
1031
			die;
1032
		}
1033
		elseif ($board_info['error'] == 'post_in_redirect')
1034
		{
1035
			// Slightly different error message here...
1036
			fatal_lang_error('cannot_post_redirect', false);
1037
		}
1038
		elseif ($user_info['is_guest'])
1039
		{
1040
			loadLanguage('Errors');
1041
			is_not_guest($txt['topic_gone']);
1042
		}
1043
		else
1044
			fatal_lang_error('topic_gone', false);
1045
	}
1046
1047
	if ($user_info['is_mod'])
1048
		$user_info['groups'][] = 3;
1049
}
1050
1051
/**
1052
 * Load this user's permissions.
1053
 */
1054
function loadPermissions()
1055
{
1056
	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...
1057
1058
	if ($user_info['is_admin'])
1059
	{
1060
		banPermissions();
1061
		return;
1062
	}
1063
1064
	if (!empty($modSettings['cache_enable']))
1065
	{
1066
		$cache_groups = $user_info['groups'];
1067
		asort($cache_groups);
1068
		$cache_groups = implode(',', $cache_groups);
1069
		// If it's a spider then cache it different.
1070
		if ($user_info['possibly_robot'])
1071
			$cache_groups .= '-spider';
1072
1073
		if ($modSettings['cache_enable'] >= 2 && !empty($board) && ($temp = cache_get_data('permissions:' . $cache_groups . ':' . $board, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1074
		{
1075
			list ($user_info['permissions']) = $temp;
1076
			banPermissions();
1077
1078
			return;
1079
		}
1080
		elseif (($temp = cache_get_data('permissions:' . $cache_groups, 240)) != null && time() - 240 > $modSettings['settings_updated'])
1081
			list ($user_info['permissions'], $removals) = $temp;
1082
	}
1083
1084
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
1085
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
1086
1087
	if (empty($user_info['permissions']))
1088
	{
1089
		// Get the general permissions.
1090
		$request = $smcFunc['db_query']('', '
1091
			SELECT permission, add_deny
1092
			FROM {db_prefix}permissions
1093
			WHERE id_group IN ({array_int:member_groups})
1094
				' . $spider_restrict,
1095
			array(
1096
				'member_groups' => $user_info['groups'],
1097
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1098
			)
1099
		);
1100
		$removals = array();
1101 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1102
		{
1103
			if (empty($row['add_deny']))
1104
				$removals[] = $row['permission'];
1105
			else
1106
				$user_info['permissions'][] = $row['permission'];
1107
		}
1108
		$smcFunc['db_free_result']($request);
1109
1110
		if (isset($cache_groups))
1111
			cache_put_data('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
1112
	}
1113
1114
	// Get the board permissions.
1115
	if (!empty($board))
1116
	{
1117
		// Make sure the board (if any) has been loaded by loadBoard().
1118
		if (!isset($board_info['profile']))
1119
			fatal_lang_error('no_board');
1120
1121
		$request = $smcFunc['db_query']('', '
1122
			SELECT permission, add_deny
1123
			FROM {db_prefix}board_permissions
1124
			WHERE (id_group IN ({array_int:member_groups})
1125
				' . $spider_restrict . ')
1126
				AND id_profile = {int:id_profile}',
1127
			array(
1128
				'member_groups' => $user_info['groups'],
1129
				'id_profile' => $board_info['profile'],
1130
				'spider_group' => !empty($modSettings['spider_group']) ? $modSettings['spider_group'] : 0,
1131
			)
1132
		);
1133 View Code Duplication
		while ($row = $smcFunc['db_fetch_assoc']($request))
1134
		{
1135
			if (empty($row['add_deny']))
1136
				$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...
1137
			else
1138
				$user_info['permissions'][] = $row['permission'];
1139
		}
1140
		$smcFunc['db_free_result']($request);
1141
	}
1142
1143
	// Remove all the permissions they shouldn't have ;).
1144
	if (!empty($modSettings['permission_enable_deny']))
1145
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
1146
1147
	if (isset($cache_groups) && !empty($board) && $modSettings['cache_enable'] >= 2)
1148
		cache_put_data('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
1149
1150
	// Banned?  Watch, don't touch..
1151
	banPermissions();
1152
1153
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
1154
	if (!$user_info['is_guest'])
1155
	{
1156
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
1157
		{
1158
			require_once($sourcedir . '/Subs-Auth.php');
1159
			rebuildModCache();
1160
		}
1161
		else
1162
			$user_info['mod_cache'] = $_SESSION['mc'];
1163
1164
		// This is a useful phantom permission added to the current user, and only the current user while they are logged in.
1165
		// For example this drastically simplifies certain changes to the profile area.
1166
		$user_info['permissions'][] = 'is_not_guest';
1167
		// And now some backwards compatibility stuff for mods and whatnot that aren't expecting the new permissions.
1168
		$user_info['permissions'][] = 'profile_view_own';
1169
		if (in_array('profile_view', $user_info['permissions']))
1170
			$user_info['permissions'][] = 'profile_view_any';
1171
	}
1172
}
1173
1174
/**
1175
 * Loads an array of users' data by ID or member_name.
1176
 *
1177
 * @param array|string $users An array of users by id or name or a single username/id
1178
 * @param bool $is_name Whether $users contains names
1179
 * @param string $set What kind of data to load (normal, profile, minimal)
1180
 * @return array The ids of the members loaded
1181
 */
1182
function loadMemberData($users, $is_name = false, $set = 'normal')
1183
{
1184
	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...
1185
	global $image_proxy_enabled, $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...
1186
1187
	// Can't just look for no users :P.
1188
	if (empty($users))
1189
		return array();
1190
1191
	// Pass the set value
1192
	$context['loadMemberContext_set'] = $set;
1193
1194
	// Make sure it's an array.
1195
	$users = !is_array($users) ? array($users) : array_unique($users);
1196
	$loaded_ids = array();
1197
1198
	if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1199
	{
1200
		$users = array_values($users);
1201
		for ($i = 0, $n = count($users); $i < $n; $i++)
1202
		{
1203
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1204
			if ($data == null)
1205
				continue;
1206
1207
			$loaded_ids[] = $data['id_member'];
1208
			$user_profile[$data['id_member']] = $data;
1209
			unset($users[$i]);
1210
		}
1211
	}
1212
1213
	// Used by default
1214
	$select_columns = '
1215
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1216
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1217
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1218
			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,
1219
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1220
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1221
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1222
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1223
	$select_tables = '
1224
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1225
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1226
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1227
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1228
1229
	// We add or replace according the the set
1230
	switch ($set)
1231
	{
1232
		case 'normal':
1233
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1234
			break;
1235
		case 'profile':
1236
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1237
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1238
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1239
			break;
1240
		case 'minimal':
1241
			$select_columns = '
1242
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1243
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1244
			$select_tables = '';
1245
			break;
1246
		default:
1247
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1248
	}
1249
1250
	// Allow mods to easily add to the selected member data
1251
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1252
1253
	if (!empty($users))
1254
	{
1255
		// Load the member's data.
1256
		$request = $smcFunc['db_query']('', '
1257
			SELECT' . $select_columns . '
1258
			FROM {db_prefix}members AS mem' . $select_tables . '
1259
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1260
			array(
1261
				'blank_string' => '',
1262
				'users' => $users,
1263
			)
1264
		);
1265
		$new_loaded_ids = array();
1266
		while ($row = $smcFunc['db_fetch_assoc']($request))
1267
		{
1268
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1269
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1270
1271
			// Take care of proxying avatar if required, do this here for maximum reach
1272 View Code Duplication
			if ($image_proxy_enabled && !empty($row['avatar']) && stripos($row['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
1273
				$row['avatar'] = $boardurl . '/proxy.php?request=' . urlencode($row['avatar']) . '&hash=' . md5($row['avatar'] . $image_proxy_secret);
1274
1275
			// Keep track of the member's normal member group
1276
			$row['primary_group'] = $row['member_group'];
1277
1278
			if (isset($row['member_ip']))
1279
				$row['member_ip'] = inet_dtop($row['member_ip']);
1280
			if (isset($row['member_ip2']))
1281
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1282
			$new_loaded_ids[] = $row['id_member'];
1283
			$loaded_ids[] = $row['id_member'];
1284
			$row['options'] = array();
1285
			$user_profile[$row['id_member']] = $row;
1286
		}
1287
		$smcFunc['db_free_result']($request);
1288
	}
1289
1290 View Code Duplication
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1291
	{
1292
		$request = $smcFunc['db_query']('', '
1293
			SELECT id_member, variable, value
1294
			FROM {db_prefix}themes
1295
			WHERE id_member IN ({array_int:loaded_ids})',
1296
			array(
1297
				'loaded_ids' => $new_loaded_ids,
1298
			)
1299
		);
1300
		while ($row = $smcFunc['db_fetch_assoc']($request))
1301
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1302
		$smcFunc['db_free_result']($request);
1303
	}
1304
1305
	$additional_mods = array();
1306
1307
	// Are any of these users in groups assigned to moderate this board?
1308
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1309
	{
1310
		foreach ($loaded_ids as $a_member)
1311
		{
1312
			if (!empty($user_profile[$a_member]['additional_groups']))
1313
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1314
			else
1315
				$groups = array($user_profile[$a_member]['id_group']);
1316
1317
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1318
1319
			if (!empty($temp))
1320
			{
1321
				$additional_mods[] = $a_member;
1322
			}
1323
		}
1324
	}
1325
1326
	if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1327
	{
1328
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1329
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1330
	}
1331
1332
	// Are we loading any moderators?  If so, fix their group data...
1333
	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)
1334
	{
1335 View Code Duplication
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1336
		{
1337
			$request = $smcFunc['db_query']('', '
1338
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1339
				FROM {db_prefix}membergroups
1340
				WHERE id_group = {int:moderator_group}
1341
				LIMIT 1',
1342
				array(
1343
					'moderator_group' => 3,
1344
				)
1345
			);
1346
			$row = $smcFunc['db_fetch_assoc']($request);
1347
			$smcFunc['db_free_result']($request);
1348
1349
			cache_put_data('moderator_group_info', $row, 480);
1350
		}
1351
1352
		foreach ($temp_mods as $id)
1353
		{
1354
			// By popular demand, don't show admins or global moderators as moderators.
1355
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1356
				$user_profile[$id]['member_group'] = $row['member_group'];
1357
1358
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1359
			if (!empty($row['icons']))
1360
				$user_profile[$id]['icons'] = $row['icons'];
1361
			if (!empty($row['member_group_color']))
1362
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1363
		}
1364
	}
1365
1366
	return $loaded_ids;
1367
}
1368
1369
/**
1370
 * Loads the user's basic values... meant for template/theme usage.
1371
 *
1372
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1373
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1374
 * @return boolean Whether or not the data was loaded successfully
1375
 */
1376
function loadMemberContext($user, $display_custom_fields = false)
1377
{
1378
	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...
1379
	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...
1380
	static $dataLoaded = array();
1381
	static $loadedLanguages = array();
1382
1383
	// If this person's data is already loaded, skip it.
1384
	if (isset($dataLoaded[$user]))
1385
		return true;
1386
1387
	// We can't load guests or members not loaded by loadMemberData()!
1388
	if ($user == 0)
1389
		return false;
1390
	if (!isset($user_profile[$user]))
1391
	{
1392
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1393
		return false;
1394
	}
1395
1396
	// Well, it's loaded now anyhow.
1397
	$dataLoaded[$user] = true;
1398
	$profile = $user_profile[$user];
1399
1400
	// Censor everything.
1401
	censorText($profile['signature']);
1402
	censorText($profile['personal_text']);
1403
1404
	// Set things up to be used before hand.
1405
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1406
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1407
1408
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1409
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1410
	// Setup the buddy status here (One whole in_array call saved :P)
1411
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1412
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1413
1414
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1415
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1416
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1417
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1418
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1419
	else //not set, bye bye
1420
		$group_icon_url = '';
1421
1422
	// These minimal values are always loaded
1423
	$memberContext[$user] = array(
1424
		'username' => $profile['member_name'],
1425
		'name' => $profile['real_name'],
1426
		'id' => $profile['id_member'],
1427
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1428
		'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>',
1429
		'email' => $profile['email_address'],
1430
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1431
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1432
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1433
	);
1434
1435
	// If the set isn't minimal then load the monstrous array.
1436
	if ($context['loadMemberContext_set'] != 'minimal')
1437
	{
1438
		// Go the extra mile and load the user's native language name.
1439
		if (empty($loadedLanguages))
1440
			$loadedLanguages = getLanguages();
1441
1442
		$memberContext[$user] += array(
1443
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1444
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1445
			'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>',
1446
			'is_buddy' => $profile['buddy'],
1447
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1448
			'buddies' => $buddy_list,
1449
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1450
			'blurb' => $profile['personal_text'],
1451
			'website' => array(
1452
				'title' => $profile['website_title'],
1453
				'url' => $profile['website_url'],
1454
			),
1455
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1456
			'signature' => $profile['signature'],
1457
			'real_posts' => $profile['posts'],
1458
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1459
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1460
			'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...
1461
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1462
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1463
			'online' => array(
1464
				'is_online' => $profile['is_online'],
1465
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1466
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1467
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1468
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1469
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1470
			),
1471
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1472
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1473
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1474
			'options' => $profile['options'],
1475
			'is_guest' => false,
1476
			'primary_group' => $profile['primary_group'],
1477
			'group' => $profile['member_group'],
1478
			'group_color' => $profile['member_group_color'],
1479
			'group_id' => $profile['id_group'],
1480
			'post_group' => $profile['post_group'],
1481
			'post_group_color' => $profile['post_group_color'],
1482
			'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]),
1483
			'warning' => $profile['warning'],
1484
			'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' : (''))),
1485
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1486
			'custom_fields' => array(),
1487
		);
1488
	}
1489
1490
	// If the set isn't minimal then load their avatar as well.
1491
	if ($context['loadMemberContext_set'] != 'minimal')
1492
	{
1493
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1494
		{
1495
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1496
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1497
			else
1498
				$image = get_gravatar_url($profile['email_address']);
1499
		}
1500
		else
1501
		{
1502
			// So it's stored in the member table?
1503
			if (!empty($profile['avatar']))
1504
			{
1505
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1506
			}
1507
			elseif (!empty($profile['filename']))
1508
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1509
			// Right... no avatar...use the default one
1510
			else
1511
				$image = $modSettings['avatar_url'] . '/default.png';
1512
		}
1513
		if (!empty($image))
1514
			$memberContext[$user]['avatar'] = array(
1515
				'name' => $profile['avatar'],
1516
				'image' => '<img class="avatar" src="' . $image . '" alt="avatar_' . $profile['member_name'] . '">',
1517
				'href' => $image,
1518
				'url' => $image,
1519
			);
1520
	}
1521
1522
	// Are we also loading the members custom fields into context?
1523
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1524
	{
1525
		$memberContext[$user]['custom_fields'] = array();
1526
1527
		if (!isset($context['display_fields']))
1528
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1529
1530
		foreach ($context['display_fields'] as $custom)
1531
		{
1532
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1533
				continue;
1534
1535
			$value = $profile['options'][$custom['col_name']];
1536
1537
			$fieldOptions = array();
1538
			$currentKey = 0;
1539
1540
			// Create a key => value array for multiple options fields
1541 View Code Duplication
			if (!empty($custom['options']))
1542
				foreach ($custom['options'] as $k => $v)
1543
				{
1544
					$fieldOptions[] = $v;
1545
					if (empty($currentKey))
1546
						$currentKey = $v == $value ? $k : 0;
1547
				}
1548
1549
			// BBC?
1550
			if ($custom['bbc'])
1551
				$value = parse_bbc($value);
1552
1553
			// ... 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...
1554
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1555
				$value = $value ? $txt['yes'] : $txt['no'];
1556
1557
			// Enclosing the user input within some other text?
1558
			if (!empty($custom['enclose']))
1559
				$value = strtr($custom['enclose'], array(
1560
					'{SCRIPTURL}' => $scripturl,
1561
					'{IMAGES_URL}' => $settings['images_url'],
1562
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1563
					'{INPUT}' => $value,
1564
					'{KEY}' => $currentKey,
1565
				));
1566
1567
			$memberContext[$user]['custom_fields'][] = array(
1568
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1569
				'col_name' => $custom['col_name'],
1570
				'value' => un_htmlspecialchars($value),
1571
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1572
			);
1573
		}
1574
	}
1575
1576
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1577
	return true;
1578
}
1579
1580
/**
1581
 * Loads the user's custom profile fields
1582
 *
1583
 * @param integer|array $users A single user ID or an array of user IDs
1584
 * @param string|array $params Either a string or an array of strings with profile field names
1585
 * @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...
1586
 */
1587
function loadMemberCustomFields($users, $params)
1588
{
1589
	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...
1590
1591
	// Do not waste my time...
1592
	if (empty($users) || empty($params))
1593
		return false;
1594
1595
	// Make sure it's an array.
1596
	$users = !is_array($users) ? array($users) : array_unique($users);
1597
	$params = !is_array($params) ? array($params) : array_unique($params);
1598
	$return = array();
1599
1600
	$request = $smcFunc['db_query']('', '
1601
		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,
1602
		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
1603
		FROM {db_prefix}themes AS t
1604
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1605
		WHERE id_member IN ({array_int:loaded_ids})
1606
			AND variable IN ({array_string:params})
1607
		ORDER BY field_order',
1608
		array(
1609
			'loaded_ids' => $users,
1610
			'params' => $params,
1611
		)
1612
	);
1613
1614
	while ($row = $smcFunc['db_fetch_assoc']($request))
1615
	{
1616
		$fieldOptions = array();
1617
		$currentKey = 0;
1618
1619
		// Create a key => value array for multiple options fields
1620 View Code Duplication
		if (!empty($row['field_options']))
1621
			foreach (explode(',', $row['field_options']) as $k => $v)
1622
			{
1623
				$fieldOptions[] = $v;
1624
				if (empty($currentKey))
1625
					$currentKey = $v == $row['value'] ? $k : 0;
1626
			}
1627
1628
		// BBC?
1629
		if (!empty($row['bbc']))
1630
			$row['value'] = parse_bbc($row['value']);
1631
1632
		// ... 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...
1633
		elseif (isset($row['type']) && $row['type'] == 'check')
1634
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1635
1636
		// Enclosing the user input within some other text?
1637
		if (!empty($row['enclose']))
1638
			$row['value'] = strtr($row['enclose'], array(
1639
				'{SCRIPTURL}' => $scripturl,
1640
				'{IMAGES_URL}' => $settings['images_url'],
1641
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1642
				'{INPUT}' => un_htmlspecialchars($row['value']),
1643
				'{KEY}' => $currentKey,
1644
			));
1645
1646
		// Send a simple array if there is just 1 param
1647
		if (count($params) == 1)
1648
			$return[$row['id_member']] = $row;
1649
1650
		// More than 1? knock yourself out...
1651
		else
1652
		{
1653
			if (!isset($return[$row['id_member']]))
1654
				$return[$row['id_member']] = array();
1655
1656
			$return[$row['id_member']][$row['variable']] = $row;
1657
		}
1658
	}
1659
1660
	$smcFunc['db_free_result']($request);
1661
1662
	return !empty($return) ? $return : false;
1663
}
1664
1665
/**
1666
 * Loads information about what browser the user is viewing with and places it in $context
1667
 *  - uses the class from {@link Class-BrowserDetect.php}
1668
 */
1669
function detectBrowser()
1670
{
1671
	// Load the current user's browser of choice
1672
	$detector = new browser_detector;
1673
	$detector->detectBrowser();
1674
}
1675
1676
/**
1677
 * Are we using this browser?
1678
 *
1679
 * Wrapper function for detectBrowser
1680
 * @param string $browser The browser we are checking for.
1681
 * @return bool Whether or not the current browser is what we're looking for
1682
*/
1683
function isBrowser($browser)
1684
{
1685
	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...
1686
1687
	// Don't know any browser!
1688
	if (empty($context['browser']))
1689
		detectBrowser();
1690
1691
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1692
}
1693
1694
/**
1695
 * Load a theme, by ID.
1696
 *
1697
 * @param int $id_theme The ID of the theme to load
1698
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1699
 */
1700
function loadTheme($id_theme = 0, $initialize = true)
1701
{
1702
	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...
1703
	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...
1704
	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...
1705
1706
	// The theme was specified by parameter.
1707
	if (!empty($id_theme))
1708
		$id_theme = (int) $id_theme;
1709
	// The theme was specified by REQUEST.
1710 View Code Duplication
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1711
	{
1712
		$id_theme = (int) $_REQUEST['theme'];
1713
		$_SESSION['id_theme'] = $id_theme;
1714
	}
1715
	// The theme was specified by REQUEST... previously.
1716 View Code Duplication
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1717
		$id_theme = (int) $_SESSION['id_theme'];
1718
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1719
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']))
1720
		$id_theme = $user_info['theme'];
1721
	// The theme was specified by the board.
1722
	elseif (!empty($board_info['theme']))
1723
		$id_theme = $board_info['theme'];
1724
	// The theme is the forum's default.
1725
	else
1726
		$id_theme = $modSettings['theme_guests'];
1727
1728
	// We already load the basic stuff?
1729
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme )
1730
	{
1731
		// Verify the id_theme... no foul play.
1732
		// Always allow the board specific theme, if they are overriding.
1733
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1734
			$id_theme = $board_info['theme'];
1735
		// If they have specified a particular theme to use with SSI allow it to be used.
1736
		elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1737
			$id_theme = (int) $id_theme;
1738
		elseif (!empty($modSettings['enableThemes']) && !allowedTo('admin_forum'))
1739
		{
1740
			$themes = explode(',', $modSettings['enableThemes']);
1741
			if (!in_array($id_theme, $themes))
1742
				$id_theme = $modSettings['theme_guests'];
1743
			else
1744
				$id_theme = (int) $id_theme;
1745
		}
1746
		else
1747
			$id_theme = (int) $id_theme;
1748
1749
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1750
1751
		// Disable image proxy if we don't have SSL enabled
1752
		if (empty($modSettings['force_ssl']))
1753
			$image_proxy_enabled = false;
1754
1755
		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'])
1756
		{
1757
			$themeData = $temp;
1758
			$flag = true;
1759
		}
1760
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1761
			$themeData = $temp + array($member => array());
1762
		else
1763
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1764
1765
		if (empty($flag))
1766
		{
1767
			// Load variables from the current or default theme, global or this user's.
1768
			$result = $smcFunc['db_query']('', '
1769
				SELECT variable, value, id_member, id_theme
1770
				FROM {db_prefix}themes
1771
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1772
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1773
				array(
1774
					'id_theme' => $id_theme,
1775
					'id_member' => $member,
1776
				)
1777
			);
1778
			// Pick between $settings and $options depending on whose data it is.
1779
			while ($row = $smcFunc['db_fetch_assoc']($result))
1780
			{
1781
				// There are just things we shouldn't be able to change as members.
1782
				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')))
1783
					continue;
1784
1785
				// If this is the theme_dir of the default theme, store it.
1786
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1787
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1788
1789
				// If this isn't set yet, is a theme option, or is not the default theme..
1790
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1791
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1792
			}
1793
			$smcFunc['db_free_result']($result);
1794
1795
			if (!empty($themeData[-1]))
1796
				foreach ($themeData[-1] as $k => $v)
1797
				{
1798
					if (!isset($themeData[$member][$k]))
1799
						$themeData[$member][$k] = $v;
1800
				}
1801
1802
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1803
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1804
			// Only if we didn't already load that part of the cache...
1805
			elseif (!isset($temp))
1806
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1807
		}
1808
1809
		$settings = $themeData[0];
1810
		$options = $themeData[$member];
1811
1812
		$settings['theme_id'] = $id_theme;
1813
1814
		$settings['actual_theme_url'] = $settings['theme_url'];
1815
		$settings['actual_images_url'] = $settings['images_url'];
1816
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1817
1818
		$settings['template_dirs'] = array();
1819
		// This theme first.
1820
		$settings['template_dirs'][] = $settings['theme_dir'];
1821
1822
		// Based on theme (if there is one).
1823
		if (!empty($settings['base_theme_dir']))
1824
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1825
1826
		// Lastly the default theme.
1827 View Code Duplication
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1828
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1829
	}
1830
1831
1832
	if (!$initialize)
1833
		return;
1834
1835
	// Check to see if we're forcing SSL
1836
	if (!empty($modSettings['force_ssl']) && empty($maintenance) &&
1837
		!httpsOn() && SMF != 'SSI')
1838
	{
1839
		if (isset($_GET['sslRedirect']))
1840
		{
1841
			loadLanguage('Errors');
1842
			fatal_lang_error($txt['login_ssl_required']);
1843
		}
1844
1845
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
1846
	}
1847
1848
	// Check to see if they're accessing it from the wrong place.
1849
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1850
	{
1851
		$detected_url = httpsOn() ? 'https://' : 'http://';
1852
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1853
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1854
		if ($temp != '/')
1855
			$detected_url .= $temp;
1856
	}
1857
	if (isset($detected_url) && $detected_url != $boardurl)
1858
	{
1859
		// Try #1 - check if it's in a list of alias addresses.
1860
		if (!empty($modSettings['forum_alias_urls']))
1861
		{
1862
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1863
1864
			foreach ($aliases as $alias)
1865
			{
1866
				// Rip off all the boring parts, spaces, etc.
1867
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1868
					$do_fix = true;
1869
			}
1870
		}
1871
1872
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1873
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
1874
		{
1875
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1876
			if (empty($_GET))
1877
				redirectexit('wwwRedirect');
1878
			else
1879
			{
1880
				$k = key($_GET);
1881
				$v = current($_GET);
1882
1883
				if ($k != 'wwwRedirect')
1884
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1885
			}
1886
		}
1887
1888
		// #3 is just a check for SSL...
1889
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1890
			$do_fix = true;
1891
1892
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1893
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1894
		{
1895
			// Caching is good ;).
1896
			$oldurl = $boardurl;
1897
1898
			// Fix $boardurl and $scripturl.
1899
			$boardurl = $detected_url;
1900
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1901
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1902
1903
			// Fix the theme urls...
1904
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1905
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1906
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1907
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1908
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1909
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1910
1911
			// And just a few mod settings :).
1912
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1913
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1914
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
1915
1916
			// Clean up after loadBoard().
1917
			if (isset($board_info['moderators']))
1918
			{
1919
				foreach ($board_info['moderators'] as $k => $dummy)
1920
				{
1921
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1922
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1923
				}
1924
			}
1925
			foreach ($context['linktree'] as $k => $dummy)
1926
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1927
		}
1928
	}
1929
	// Set up the contextual user array.
1930
	if (!empty($user_info))
1931
	{
1932
		$context['user'] = array(
1933
			'id' => $user_info['id'],
1934
			'is_logged' => !$user_info['is_guest'],
1935
			'is_guest' => &$user_info['is_guest'],
1936
			'is_admin' => &$user_info['is_admin'],
1937
			'is_mod' => &$user_info['is_mod'],
1938
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1939
			'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'])))),
1940
			'name' => $user_info['username'],
1941
			'language' => $user_info['language'],
1942
			'email' => $user_info['email'],
1943
			'ignoreusers' => $user_info['ignoreusers'],
1944
		);
1945
		if (!$context['user']['is_guest'])
1946
			$context['user']['name'] = $user_info['name'];
1947 View Code Duplication
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1948
			$context['user']['name'] = $txt['guest_title'];
1949
1950
		// Determine the current smiley set.
1951
		$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'];
1952
		$context['user']['smiley_set'] = $user_info['smiley_set'];
1953
	}
1954
	else
1955
	{
1956
		// What to do when there is no $user_info (e.g., an error very early in the login process)
1957
		$context['user'] = array(
1958
			'id' => -1,
1959
			'is_logged' => false,
1960
			'is_guest' => true,
1961
			'is_mod' => false,
1962
			'can_mod' => false,
1963
			'name' => $txt['guest_title'],
1964
			'language' => $language,
1965
			'email' => '',
1966
			'ignoreusers' => array(),
1967
		);
1968
		// Note we should stuff $user_info with some guest values also...
1969
		$user_info = array(
1970
			'id' => 0,
1971
			'is_guest' => true,
1972
			'is_admin' => false,
1973
			'is_mod' => false,
1974
			'username' => $txt['guest_title'],
1975
			'language' => $language,
1976
			'email' => '',
1977
			'smiley_set' => '',
1978
			'permissions' => array(),
1979
			'groups' => array(),
1980
			'ignoreusers' => array(),
1981
			'possibly_robot' => true,
1982
			'time_offset' => 0,
1983
			'time_format' => $modSettings['time_format'],
1984
		);
1985
	}
1986
1987
	// Some basic information...
1988
	if (!isset($context['html_headers']))
1989
		$context['html_headers'] = '';
1990
	if (!isset($context['javascript_files']))
1991
		$context['javascript_files'] = array();
1992
	if (!isset($context['css_files']))
1993
		$context['css_files'] = array();
1994
	if (!isset($context['css_header']))
1995
		$context['css_header'] = array();
1996
	if (!isset($context['javascript_inline']))
1997
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
1998
	if (!isset($context['javascript_vars']))
1999
		$context['javascript_vars'] = array();
2000
2001
	$context['login_url'] =  $scripturl . '?action=login2';
2002
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2003
	$context['session_var'] = $_SESSION['session_var'];
2004
	$context['session_id'] = $_SESSION['session_value'];
2005
	$context['forum_name'] = $mbname;
2006
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2007
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2008
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2009
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2010
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2011
	if (isset($modSettings['load_average']))
2012
		$context['load_average'] = $modSettings['load_average'];
2013
2014
	// Detect the browser. This is separated out because it's also used in attachment downloads
2015
	detectBrowser();
2016
2017
	// Set the top level linktree up.
2018
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2019
	if (empty($context['linktree']))
2020
		$context['linktree'] = array();
2021
	array_unshift($context['linktree'], array(
2022
		'url' => $scripturl,
2023
		'name' => $context['forum_name_html_safe']
2024
	));
2025
2026
	// This allows sticking some HTML on the page output - useful for controls.
2027
	$context['insert_after_template'] = '';
2028
2029
	if (!isset($txt))
2030
		$txt = array();
2031
2032
	$simpleActions = array(
2033
		'findmember',
2034
		'helpadmin',
2035
		'printpage',
2036
		'spellcheck',
2037
	);
2038
2039
	// Parent action => array of areas
2040
	$simpleAreas = array(
2041
		'profile' => array('popup', 'alerts_popup',),
2042
	);
2043
2044
	// Parent action => array of subactions
2045
	$simpleSubActions = array(
2046
		'pm' => array('popup',),
2047
		'signup' => array('usernamecheck'),
2048
	);
2049
2050
	// Extra params like ;preview ;js, etc.
2051
	$extraParams = array(
2052
		'preview',
2053
		'splitjs',
2054
	);
2055
2056
	// Actions that specifically uses XML output.
2057
	$xmlActions = array(
2058
		'quotefast',
2059
		'jsmodify',
2060
		'xmlhttp',
2061
		'post2',
2062
		'suggest',
2063
		'stats',
2064
		'notifytopic',
2065
		'notifyboard',
2066
	);
2067
2068
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2069
2070
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2071
	(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2072
	(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2073
2074
	// See if theres any extra param to check.
2075
	$requiresXML = false;
2076
	foreach ($extraParams as $key => $extra)
2077
		if (isset($_REQUEST[$extra]))
2078
			$requiresXML = true;
2079
2080
	// Output is fully XML, so no need for the index template.
2081
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2082
	{
2083
		loadLanguage('index+Modifications');
2084
		loadTemplate('Xml');
2085
		$context['template_layers'] = array();
2086
	}
2087
2088
	// These actions don't require the index template at all.
2089
	elseif (!empty($context['simple_action']))
2090
	{
2091
		loadLanguage('index+Modifications');
2092
		$context['template_layers'] = array();
2093
	}
2094
2095
	else
2096
	{
2097
		// Custom templates to load, or just default?
2098
		if (isset($settings['theme_templates']))
2099
			$templates = explode(',', $settings['theme_templates']);
2100
		else
2101
			$templates = array('index');
2102
2103
		// Load each template...
2104
		foreach ($templates as $template)
2105
			loadTemplate($template);
2106
2107
		// ...and attempt to load their associated language files.
2108
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2109
		loadLanguage($required_files, '', false);
2110
2111
		// Custom template layers?
2112
		if (isset($settings['theme_layers']))
2113
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2114
		else
2115
			$context['template_layers'] = array('html', 'body');
2116
	}
2117
2118
	// Initialize the theme.
2119
	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...
2120
2121
	// Allow overriding the board wide time/number formats.
2122
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2123
		$user_info['time_format'] = $txt['time_format'];
2124
2125
	// Set the character set from the template.
2126
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2127
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2128
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2129
2130
	// Guests may still need a name.
2131 View Code Duplication
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2132
		$context['user']['name'] = $txt['guest_title'];
2133
2134
	// Any theme-related strings that need to be loaded?
2135
	if (!empty($settings['require_theme_strings']))
2136
		loadLanguage('ThemeStrings', '', false);
2137
2138
	// Make a special URL for the language.
2139
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2140
2141
	// And of course, let's load the default CSS file.
2142
	loadCSSFile('index.css', array('minimize' => true), 'smf_index');
2143
2144
	// Here is my luvly Responsive CSS
2145
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true), 'smf_responsive');
2146
2147
	if ($context['right_to_left'])
2148
		loadCSSFile('rtl.css', array(), 'smf_rtl');
2149
2150
	// We allow theme variants, because we're cool.
2151
	$context['theme_variant'] = '';
2152
	$context['theme_variant_url'] = '';
2153
	if (!empty($settings['theme_variants']))
2154
	{
2155
		// Overriding - for previews and that ilk.
2156
		if (!empty($_REQUEST['variant']))
2157
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2158
		// User selection?
2159
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2160
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2161
		// If not a user variant, select the default.
2162
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2163
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2164
2165
		// Do this to keep things easier in the templates.
2166
		$context['theme_variant'] = '_' . $context['theme_variant'];
2167
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2168
2169
		if (!empty($context['theme_variant']))
2170
		{
2171
			loadCSSFile('index' . $context['theme_variant'] . '.css', array(), 'smf_index' . $context['theme_variant']);
2172
			if ($context['right_to_left'])
2173
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array(), 'smf_rtl' . $context['theme_variant']);
2174
		}
2175
	}
2176
2177
	// Let's be compatible with old themes!
2178
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2179
		$context['template_layers'] = array('main');
2180
2181
	$context['tabindex'] = 1;
2182
2183
	// Compatibility.
2184
	if (!isset($settings['theme_version']))
2185
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2186
2187
	// Default JS variables for use in every theme
2188
	$context['javascript_vars'] = array(
2189
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2190
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2191
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2192
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2193
		'smf_scripturl' => '"' . $scripturl . '"',
2194
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2195
		'smf_charset' => '"' . $context['character_set'] . '"',
2196
		'smf_session_id' => '"' . $context['session_id'] . '"',
2197
		'smf_session_var' => '"' . $context['session_var'] . '"',
2198
		'smf_member_id' => $context['user']['id'],
2199
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2200
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2201
	);
2202
2203
	// Add the JQuery library to the list of files to load.
2204
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2205
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2206
2207
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2208
		loadJavaScriptFile('jquery-3.2.1.min.js', array('seed' => false), 'smf_jquery');
2209
2210
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2211
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2212
2213
	// Auto loading? template_javascript() will take care of the local half of this.
2214
	else
2215
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2216
2217
	// Queue our JQuery plugins!
2218
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2219
	if (!$user_info['is_guest'])
2220
	{
2221
		loadJavaScriptFile('jquery.custom-scrollbar.js', array(), 'smf_jquery_scrollbar');
2222
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2223
	}
2224
2225
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2226
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2227
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2228
2229
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2230
	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())
2231
	{
2232
		if (isBrowser('possibly_robot'))
2233
		{
2234
			// @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...
2235
			require_once($sourcedir . '/ScheduledTasks.php');
2236
2237
			// What to do, what to do?!
2238
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2239
				AutoTask();
2240
			else
2241
				ReduceMailQueue();
2242
		}
2243
		else
2244
		{
2245
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2246
			$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...
2247
2248
			addInlineJavaScript('
2249
		function smfAutoTask()
2250
		{
2251
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2252
		}
2253
		window.setTimeout("smfAutoTask();", 1);');
2254
		}
2255
	}
2256
2257
	// And we should probably trigger the cron too.
2258
	if (empty($modSettings['cron_is_real_cron']))
2259
	{
2260
		$ts = time();
2261
		$ts -= $ts % 15;
2262
		addInlineJavaScript('
2263
	function triggerCron()
2264
	{
2265
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2266
	}
2267
	window.setTimeout(triggerCron, 1);', true);
2268
	}
2269
2270
	// Filter out the restricted boards from the linktree
2271
	if (!$user_info['is_admin'] && !empty($board))
2272
	{
2273
		foreach ($context['linktree'] as $k => $element)
2274
		{
2275
			if (!empty($element['groups']) &&
2276
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2277
				(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2278
			{
2279
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2280
				$context['linktree'][$k]['extra_before'] = '<i>';
2281
				$context['linktree'][$k]['extra_after'] = '</i>';
2282
				unset($context['linktree'][$k]['url']);
2283
			}
2284
		}
2285
	}
2286
2287
	// Any files to include at this point?
2288 View Code Duplication
	if (!empty($modSettings['integrate_theme_include']))
2289
	{
2290
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2291
		foreach ($theme_includes as $include)
2292
		{
2293
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2294
			if (file_exists($include))
2295
				require_once($include);
2296
		}
2297
	}
2298
2299
	// Call load theme integration functions.
2300
	call_integration_hook('integrate_load_theme');
2301
2302
	// We are ready to go.
2303
	$context['theme_loaded'] = true;
2304
}
2305
2306
/**
2307
 * Load a template - if the theme doesn't include it, use the default.
2308
 * What this function does:
2309
 *  - loads a template file with the name template_name from the current, default, or base theme.
2310
 *  - detects a wrong default theme directory and tries to work around it.
2311
 *
2312
 * @uses the template_include() function to include the file.
2313
 * @param string $template_name The name of the template to load
2314
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2315
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2316
 * @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...
2317
 */
2318
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2319
{
2320
	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...
2321
2322
	// Do any style sheets first, cause we're easy with those.
2323
	if (!empty($style_sheets))
2324
	{
2325
		if (!is_array($style_sheets))
2326
			$style_sheets = array($style_sheets);
2327
2328
		foreach ($style_sheets as $sheet)
2329
			loadCSSFile($sheet . '.css', array(), $sheet);
2330
	}
2331
2332
	// No template to load?
2333
	if ($template_name === false)
2334
		return true;
2335
2336
	$loaded = false;
2337
	foreach ($settings['template_dirs'] as $template_dir)
2338
	{
2339
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2340
		{
2341
			$loaded = true;
2342
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2343
			break;
2344
		}
2345
	}
2346
2347
	if ($loaded)
2348
	{
2349
		if ($db_show_debug === true)
2350
			$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 2337. 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...
2351
2352
		// If they have specified an initialization function for this template, go ahead and call it now.
2353
		if (function_exists('template_' . $template_name . '_init'))
2354
			call_user_func('template_' . $template_name . '_init');
2355
	}
2356
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2357
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2358
	{
2359
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2360
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2361
2362 View Code Duplication
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2363
		{
2364
			loadLanguage('Errors');
2365
			echo '
2366
<div class="alert errorbox">
2367
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2368
</div>';
2369
		}
2370
2371
		loadTemplate($template_name);
2372
	}
2373
	// Cause an error otherwise.
2374
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2375
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2376
	elseif ($fatal)
2377
		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'));
2378
	else
2379
		return false;
2380
}
2381
2382
/**
2383
 * Load a sub-template.
2384
 * What it does:
2385
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2386
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2387
 *	for debugging purposes.
2388
 *
2389
 * @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...
2390
 *
2391
 * @param string $sub_template_name The name of the sub-template to load
2392
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2393
 */
2394
function loadSubTemplate($sub_template_name, $fatal = false)
2395
{
2396
	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...
2397
2398
	if ($db_show_debug === true)
2399
		$context['debug']['sub_templates'][] = $sub_template_name;
2400
2401
	// Figure out what the template function is named.
2402
	$theme_function = 'template_' . $sub_template_name;
2403
	if (function_exists($theme_function))
2404
		$theme_function();
2405
	elseif ($fatal === false)
2406
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2407
	elseif ($fatal !== 'ignore')
2408
		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'));
2409
2410
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2411
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2412
	{
2413
		echo '
2414
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2415
	}
2416
}
2417
2418
/**
2419
 * Add a CSS file for output later
2420
 *
2421
 * @param string $fileName The name of the file to load
2422
 * @param array $params An array of parameters
2423
 * Keys are the following:
2424
 * 	- ['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
2425
 * 	- ['default_theme'] (true/false): force use of default theme url
2426
 * 	- ['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
2427
 *  - ['validate'] (true/false): if true script will validate the local file exists
2428
 *  - ['rtl'] (string): additional file to load in RTL mode
2429
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2430
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2431
 * @param string $id An ID to stick on the end of the filename for caching purposes
2432
 */
2433
function loadCSSFile($fileName, $params = array(), $id = '')
2434
{
2435
	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...
2436
2437
	$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']) : '');
2438
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2439
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2440
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2441
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2442
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2443
2444
	// If this is an external file, automatically set this to false.
2445
	if (!empty($params['external']))
2446
		$params['minimize'] = false;
2447
2448
	// Account for shorthand like admin.css?alp21 filenames
2449
	$has_seed = strpos($fileName, '.css?');
2450
	$id = empty($id) ? strtr(basename(str_replace('.css', '', $fileName)), '?', '_') : $id;
2451
2452
	// Is this a local file?
2453 View Code Duplication
	if (empty($params['external']))
2454
	{
2455
		// Are we validating the the file exists?
2456
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/css/' . $fileName))
2457
		{
2458
			// Maybe the default theme has it?
2459
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/css/' . $fileName))
2460
			{
2461
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2462
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2463
			}
2464
2465
			else
2466
				$fileUrl = false;
2467
		}
2468
2469
		else
2470
		{
2471
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2472
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2473
		}
2474
	}
2475
2476
	// An external file doesn't have a filepath. Mock one for simplicity.
2477
	else
2478
	{
2479
		$fileUrl = $fileName;
2480
		$filePath = $fileName;
2481
	}
2482
2483
	// Add it to the array for use in the template
2484 View Code Duplication
	if (!empty($fileName))
2485
		$context['css_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
0 ignored issues
show
Bug introduced by
The variable $filePath 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...
2486
2487
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2488
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2489
}
2490
2491
/**
2492
 * Add a block of inline css code to be executed later
2493
 *
2494
 * - only use this if you have to, generally external css files are better, but for very small changes
2495
 *   or for scripts that require help from PHP/whatever, this can be useful.
2496
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2497
 *
2498
 * @param string $css Some css code
2499
 * @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...
2500
 */
2501
function addInlineCss($css)
2502
{
2503
	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...
2504
2505
	// Gotta add something...
2506
	if (empty($css))
2507
		return false;
2508
2509
	$context['css_header'][] = $css;
2510
}
2511
2512
/**
2513
 * Add a Javascript file for output later
2514
 *
2515
 * @param string $fileName The name of the file to load
2516
 * @param array $params An array of parameter info
2517
 * Keys are the following:
2518
 * 	- ['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
2519
 * 	- ['default_theme'] (true/false): force use of default theme url
2520
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2521
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2522
 *	default theme if not found in the current theme
2523
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2524
 *  - ['validate'] (true/false): if true script will validate the local file exists
2525
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2526
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2527
 *
2528
 * @param string $id An ID to stick on the end of the filename
2529
 */
2530
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2531
{
2532
	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...
2533
2534
	$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']) : '');
2535
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2536
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2537
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2538
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2539
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2540
2541
	// If this is an external file, automatically set this to false.
2542
	if (!empty($params['external']))
2543
		$params['minimize'] = false;
2544
2545
	// Account for shorthand like admin.js?alp21 filenames
2546
	$has_seed = strpos($fileName, '.js?');
2547
	$id = empty($id) ? strtr(basename(str_replace('.js', '', $fileName)), '?', '_') : $id;
2548
2549
	// Is this a local file?
2550 View Code Duplication
	if (empty($params['external']))
2551
	{
2552
		// Are we validating it exists on disk?
2553
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/scripts/' . $fileName))
2554
		{
2555
			// Can't find it in this theme, how about the default?
2556
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/scripts/' . $fileName))
2557
			{
2558
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2559
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2560
			}
2561
2562
			else
2563
			{
2564
				$fileUrl = false;
2565
				$filePath = false;
2566
			}
2567
		}
2568
2569
		else
2570
		{
2571
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2572
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2573
		}
2574
	}
2575
2576
	// An external file doesn't have a filepath. Mock one for simplicity.
2577
	else
2578
	{
2579
		$fileUrl = $fileName;
2580
		$filePath = $fileName;
2581
	}
2582
2583
	// Add it to the array for use in the template
2584 View Code Duplication
	if (!empty($fileName))
2585
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2586
}
2587
2588
/**
2589
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2590
 * Cleaner and easier (for modders) than to use the function below.
2591
 *
2592
 * @param string $key The key for this variable
2593
 * @param string $value The value
2594
 * @param bool $escape Whether or not to escape the value
2595
 */
2596
function addJavaScriptVar($key, $value, $escape = false)
2597
{
2598
	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...
2599
2600
	if (!empty($key) && (!empty($value) || $value === '0'))
2601
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2602
}
2603
2604
/**
2605
 * Add a block of inline Javascript code to be executed later
2606
 *
2607
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2608
 *   or for scripts that require help from PHP/whatever, this can be useful.
2609
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2610
 *
2611
 * @param string $javascript Some JS code
2612
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2613
 * @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...
2614
 */
2615
function addInlineJavaScript($javascript, $defer = false)
2616
{
2617
	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...
2618
2619
	if (empty($javascript))
2620
		return false;
2621
2622
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2623
}
2624
2625
/**
2626
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2627
 *
2628
 * @param string $template_name The name of a template file
2629
 * @param string $lang A specific language to load this file from
2630
 * @param bool $fatal Whether to die with an error if it can't be loaded
2631
 * @param bool $force_reload Whether to load the file again if it's already loaded
2632
 * @return string The language actually loaded.
2633
 */
2634
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2635
{
2636
	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...
2637
	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...
2638
	static $already_loaded = array();
2639
2640
	// Default to the user's language.
2641
	if ($lang == '')
2642
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2643
2644
	// Do we want the English version of language file as fallback?
2645
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2646
		loadLanguage($template_name, 'english', false);
2647
2648
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2649
		return $lang;
2650
2651
	// Make sure we have $settings - if not we're in trouble and need to find it!
2652
	if (empty($settings['default_theme_dir']))
2653
	{
2654
		require_once($sourcedir . '/ScheduledTasks.php');
2655
		loadEssentialThemeData();
2656
	}
2657
2658
	// What theme are we in?
2659
	$theme_name = basename($settings['theme_url']);
2660
	if (empty($theme_name))
2661
		$theme_name = 'unknown';
2662
2663
	// For each file open it up and write it out!
2664
	foreach (explode('+', $template_name) as $template)
2665
	{
2666
		// Obviously, the current theme is most important to check.
2667
		$attempts = array(
2668
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2669
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2670
		);
2671
2672
		// Do we have a base theme to worry about?
2673
		if (isset($settings['base_theme_dir']))
2674
		{
2675
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2676
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2677
		}
2678
2679
		// Fall back on the default theme if necessary.
2680
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2681
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2682
2683
		// Fall back on the English language if none of the preferred languages can be found.
2684
		if (!in_array('english', array($lang, $language)))
2685
		{
2686
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2687
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2688
		}
2689
2690
		// Try to find the language file.
2691
		$found = false;
2692
		foreach ($attempts as $k => $file)
2693
		{
2694
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2695
			{
2696
				// Include it!
2697
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2698
2699
				// Note that we found it.
2700
				$found = true;
2701
2702
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2703
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2704
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2705
2706
				break;
2707
			}
2708
		}
2709
2710
		// That couldn't be found!  Log the error, but *try* to continue normally.
2711
		if (!$found && $fatal)
2712
		{
2713
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2714
			break;
2715
		}
2716
2717
		// For the sake of backward compatibility
2718
		if (!empty($txt['emails']))
2719
		{
2720
			foreach ($txt['emails'] as $key => $value)
2721
			{
2722
				$txt[$key . '_subject'] = $value['subject'];
2723
				$txt[$key . '_body'] = $value['body'];
2724
			}
2725
			$txt['emails'] = array();
2726
		}
2727
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2728
		// empty in a normal install. If it isn't it means the forum is using
2729
		// something "old" (it may be the translation, it may be a mod) and this
2730
		// code (like the piece above) takes care of converting it to the new format
2731
		if (!empty($birthdayEmails))
2732
		{
2733
			foreach ($birthdayEmails as $key => $value)
2734
			{
2735
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2736
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2737
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2738
			}
2739
			$birthdayEmails = array();
2740
		}
2741
	}
2742
2743
	// Keep track of what we're up to soldier.
2744
	if ($db_show_debug === true)
2745
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2746
2747
	// Remember what we have loaded, and in which language.
2748
	$already_loaded[$template_name] = $lang;
2749
2750
	// Return the language actually loaded.
2751
	return $lang;
2752
}
2753
2754
/**
2755
 * Get all parent boards (requires first parent as parameter)
2756
 * It finds all the parents of id_parent, and that board itself.
2757
 * Additionally, it detects the moderators of said boards.
2758
 *
2759
 * @param int $id_parent The ID of the parent board
2760
 * @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...
2761
 */
2762
function getBoardParents($id_parent)
2763
{
2764
	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...
2765
2766
	// First check if we have this cached already.
2767
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
2768
	{
2769
		$boards = array();
2770
		$original_parent = $id_parent;
2771
2772
		// Loop while the parent is non-zero.
2773
		while ($id_parent != 0)
2774
		{
2775
			$result = $smcFunc['db_query']('', '
2776
				SELECT
2777
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2778
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2779
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2780
				FROM {db_prefix}boards AS b
2781
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2782
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2783
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2784
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2785
				WHERE b.id_board = {int:board_parent}',
2786
				array(
2787
					'board_parent' => $id_parent,
2788
				)
2789
			);
2790
			// In the EXTREMELY unlikely event this happens, give an error message.
2791
			if ($smcFunc['db_num_rows']($result) == 0)
2792
				fatal_lang_error('parent_not_found', 'critical');
2793
			while ($row = $smcFunc['db_fetch_assoc']($result))
2794
			{
2795
				if (!isset($boards[$row['id_board']]))
2796
				{
2797
					$id_parent = $row['id_parent'];
2798
					$boards[$row['id_board']] = array(
2799
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2800
						'name' => $row['name'],
2801
						'level' => $row['child_level'],
2802
						'groups' => explode(',', $row['member_groups']),
2803
						'deny_groups' => explode(',', $row['deny_member_groups']),
2804
						'moderators' => array(),
2805
						'moderator_groups' => array()
2806
					);
2807
				}
2808
				// If a moderator exists for this board, add that moderator for all children too.
2809 View Code Duplication
				if (!empty($row['id_moderator']))
2810
					foreach ($boards as $id => $dummy)
2811
					{
2812
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2813
							'id' => $row['id_moderator'],
2814
							'name' => $row['real_name'],
2815
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2816
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2817
						);
2818
					}
2819
2820
				// If a moderator group exists for this board, add that moderator group for all children too
2821 View Code Duplication
				if (!empty($row['id_moderator_group']))
2822
					foreach ($boards as $id => $dummy)
2823
					{
2824
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
2825
							'id' => $row['id_moderator_group'],
2826
							'name' => $row['group_name'],
2827
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
2828
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
2829
						);
2830
					}
2831
			}
2832
			$smcFunc['db_free_result']($result);
2833
		}
2834
2835
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
2836
	}
2837
2838
	return $boards;
2839
}
2840
2841
/**
2842
 * Attempt to reload our known languages.
2843
 * It will try to choose only utf8 or non-utf8 languages.
2844
 *
2845
 * @param bool $use_cache Whether or not to use the cache
2846
 * @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...
2847
 */
2848
function getLanguages($use_cache = true)
2849
{
2850
	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...
2851
2852
	// Either we don't use the cache, or its expired.
2853
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null)
2854
	{
2855
		// If we don't have our ucwords function defined yet, let's load the settings data.
2856
		if (empty($smcFunc['ucwords']))
2857
			reloadSettings();
2858
2859
		// If we don't have our theme information yet, let's get it.
2860
		if (empty($settings['default_theme_dir']))
2861
			loadTheme(0, false);
2862
2863
		// Default language directories to try.
2864
		$language_directories = array(
2865
			$settings['default_theme_dir'] . '/languages',
2866
		);
2867
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
2868
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
2869
2870
		// We possibly have a base theme directory.
2871
		if (!empty($settings['base_theme_dir']))
2872
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2873
2874
		// Remove any duplicates.
2875
		$language_directories = array_unique($language_directories);
2876
2877
		// Get a list of languages.
2878
		$langList = !empty($modSettings['langList']) ? $smcFunc['json_decode']($modSettings['langList'], true) : array();
2879
		$langList = is_array($langList) ? $langList : false;
2880
2881
		$catchLang = array();
2882
2883
		foreach ($language_directories as $language_dir)
2884
		{
2885
			// Can't look in here... doesn't exist!
2886
			if (!file_exists($language_dir))
2887
				continue;
2888
2889
			$dir = dir($language_dir);
2890
			while ($entry = $dir->read())
2891
			{
2892
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
2893
				if (!preg_match('~^index\.(.+[^-utf8])\.php$~', $entry, $matches))
2894
					continue;
2895
2896
				if (!empty($langList) && !empty($langList[$matches[1]]))
2897
					$langName = $langList[$matches[1]];
2898
2899
				else
2900
				{
2901
					$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
2902
2903
					// Get the line we need.
2904
					$fp = @fopen($language_dir . '/' . $entry);
2905
2906
					// Yay!
2907
					if ($fp)
2908
					{
2909
						while (($line = fgets($fp)) !== false)
2910
						{
2911
							preg_match('~\$txt\[\'native_name\'\] = \'(.+)\'\;~', $line, $matchNative);
2912
2913
							// Set the language's name.
2914
							if (!empty($matchNative) && !empty($matchNative[1]))
2915
							{
2916
								$langName = un_htmlspecialchars($matchNative[1]);
2917
								break;
2918
							}
2919
						}
2920
2921
						fclose($fp);
2922
					}
2923
2924
					// Catch the language name.
2925
					$catchLang[$matches[1]] = $langName;
2926
				}
2927
2928
				// Build this language entry.
2929
				$context['languages'][$matches[1]] = array(
2930
					'name' => $langName,
2931
					'selected' => false,
2932
					'filename' => $matches[1],
2933
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
2934
				);
2935
			}
2936
			$dir->close();
2937
		}
2938
2939
		// Do we need to store the lang list?
2940
		if (empty($langList))
2941
			updateSettings(array('langList' => $smcFunc['json_encode']($catchLang)));
2942
2943
		// Let's cash in on this deal.
2944 View Code Duplication
		if (!empty($modSettings['cache_enable']))
2945
			cache_put_data('known_languages', $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
2946
	}
2947
2948
	return $context['languages'];
2949
}
2950
2951
/**
2952
 * Replace all vulgar words with respective proper words. (substring or whole words..)
2953
 * What this function does:
2954
 *  - it censors the passed string.
2955
 *  - if the theme setting allow_no_censored is on, and the theme option
2956
 *	show_no_censored is enabled, does not censor, unless force is also set.
2957
 *  - it caches the list of censored words to reduce parsing.
2958
 *
2959
 * @param string &$text The text to censor
2960
 * @param bool $force Whether to censor the text regardless of settings
2961
 * @return string The censored text
2962
 */
2963
function censorText(&$text, $force = false)
2964
{
2965
	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...
2966
	static $censor_vulgar = null, $censor_proper;
2967
2968
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
2969
		return $text;
2970
2971
	// If they haven't yet been loaded, load them.
2972
	if ($censor_vulgar == null)
2973
	{
2974
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
2975
		$censor_proper = explode("\n", $modSettings['censor_proper']);
2976
2977
		// Quote them for use in regular expressions.
2978
		if (!empty($modSettings['censorWholeWord']))
2979
		{
2980
			$charset = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2981
2982
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
2983
			{
2984
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
2985
2986
				// Use the faster \b if we can, or something more complex if we can't
2987
				$boundary_before = preg_match('/^\w/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?<![\p{L}\p{M}\p{N}_])' : '(?<!\w)');
2988
				$boundary_after = preg_match('/\w$/', $censor_vulgar[$i]) ? '\b' : ($charset === 'UTF-8' ? '(?![\p{L}\p{M}\p{N}_])' : '(?!\w)');
2989
2990
				$censor_vulgar[$i] = '/' . $boundary_before . $censor_vulgar[$i] . $boundary_after . '/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ($charset === 'UTF-8' ? 'u' : '');
2991
			}
2992
		}
2993
	}
2994
2995
	// Censoring isn't so very complicated :P.
2996
	if (empty($modSettings['censorWholeWord']))
2997
	{
2998
		$func = !empty($modSettings['censorIgnoreCase']) ? 'str_ireplace' : 'str_replace';
2999
		$text = $func($censor_vulgar, $censor_proper, $text);
3000
	}
3001
	else
3002
		$text = preg_replace($censor_vulgar, $censor_proper, $text);
3003
3004
	return $text;
3005
}
3006
3007
/**
3008
 * Load the template/language file using eval or require? (with eval we can show an error message!)
3009
 * 	- loads the template or language file specified by filename.
3010
 * 	- uses eval unless disableTemplateEval is enabled.
3011
 * 	- outputs a parse error if the file did not exist or contained errors.
3012
 * 	- attempts to detect the error and line, and show detailed information.
3013
 *
3014
 * @param string $filename The name of the file to include
3015
 * @param bool $once If true only includes the file once (like include_once)
3016
 */
3017
function template_include($filename, $once = false)
3018
{
3019
	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...
3020
	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...
3021
	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...
3022
	static $templates = array();
3023
3024
	// We want to be able to figure out any errors...
3025
	@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...
3026
3027
	// Don't include the file more than once, if $once is true.
3028
	if ($once && in_array($filename, $templates))
3029
		return;
3030
	// Add this file to the include list, whether $once is true or not.
3031
	else
3032
		$templates[] = $filename;
3033
3034
	// Are we going to use eval?
3035
	if (empty($modSettings['disableTemplateEval']))
3036
	{
3037
		$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...
3038
		$settings['current_include_filename'] = $filename;
3039
	}
3040
	else
3041
	{
3042
		$file_found = file_exists($filename);
3043
3044
		if ($once && $file_found)
3045
			require_once($filename);
3046
		elseif ($file_found)
3047
			require($filename);
3048
	}
3049
3050
	if ($file_found !== true)
3051
	{
3052
		ob_end_clean();
3053
		if (!empty($modSettings['enableCompressedOutput']))
3054
			@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...
3055
		else
3056
			ob_start();
3057
3058 View Code Duplication
		if (isset($_GET['debug']))
3059
			header('Content-Type: application/xhtml+xml; charset=' . (empty($context['character_set']) ? 'ISO-8859-1' : $context['character_set']));
3060
3061
		// Don't cache error pages!!
3062
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
3063
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
3064
		header('Cache-Control: no-cache');
3065
3066
		if (!isset($txt['template_parse_error']))
3067
		{
3068
			$txt['template_parse_error'] = 'Template Parse Error!';
3069
			$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>.';
3070
			$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>.';
3071
			$txt['template_parse_errmsg'] = 'Unfortunately more information is not available at this time as to exactly what is wrong.';
3072
		}
3073
3074
		// First, let's get the doctype and language information out of the way.
3075
		echo '<!DOCTYPE html>
3076
<html', !empty($context['right_to_left']) ? ' dir="rtl"' : '', '>
3077
	<head>';
3078
		if (isset($context['character_set']))
3079
			echo '
3080
		<meta charset="', $context['character_set'], '">';
3081
3082
		if (!empty($maintenance) && !allowedTo('admin_forum'))
3083
			echo '
3084
		<title>', $mtitle, '</title>
3085
	</head>
3086
	<body>
3087
		<h3>', $mtitle, '</h3>
3088
		', $mmessage, '
3089
	</body>
3090
</html>';
3091
		elseif (!allowedTo('admin_forum'))
3092
			echo '
3093
		<title>', $txt['template_parse_error'], '</title>
3094
	</head>
3095
	<body>
3096
		<h3>', $txt['template_parse_error'], '</h3>
3097
		', $txt['template_parse_error_message'], '
3098
	</body>
3099
</html>';
3100
		else
3101
		{
3102
			require_once($sourcedir . '/Subs-Package.php');
3103
3104
			$error = fetch_web_data($boardurl . strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3105
			$error_array = error_get_last();
3106
			if (empty($error) && ini_get('track_errors') && !empty($error_array))
3107
				$error = $error_array['message'];
3108
			if (empty($error))
3109
				$error = $txt['template_parse_errmsg'];
3110
3111
			$error = strtr($error, array('<b>' => '<strong>', '</b>' => '</strong>'));
3112
3113
			echo '
3114
		<title>', $txt['template_parse_error'], '</title>
3115
	</head>
3116
	<body>
3117
		<h3>', $txt['template_parse_error'], '</h3>
3118
		', sprintf($txt['template_parse_error_details'], strtr($filename, array($boarddir => '', strtr($boarddir, '\\', '/') => '')));
3119
3120
			if (!empty($error))
3121
				echo '
3122
		<hr>
3123
3124
		<div style="margin: 0 20px;"><pre>', strtr(strtr($error, array('<strong>' . $boarddir => '<strong>...', '<strong>' . strtr($boarddir, '\\', '/') => '<strong>...')), '\\', '/'), '</pre></div>';
3125
3126
			// I know, I know... this is VERY COMPLICATED.  Still, it's good.
3127
			if (preg_match('~ <strong>(\d+)</strong><br( /)?' . '>$~i', $error, $match) != 0)
3128
			{
3129
				$data = file($filename);
3130
				$data2 = highlight_php_code(implode('', $data));
3131
				$data2 = preg_split('~\<br( /)?\>~', $data2);
3132
3133
				// Fix the PHP code stuff...
3134
				if (!isBrowser('gecko'))
3135
					$data2 = str_replace("\t", '<span style="white-space: pre;">' . "\t" . '</span>', $data2);
3136
				else
3137
					$data2 = str_replace('<pre style="display: inline;">' . "\t" . '</pre>', "\t", $data2);
3138
3139
				// Now we get to work around a bug in PHP where it doesn't escape <br>s!
3140
				$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...
3141
				foreach ($data as $line)
3142
				{
3143
					$j++;
3144
3145
					if (substr_count($line, '<br>') == 0)
3146
						continue;
3147
3148
					$n = substr_count($line, '<br>');
3149
					for ($i = 0; $i < $n; $i++)
3150
					{
3151
						$data2[$j] .= '&lt;br /&gt;' . $data2[$j + $i + 1];
3152
						unset($data2[$j + $i + 1]);
3153
					}
3154
					$j += $n;
3155
				}
3156
				$data2 = array_values($data2);
3157
				array_unshift($data2, '');
3158
3159
				echo '
3160
		<div style="margin: 2ex 20px; width: 96%; overflow: auto;"><pre style="margin: 0;">';
3161
3162
				// Figure out what the color coding was before...
3163
				$line = max($match[1] - 9, 1);
3164
				$last_line = '';
3165
				for ($line2 = $line - 1; $line2 > 1; $line2--)
3166
					if (strpos($data2[$line2], '<') !== false)
3167
					{
3168
						if (preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line2], $color_match) != 0)
3169
							$last_line = $color_match[1];
3170
						break;
3171
					}
3172
3173
				// Show the relevant lines...
3174
				for ($n = min($match[1] + 4, count($data2) + 1); $line <= $n; $line++)
3175
				{
3176
					if ($line == $match[1])
3177
						echo '</pre><div style="background-color: #ffb0b5;"><pre style="margin: 0;">';
3178
3179
					echo '<span style="color: black;">', sprintf('%' . strlen($n) . 's', $line), ':</span> ';
3180
					if (isset($data2[$line]) && $data2[$line] != '')
3181
						echo substr($data2[$line], 0, 2) == '</' ? preg_replace('~^</[^>]+>~', '', $data2[$line]) : $last_line . $data2[$line];
3182
3183
					if (isset($data2[$line]) && preg_match('~(<[^/>]+>)[^<]*$~', $data2[$line], $color_match) != 0)
3184
					{
3185
						$last_line = $color_match[1];
3186
						echo '</', substr($last_line, 1, 4), '>';
3187
					}
3188
					elseif ($last_line != '' && strpos($data2[$line], '<') !== false)
3189
						$last_line = '';
3190
					elseif ($last_line != '' && $data2[$line] != '')
3191
						echo '</', substr($last_line, 1, 4), '>';
3192
3193
					if ($line == $match[1])
3194
						echo '</pre></div><pre style="margin: 0;">';
3195
					else
3196
						echo "\n";
3197
				}
3198
3199
				echo '</pre></div>';
3200
			}
3201
3202
			echo '
3203
	</body>
3204
</html>';
3205
		}
3206
3207
		die;
3208
	}
3209
}
3210
3211
/**
3212
 * Initialize a database connection.
3213
 */
3214
function loadDatabase()
3215
{
3216
	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...
3217
	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...
3218
3219
	// Figure out what type of database we are using.
3220
	if (empty($db_type) || !file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
3221
		$db_type = 'mysql';
3222
3223
	// Load the file for the database.
3224
	require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
3225
3226
	$db_options = array();
3227
3228
	// Add in the port if needed
3229
	if (!empty($db_port))
3230
		$db_options['port'] = $db_port;
3231
3232
	// 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.
3233
	if (SMF == 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
3234
	{
3235
		$options = array_merge($db_options, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true));
3236
3237
		$db_connection = smf_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, $options);
3238
	}
3239
3240
	// Either we aren't in SSI mode, or it failed.
3241
	if (empty($db_connection))
3242
	{
3243
		$options = array_merge($db_options, array('persist' => $db_persist, 'dont_select_db' => SMF == 'SSI'));
3244
3245
		$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
3246
	}
3247
3248
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
3249
	if (!$db_connection)
3250
		display_db_error();
3251
3252
	// If in SSI mode fix up the prefix.
3253
	if (SMF == 'SSI')
3254
		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...
3255
}
3256
3257
/**
3258
 * Try to load up a supported caching method. This is saved in $cacheAPI if we are not overriding it.
3259
 *
3260
 * @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...
3261
 * @param bool $fallbackSMF Use the default SMF method if the accelerator fails.
3262
 * @return object|false A object of $cacheAPI, or False on failure.
3263
*/
3264
function loadCacheAccelerator($overrideCache = null, $fallbackSMF = true)
3265
{
3266
	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...
3267
3268
	// Not overriding this and we have a cacheAPI, send it back.
3269
	if (empty($overrideCache) && is_object($cacheAPI))
3270
		return $cacheAPI;
3271
	elseif (is_null($cacheAPI))
3272
		$cacheAPI = false;
3273
3274
	// Make sure our class is in session.
3275
	require_once($sourcedir . '/Class-CacheAPI.php');
3276
3277
	// What accelerator we are going to try.
3278
	$tryAccelerator = !empty($overrideCache) ? $overrideCache : !empty($cache_accelerator) ? $cache_accelerator : 'smf';
3279
	$tryAccelerator = strtolower($tryAccelerator);
3280
3281
	// Do some basic tests.
3282
	if (file_exists($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php'))
3283
	{
3284
		require_once($sourcedir . '/CacheAPI-' . $tryAccelerator . '.php');
3285
3286
		$cache_class_name = $tryAccelerator . '_cache';
3287
		$testAPI = new $cache_class_name();
3288
3289
		// No Support?  NEXT!
3290
		if (!$testAPI->isSupported())
3291
		{
3292
			// Can we save ourselves?
3293
			if (!empty($fallbackSMF) && is_null($overrideCache) && $tryAccelerator != 'smf')
3294
				return loadCacheAccelerator(null, false);
3295
			return false;
3296
		}
3297
3298
		// Connect up to the accelerator.
3299
		$testAPI->connect();
3300
3301
		// Don't set this if we are overriding the cache.
3302
		if (is_null($overrideCache))
3303
		{
3304
			$cacheAPI = $testAPI;
3305
			return $cacheAPI;
3306
		}
3307
		else
3308
			return $testAPI;
3309
	}
3310
}
3311
3312
/**
3313
 * Try to retrieve a cache entry. On failure, call the appropriate function.
3314
 *
3315
 * @param string $key The key for this entry
3316
 * @param string $file The file associated with this entry
3317
 * @param string $function The function to call
3318
 * @param array $params Parameters to be passed to the specified function
3319
 * @param int $level The cache level
3320
 * @return string The cached data
3321
 */
3322
function cache_quick_get($key, $file, $function, $params, $level = 1)
3323
{
3324
	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...
3325
3326
	// @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...
3327
3328
	if (function_exists('call_integration_hook'))
3329
		call_integration_hook('pre_cache_quick_get', array(&$key, &$file, &$function, &$params, &$level));
3330
3331
	/* Refresh the cache if either:
3332
		1. Caching is disabled.
3333
		2. The cache level isn't high enough.
3334
		3. The item has not been cached or the cached item expired.
3335
		4. The cached item has a custom expiration condition evaluating to true.
3336
		5. The expire time set in the cache item has passed (needed for Zend).
3337
	*/
3338
	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...
3339
	{
3340
		require_once($sourcedir . '/' . $file);
3341
		$cache_block = call_user_func_array($function, $params);
3342
3343
		if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= $level)
3344
			cache_put_data($key, $cache_block, $cache_block['expires'] - time());
3345
	}
3346
3347
	// Some cached data may need a freshening up after retrieval.
3348
	if (!empty($cache_block['post_retri_eval']))
3349
		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...
3350
3351
	if (function_exists('call_integration_hook'))
3352
		call_integration_hook('post_cache_quick_get', array(&$cache_block));
3353
3354
	return $cache_block['data'];
3355
}
3356
3357
/**
3358
 * Puts value in the cache under key for ttl seconds.
3359
 *
3360
 * - It may "miss" so shouldn't be depended on
3361
 * - Uses the cache engine chosen in the ACP and saved in settings.php
3362
 * - It supports:
3363
 *	 Xcache: https://xcache.lighttpd.net/wiki/XcacheApi
3364
 *	 memcache: https://php.net/memcache
3365
 *	 APC: https://php.net/apc
3366
 *   APCu: https://php.net/book.apcu
3367
 *	 Zend: http://files.zend.com/help/Zend-Platform/output_cache_functions.htm
3368
 *	 Zend: http://files.zend.com/help/Zend-Platform/zend_cache_functions.htm
3369
 *
3370
 * @param string $key A key for this value
3371
 * @param mixed $value The data to cache
3372
 * @param int $ttl How long (in seconds) the data should be cached for
3373
 */
3374
function cache_put_data($key, $value, $ttl = 120)
3375
{
3376
	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...
3377
	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...
3378
3379
	if (empty($cache_enable) || empty($cacheAPI))
3380
		return;
3381
3382
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3383
	if (isset($db_show_debug) && $db_show_debug === true)
3384
	{
3385
		$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)));
3386
		$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...
3387
	}
3388
3389
	// The API will handle the rest.
3390
	$value = $value === null ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_encode']($value) : json_encode($value));
3391
	$cacheAPI->putData($key, $value, $ttl);
3392
3393
	if (function_exists('call_integration_hook'))
3394
		call_integration_hook('cache_put_data', array(&$key, &$value, &$ttl));
3395
3396 View Code Duplication
	if (isset($db_show_debug) && $db_show_debug === true)
3397
		$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...
3398
}
3399
3400
/**
3401
 * Gets the value from the cache specified by key, so long as it is not older than ttl seconds.
3402
 * - It may often "miss", so shouldn't be depended on.
3403
 * - It supports the same as cache_put_data().
3404
 *
3405
 * @param string $key The key for the value to retrieve
3406
 * @param int $ttl The maximum age of the cached data
3407
 * @return string The cached data or null if nothing was loaded
3408
 */
3409
function cache_get_data($key, $ttl = 120)
3410
{
3411
	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...
3412
	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...
3413
3414
	if (empty($cache_enable) || empty($cacheAPI))
3415
		return;
3416
3417
	$cache_count = isset($cache_count) ? $cache_count + 1 : 1;
3418
	if (isset($db_show_debug) && $db_show_debug === true)
3419
	{
3420
		$cache_hits[$cache_count] = array('k' => $key, 'd' => 'get');
3421
		$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...
3422
		$original_key = $key;
3423
	}
3424
3425
	// Ask the API to get the data.
3426
	$value = $cacheAPI->getData($key, $ttl);
3427
3428
	if (isset($db_show_debug) && $db_show_debug === true)
3429
	{
3430
		$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...
3431
		$cache_hits[$cache_count]['s'] = isset($value) ? strlen($value) : 0;
3432
3433
		if (empty($value))
3434
		{
3435
			if (!is_array($cache_misses))
3436
				$cache_misses = array();
3437
3438
			$cache_count_misses = isset($cache_count_misses) ? $cache_count_misses + 1 : 1;
3439
			$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...
3440
		}
3441
	}
3442
3443
	if (function_exists('call_integration_hook') && isset($value))
3444
		call_integration_hook('cache_get_data', array(&$key, &$ttl, &$value));
3445
3446
	return empty($value) ? null : (isset($smcFunc['json_encode']) ? $smcFunc['json_decode']($value, true) : smf_json_decode($value, true));
3447
}
3448
3449
/**
3450
 * Empty out the cache in use as best it can
3451
 *
3452
 * It may only remove the files of a certain type (if the $type parameter is given)
3453
 * Type can be user, data or left blank
3454
 * 	- user clears out user data
3455
 *  - data clears out system / opcode data
3456
 *  - If no type is specified will perform a complete cache clearing
3457
 * For cache engines that do not distinguish on types, a full cache flush will be done
3458
 *
3459
 * @param string $type The cache type ('memcached', 'apc', 'xcache', 'zend' or something else for SMF's file cache)
3460
 */
3461
function clean_cache($type = '')
3462
{
3463
	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...
3464
3465
	// If we can't get to the API, can't do this.
3466
	if (empty($cacheAPI))
3467
		return;
3468
3469
	// Ask the API to do the heavy lifting. cleanCache also calls invalidateCache to be sure.
3470
	$cacheAPI->cleanCache($type);
3471
3472
	call_integration_hook('integrate_clean_cache');
3473
	clearstatcache();
3474
}
3475
3476
/**
3477
 * Helper function to set an array of data for an user's avatar.
3478
 *
3479
 * Makes assumptions based on the data provided, the following keys are required:
3480
 * - avatar The raw "avatar" column in members table
3481
 * - email The user's email. Used to get the gravatar info
3482
 * - filename The attachment filename
3483
 *
3484
 * @param array $data An array of raw info
3485
 * @return array An array of avatar data
3486
 */
3487
function set_avatar_data($data = array())
3488
{
3489
	global $modSettings, $boardurl, $smcFunc, $image_proxy_enabled, $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...
3490
3491
	// Come on!
3492
	if (empty($data))
3493
		return array();
3494
3495
	// Set a nice default var.
3496
	$image = '';
3497
3498
	// Gravatar has been set as mandatory!
3499
	if (!empty($modSettings['gravatarOverride']))
3500
	{
3501
		if (!empty($modSettings['gravatarAllowExtraEmail']) && !empty($data['avatar']) && stristr($data['avatar'], 'gravatar://'))
3502
			$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3503
3504
		else if (!empty($data['email']))
3505
			$image = get_gravatar_url($data['email']);
3506
	}
3507
3508
	// Look if the user has a gravatar field or has set an external url as avatar.
3509
	else
3510
	{
3511
		// So it's stored in the member table?
3512
		if (!empty($data['avatar']))
3513
		{
3514
			// Gravatar.
3515
			if (stristr($data['avatar'], 'gravatar://'))
3516
			{
3517
				if ($data['avatar'] == 'gravatar://')
3518
					$image = get_gravatar_url($data['email']);
3519
3520
				elseif (!empty($modSettings['gravatarAllowExtraEmail']))
3521
					$image = get_gravatar_url($smcFunc['substr']($data['avatar'], 11));
3522
			}
3523
3524
			// External url.
3525
			else
3526
			{
3527
				// Using ssl?
3528
				if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($data['avatar'], 'http://') !== false && empty($user_info['possibly_robot']))
3529
					$image = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($data['avatar']) . '&hash=' . md5($data['avatar'] . $image_proxy_secret);
3530
3531
				// Just a plain external url.
3532
				else
3533
					$image = (stristr($data['avatar'], 'http://') || stristr($data['avatar'], 'https://')) ? $data['avatar'] : $modSettings['avatar_url'] . '/' . $data['avatar'];
3534
			}
3535
		}
3536
3537
		// Perhaps this user has an attachment as avatar...
3538
		else if (!empty($data['filename']))
3539
			$image = $modSettings['custom_avatar_url'] . '/' . $data['filename'];
3540
3541
		// Right... no avatar... use our default image.
3542
		else
3543
			$image = $modSettings['avatar_url'] . '/default.png';
3544
	}
3545
3546
	call_integration_hook('integrate_set_avatar_data', array(&$image, &$data));
3547
3548
	// 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.
3549
	if (!empty($image))
3550
		return array(
3551
			'name' => !empty($data['avatar']) ? $data['avatar'] : '',
3552
			'image' => '<img class="avatar" src="' . $image . '" />',
3553
			'href' => $image,
3554
			'url' => $image,
3555
		);
3556
3557
	// Fallback to make life easier for everyone...
3558
	else
3559
		return array(
3560
			'name' => '',
3561
			'image' => '',
3562
			'href' => '',
3563
			'url' => '',
3564
		);
3565
}
3566
3567
?>