Completed
Pull Request — release-2.1 (#4383)
by Colin
07:31
created

Load.php ➔ loadMemberCustomFields()   F

Complexity

Conditions 17
Paths 393

Size

Total Lines 76
Code Lines 37

Duplication

Lines 6
Ratio 7.89 %

Importance

Changes 0
Metric Value
cc 17
eloc 37
nc 393
nop 2
dl 6
loc 76
rs 3.9636
c 0
b 0
f 0

How to fix   Long Method    Complexity   

Long Method

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

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

Commonly applied refactorings include:

1
<?php
2
3
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * Simple Machines Forum (SMF)
7
 *
8
 * @package SMF
9
 * @author Simple Machines http://www.simplemachines.org
10
 * @copyright 2017 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
	// Get an error count, if necessary
362
	if (!isset($context['num_errors']))
363
	{
364
		$query = $smcFunc['db_query']('', '
365
			SELECT COUNT(id_error)
366
			FROM {db_prefix}log_errors',
367
			array()
368
		);
369
370
		list($context['num_errors']) = $smcFunc['db_fetch_row']($query);
371
		$smcFunc['db_free_result']($query);
372
	}
373
374
	// Call pre load integration functions.
375
	call_integration_hook('integrate_pre_load');
376
}
377
378
/**
379
 * Load all the important user information.
380
 * What it does:
381
 * 	- sets up the $user_info array
382
 * 	- assigns $user_info['query_wanna_see_board'] for what boards the user can see.
383
 * 	- first checks for cookie or integration validation.
384
 * 	- uses the current session if no integration function or cookie is found.
385
 * 	- checks password length, if member is activated and the login span isn't over.
386
 * 		- if validation fails for the user, $id_member is set to 0.
387
 * 		- updates the last visit time when needed.
388
 */
389
function loadUserSettings()
390
{
391
	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...
392
	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...
393
394
	// Check first the integration, then the cookie, and last the session.
395
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
396
	{
397
		$id_member = 0;
398
		foreach ($integration_ids as $integration_id)
399
		{
400
			$integration_id = (int) $integration_id;
401
			if ($integration_id > 0)
402
			{
403
				$id_member = $integration_id;
404
				$already_verified = true;
405
				break;
406
			}
407
		}
408
	}
409
	else
410
		$id_member = 0;
411
412
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
413
	{
414
		$cookie_data = $smcFunc['json_decode']($_COOKIE[$cookiename], true, false);
415
416
		if (empty($cookie_data))
417
			$cookie_data = safe_unserialize($_COOKIE[$cookiename]);
418
419
		list ($id_member, $password) = $cookie_data;
420
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
421
	}
422
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $_SERVER['HTTP_USER_AGENT'] || !empty($modSettings['disableCheckUA'])))
423
	{
424
		// @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...
425
		$cookie_data = $smcFunc['json_decode']($_SESSION['login_' . $cookiename]);
426
427
		if (empty($cookie_data))
428
			$cookie_data = safe_unserialize($_SESSION['login_' . $cookiename]);
429
430
		list ($id_member, $password, $login_span) = $cookie_data;
431
		$id_member = !empty($id_member) && strlen($password) == 128 && $login_span > time() ? (int) $id_member : 0;
432
	}
433
434
	// Only load this stuff if the user isn't a guest.
435
	if ($id_member != 0)
436
	{
437
		// Is the member data cached?
438
		if (empty($modSettings['cache_enable']) || $modSettings['cache_enable'] < 2 || ($user_settings = cache_get_data('user_settings-' . $id_member, 60)) == null)
439
		{
440
			$request = $smcFunc['db_query']('', '
441
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
442
				FROM {db_prefix}members AS mem
443
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
444
				WHERE mem.id_member = {int:id_member}
445
				LIMIT 1',
446
				array(
447
					'id_member' => $id_member,
448
				)
449
			);
450
			$user_settings = $smcFunc['db_fetch_assoc']($request);
451
			$smcFunc['db_free_result']($request);
452
453 View Code Duplication
			if (!empty($modSettings['force_ssl']) && $image_proxy_enabled && stripos($user_settings['avatar'], 'http://') !== false)
454
				$user_settings['avatar'] = strtr($boardurl, array('http://' => 'https://')) . '/proxy.php?request=' . urlencode($user_settings['avatar']) . '&hash=' . md5($user_settings['avatar'] . $image_proxy_secret);
455
456
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
457
				cache_put_data('user_settings-' . $id_member, $user_settings, 60);
458
		}
459
460
		// Did we find 'im?  If not, junk it.
461
		if (!empty($user_settings))
462
		{
463
			// As much as the password should be right, we can assume the integration set things up.
464
			if (!empty($already_verified) && $already_verified === true)
465
				$check = true;
466
			// SHA-512 hash should be 128 characters long.
467
			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...
468
				$check = hash_salt($user_settings['passwd'], $user_settings['password_salt']) == $password;
469
			else
470
				$check = false;
471
472
			// Wrong password or not activated - either way, you're going nowhere.
473
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
474
		}
475
		else
476
			$id_member = 0;
477
478
		// If we no longer have the member maybe they're being all hackey, stop brute force!
479
		if (!$id_member)
480
		{
481
			require_once($sourcedir . '/LogInOut.php');
482
			validatePasswordFlood(
483
				!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member,
484
				!empty($user_settings['member_name']) ? $user_settings['member_name'] : '',
485
				!empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false,
486
				$id_member != 0
487
			);
488
		}
489
		// Validate for Two Factor Authentication
490
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && (empty($_REQUEST['action']) || !in_array($_REQUEST['action'], array('login2', 'logintfa'))))
491
		{
492
			$tfacookie = $cookiename . '_tfa';
493
			$tfasecret = null;
494
495
			$verified = call_integration_hook('integrate_verify_tfa', array($id_member, $user_settings));
496
497
			if (empty($verified) || !in_array(true, $verified))
498
			{
499
				if (!empty($_COOKIE[$tfacookie]))
500
				{
501
					$tfa_data = $smcFunc['json_decode']($_COOKIE[$tfacookie]);
502
503
					list ($tfamember, $tfasecret) = $tfa_data;
504
505
					if (!isset($tfamember, $tfasecret) || (int) $tfamember != $id_member)
506
						$tfasecret = null;
507
				}
508
509
				if (empty($tfasecret) || hash_salt($user_settings['tfa_backup'], $user_settings['password_salt']) != $tfasecret)
510
				{
511
					$id_member = 0;
512
					redirectexit('action=logintfa');
513
				}
514
			}
515
		}
516
		// When authenticating their two factor code, make sure to reset their ID for security
517
		elseif (!empty($modSettings['tfa_mode']) && $id_member && !empty($user_settings['tfa_secret']) && $_REQUEST['action'] == 'logintfa')
518
		{
519
			$id_member = 0;
520
			$context['tfa_member'] = $user_settings;
521
			$user_settings = array();
522
		}
523
		// Are we forcing 2FA? Need to check if the user groups actually require 2FA
524
		elseif (!empty($modSettings['tfa_mode']) && $modSettings['tfa_mode'] >= 2 && $id_member && empty($user_settings['tfa_secret']))
525
		{
526
			if ($modSettings['tfa_mode'] == 2) //only do this if we are just forcing SOME membergroups
527
			{
528
				//Build an array of ALL user membergroups.
529
				$full_groups = array($user_settings['id_group']);
530
				if (!empty($user_settings['additional_groups']))
531
				{
532
					$full_groups = array_merge($full_groups, explode(',', $user_settings['additional_groups']));
533
					$full_groups = array_unique($full_groups); //duplicates, maybe?
534
				}
535
536
				//Find out if any group requires 2FA
537
				$request = $smcFunc['db_query']('', '
538
					SELECT COUNT(id_group) AS total
539
					FROM {db_prefix}membergroups
540
					WHERE tfa_required = {int:tfa_required}
541
						AND id_group IN ({array_int:full_groups})',
542
					array(
543
						'tfa_required' => 1,
544
						'full_groups' => $full_groups,
545
					)
546
				);
547
				$row = $smcFunc['db_fetch_assoc']($request);
548
				$smcFunc['db_free_result']($request);
549
			}
550
			else
551
				$row['total'] = 1; //simplifies logics in the next "if"
0 ignored issues
show
Coding Style Comprehensibility introduced by
$row was never initialized. Although not strictly required by PHP, it is generally a good practice to add $row = array(); before regardless.

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

Let’s take a look at an example:

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

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

    // do something with $myArray
}

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

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

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

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

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

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

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

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

Let’s take a look at an example:

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

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

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

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

Available Fixes

  1. Check for existence of the variable explicitly:

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

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

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

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

1. Pass all data via parameters

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

2. Create a class that maintains your state

class MyClass {
    private $a;
    private $b;

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

    public function myFunction() {
        // Do something
    }
}
Loading history...
1190
	global $image_proxy_enabled, $image_proxy_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...
1191
1192
	// Can't just look for no users :P.
1193
	if (empty($users))
1194
		return array();
1195
1196
	// Pass the set value
1197
	$context['loadMemberContext_set'] = $set;
1198
1199
	// Make sure it's an array.
1200
	$users = !is_array($users) ? array($users) : array_unique($users);
1201
	$loaded_ids = array();
1202
1203
	if (!$is_name && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1204
	{
1205
		$users = array_values($users);
1206
		for ($i = 0, $n = count($users); $i < $n; $i++)
1207
		{
1208
			$data = cache_get_data('member_data-' . $set . '-' . $users[$i], 240);
1209
			if ($data == null)
1210
				continue;
1211
1212
			$loaded_ids[] = $data['id_member'];
1213
			$user_profile[$data['id_member']] = $data;
1214
			unset($users[$i]);
1215
		}
1216
	}
1217
1218
	// Used by default
1219
	$select_columns = '
1220
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
1221
			mem.signature, mem.personal_text, mem.avatar, mem.id_member, mem.member_name,
1222
			mem.real_name, mem.email_address, mem.date_registered, mem.website_title, mem.website_url,
1223
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.id_post_group, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online,
1224
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
1225
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
1226
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
1227
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
1228
	$select_tables = '
1229
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
1230
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
1231
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
1232
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
1233
1234
	// We add or replace according the the set
1235
	switch ($set)
1236
	{
1237
		case 'normal':
1238
			$select_columns .= ', mem.buddy_list,  mem.additional_groups';
1239
			break;
1240
		case 'profile':
1241
			$select_columns .= ', mem.additional_groups, mem.id_theme, mem.pm_ignore_list, mem.pm_receive_from,
1242
			mem.time_format, mem.timezone, mem.secret_question, mem.smiley_set, mem.tfa_secret,
1243
			mem.total_time_logged_in, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.alerts';
1244
			break;
1245
		case 'minimal':
1246
			$select_columns = '
1247
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.date_registered,
1248
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
1249
			$select_tables = '';
1250
			break;
1251
		default:
1252
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
1253
	}
1254
1255
	// Allow mods to easily add to the selected member data
1256
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, &$set));
1257
1258
	if (!empty($users))
1259
	{
1260
		// Load the member's data.
1261
		$request = $smcFunc['db_query']('', '
1262
			SELECT' . $select_columns . '
1263
			FROM {db_prefix}members AS mem' . $select_tables . '
1264
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})',
1265
			array(
1266
				'blank_string' => '',
1267
				'users' => $users,
1268
			)
1269
		);
1270
		$new_loaded_ids = array();
1271
		while ($row = $smcFunc['db_fetch_assoc']($request))
1272
		{
1273
			// If the image proxy is enabled, we still want the original URL when they're editing the profile...
1274
			$row['avatar_original'] = !empty($row['avatar']) ? $row['avatar'] : '';
1275
1276
			// Take care of proxying avatar if required, do this here for maximum reach
1277 View Code Duplication
			if ($image_proxy_enabled && !empty($row['avatar']) && stripos($row['avatar'], 'http://') !== false)
1278
				$row['avatar'] = $boardurl . '/proxy.php?request=' . urlencode($row['avatar']) . '&hash=' . md5($row['avatar'] . $image_proxy_secret);
1279
1280
			// Keep track of the member's normal member group
1281
			$row['primary_group'] = $row['member_group'];
1282
1283
			if (isset($row['member_ip']))
1284
				$row['member_ip'] = inet_dtop($row['member_ip']);
1285
			if (isset($row['member_ip2']))
1286
				$row['member_ip2'] = inet_dtop($row['member_ip2']);
1287
			$new_loaded_ids[] = $row['id_member'];
1288
			$loaded_ids[] = $row['id_member'];
1289
			$row['options'] = array();
1290
			$user_profile[$row['id_member']] = $row;
1291
		}
1292
		$smcFunc['db_free_result']($request);
1293
	}
1294
1295 View Code Duplication
	if (!empty($new_loaded_ids) && $set !== 'minimal')
1296
	{
1297
		$request = $smcFunc['db_query']('', '
1298
			SELECT id_member, variable, value
1299
			FROM {db_prefix}themes
1300
			WHERE id_member IN ({array_int:loaded_ids})',
1301
			array(
1302
				'loaded_ids' => $new_loaded_ids,
1303
			)
1304
		);
1305
		while ($row = $smcFunc['db_fetch_assoc']($request))
1306
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1307
		$smcFunc['db_free_result']($request);
1308
	}
1309
1310
	$additional_mods = array();
1311
1312
	// Are any of these users in groups assigned to moderate this board?
1313
	if (!empty($loaded_ids) && !empty($board_info['moderator_groups']) && $set === 'normal')
1314
	{
1315
		foreach ($loaded_ids as $a_member)
1316
		{
1317
			if (!empty($user_profile[$a_member]['additional_groups']))
1318
				$groups = array_merge(array($user_profile[$a_member]['id_group']), explode(',', $user_profile[$a_member]['additional_groups']));
1319
			else
1320
				$groups = array($user_profile[$a_member]['id_group']);
1321
1322
			$temp = array_intersect($groups, array_keys($board_info['moderator_groups']));
1323
1324
			if (!empty($temp))
1325
			{
1326
				$additional_mods[] = $a_member;
1327
			}
1328
		}
1329
	}
1330
1331
	if (!empty($new_loaded_ids) && !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 3)
1332
	{
1333
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1334
			cache_put_data('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1335
	}
1336
1337
	// Are we loading any moderators?  If so, fix their group data...
1338
	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)
1339
	{
1340 View Code Duplication
		if (($row = cache_get_data('moderator_group_info', 480)) == null)
1341
		{
1342
			$request = $smcFunc['db_query']('', '
1343
				SELECT group_name AS member_group, online_color AS member_group_color, icons
1344
				FROM {db_prefix}membergroups
1345
				WHERE id_group = {int:moderator_group}
1346
				LIMIT 1',
1347
				array(
1348
					'moderator_group' => 3,
1349
				)
1350
			);
1351
			$row = $smcFunc['db_fetch_assoc']($request);
1352
			$smcFunc['db_free_result']($request);
1353
1354
			cache_put_data('moderator_group_info', $row, 480);
1355
		}
1356
1357
		foreach ($temp_mods as $id)
1358
		{
1359
			// By popular demand, don't show admins or global moderators as moderators.
1360
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1361
				$user_profile[$id]['member_group'] = $row['member_group'];
1362
1363
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1364
			if (!empty($row['icons']))
1365
				$user_profile[$id]['icons'] = $row['icons'];
1366
			if (!empty($row['member_group_color']))
1367
				$user_profile[$id]['member_group_color'] = $row['member_group_color'];
1368
		}
1369
	}
1370
1371
	return $loaded_ids;
1372
}
1373
1374
/**
1375
 * Loads the user's basic values... meant for template/theme usage.
1376
 *
1377
 * @param int $user The ID of a user previously loaded by {@link loadMemberData()}
1378
 * @param bool $display_custom_fields Whether or not to display custom profile fields
1379
 * @return boolean Whether or not the data was loaded successfully
1380
 */
1381
function loadMemberContext($user, $display_custom_fields = false)
1382
{
1383
	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...
1384
	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...
1385
	static $dataLoaded = array();
1386
	static $loadedLanguages = array();
1387
1388
	// If this person's data is already loaded, skip it.
1389
	if (isset($dataLoaded[$user]))
1390
		return true;
1391
1392
	// We can't load guests or members not loaded by loadMemberData()!
1393
	if ($user == 0)
1394
		return false;
1395
	if (!isset($user_profile[$user]))
1396
	{
1397
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1398
		return false;
1399
	}
1400
1401
	// Well, it's loaded now anyhow.
1402
	$dataLoaded[$user] = true;
1403
	$profile = $user_profile[$user];
1404
1405
	// Censor everything.
1406
	censorText($profile['signature']);
1407
	censorText($profile['personal_text']);
1408
1409
	// Set things up to be used before hand.
1410
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br>', ''), $profile['signature']);
1411
	$profile['signature'] = parse_bbc($profile['signature'], true, 'sig' . $profile['id_member']);
1412
1413
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1414
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1415
	// Setup the buddy status here (One whole in_array call saved :P)
1416
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1417
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1418
1419
	//We need a little fallback for the membergroup icons. If it doesn't exist in the current theme, fallback to default theme
1420
	if (isset($profile['icons'][1]) && file_exists($settings['actual_theme_dir'] . '/images/membericons/' . $profile['icons'][1])) //icon is set and exists
1421
		$group_icon_url = $settings['images_url'] . '/membericons/' . $profile['icons'][1];
1422
	elseif (isset($profile['icons'][1])) //icon is set and doesn't exist, fallback to default
1423
		$group_icon_url = $settings['default_images_url'] . '/membericons/' . $profile['icons'][1];
1424
	else //not set, bye bye
1425
		$group_icon_url = '';
1426
1427
	// These minimal values are always loaded
1428
	$memberContext[$user] = array(
1429
		'username' => $profile['member_name'],
1430
		'name' => $profile['real_name'],
1431
		'id' => $profile['id_member'],
1432
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1433
		'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>',
1434
		'email' => $profile['email_address'],
1435
		'show_email' => !$user_info['is_guest'] && ($user_info['id'] == $profile['id_member'] || allowedTo('moderate_forum')),
1436
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : timeformat($profile['date_registered']),
1437
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1438
	);
1439
1440
	// If the set isn't minimal then load the monstrous array.
1441
	if ($context['loadMemberContext_set'] != 'minimal')
1442
	{
1443
		// Go the extra mile and load the user's native language name.
1444
		if (empty($loadedLanguages))
1445
			$loadedLanguages = getLanguages();
1446
1447
		$memberContext[$user] += array(
1448
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1449
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1450
			'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>',
1451
			'is_buddy' => $profile['buddy'],
1452
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1453
			'buddies' => $buddy_list,
1454
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1455
			'blurb' => $profile['personal_text'],
1456
			'website' => array(
1457
				'title' => $profile['website_title'],
1458
				'url' => $profile['website_url'],
1459
			),
1460
			'birth_date' => empty($profile['birthdate']) ? '1004-01-01' : (substr($profile['birthdate'], 0, 4) === '0004' ? '1004' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1461
			'signature' => $profile['signature'],
1462
			'real_posts' => $profile['posts'],
1463
			'posts' => $profile['posts'] > 500000 ? $txt['geek'] : comma_format($profile['posts']),
1464
			'last_login' => empty($profile['last_login']) ? $txt['never'] : timeformat($profile['last_login']),
1465
			'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...
1466
			'ip' => $smcFunc['htmlspecialchars']($profile['member_ip']),
1467
			'ip2' => $smcFunc['htmlspecialchars']($profile['member_ip2']),
1468
			'online' => array(
1469
				'is_online' => $profile['is_online'],
1470
				'text' => $smcFunc['htmlspecialchars']($txt[$profile['is_online'] ? 'online' : 'offline']),
1471
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], $smcFunc['htmlspecialchars']($profile['real_name'])),
1472
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1473
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1474
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1475
			),
1476
			'language' => !empty($loadedLanguages[$profile['lngfile']]) && !empty($loadedLanguages[$profile['lngfile']]['name']) ? $loadedLanguages[$profile['lngfile']]['name'] : $smcFunc['ucwords'](strtr($profile['lngfile'], array('_' => ' ', '-utf8' => ''))),
1477
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1478
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1479
			'options' => $profile['options'],
1480
			'is_guest' => false,
1481
			'primary_group' => $profile['primary_group'],
1482
			'group' => $profile['member_group'],
1483
			'group_color' => $profile['member_group_color'],
1484
			'group_id' => $profile['id_group'],
1485
			'post_group' => $profile['post_group'],
1486
			'post_group_color' => $profile['post_group_color'],
1487
			'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]),
1488
			'warning' => $profile['warning'],
1489
			'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' : (''))),
1490
			'local_time' => timeformat(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1491
			'custom_fields' => array(),
1492
		);
1493
	}
1494
1495
	// If the set isn't minimal then load their avatar as well.
1496
	if ($context['loadMemberContext_set'] != 'minimal')
1497
	{
1498
		if (!empty($modSettings['gravatarOverride']) || (!empty($modSettings['gravatarEnabled']) && stristr($profile['avatar'], 'gravatar://')))
1499
		{
1500
			if (!empty($modSettings['gravatarAllowExtraEmail']) && stristr($profile['avatar'], 'gravatar://') && strlen($profile['avatar']) > 11)
1501
				$image = get_gravatar_url($smcFunc['substr']($profile['avatar'], 11));
1502
			else
1503
				$image = get_gravatar_url($profile['email_address']);
1504
		}
1505
		else
1506
		{
1507
			// So it's stored in the member table?
1508
			if (!empty($profile['avatar']))
1509
			{
1510
				$image = (stristr($profile['avatar'], 'http://') || stristr($profile['avatar'], 'https://')) ? $profile['avatar'] : $modSettings['avatar_url'] . '/' . $profile['avatar'];
1511
			}
1512
			elseif (!empty($profile['filename']))
1513
				$image = $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1514
			// Right... no avatar...use the default one
1515
			else
1516
				$image = $modSettings['avatar_url'] . '/default.png';
1517
		}
1518
		if (!empty($image))
1519
			$memberContext[$user]['avatar'] = array(
1520
				'name' => $profile['avatar'],
1521
				'image' => '<img class="avatar" src="' . $image . '" alt="avatar_' . $profile['member_name'] . '">',
1522
				'href' => $image,
1523
				'url' => $image,
1524
			);
1525
	}
1526
1527
	// Are we also loading the members custom fields into context?
1528
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1529
	{
1530
		$memberContext[$user]['custom_fields'] = array();
1531
1532
		if (!isset($context['display_fields']))
1533
			$context['display_fields'] = $smcFunc['json_decode']($modSettings['displayFields'], true);
1534
1535
		foreach ($context['display_fields'] as $custom)
1536
		{
1537
			if (!isset($custom['col_name']) || trim($custom['col_name']) == '' || empty($profile['options'][$custom['col_name']]))
1538
				continue;
1539
1540
			$value = $profile['options'][$custom['col_name']];
1541
1542
			$fieldOptions = array();
1543
			$currentKey = 0;
1544
1545
			// Create a key => value array for multiple options fields
1546 View Code Duplication
			if (!empty($custom['options']))
1547
				foreach ($custom['options'] as $k => $v)
1548
				{
1549
					$fieldOptions[] = $v;
1550
					$currentKey = $v == $value ? $k : 0;
1551
				}
1552
1553
			// BBC?
1554
			if ($custom['bbc'])
1555
				$value = parse_bbc($value);
1556
1557
			// ... 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...
1558
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1559
				$value = $value ? $txt['yes'] : $txt['no'];
1560
1561
			// Enclosing the user input within some other text?
1562
			if (!empty($custom['enclose']))
1563
				$value = strtr($custom['enclose'], array(
1564
					'{SCRIPTURL}' => $scripturl,
1565
					'{IMAGES_URL}' => $settings['images_url'],
1566
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1567
					'{INPUT}' => $value,
1568
					'{KEY}' => $currentKey,
1569
				));
1570
1571
			$memberContext[$user]['custom_fields'][] = array(
1572
				'title' => !empty($custom['title']) ? $custom['title'] : $custom['col_name'],
1573
				'col_name' => $custom['col_name'],
1574
				'value' => un_htmlspecialchars($value),
1575
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1576
			);
1577
		}
1578
	}
1579
1580
	call_integration_hook('integrate_member_context', array(&$memberContext[$user], $user, $display_custom_fields));
1581
	return true;
1582
}
1583
1584
/**
1585
 * Loads the user's custom profile fields
1586
 *
1587
 * @param integer|array $users A single user ID or an array of user IDs
1588
 * @param string|array $params Either a string or an array of strings with profile field names
1589
 * @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...
1590
 */
1591
function loadMemberCustomFields($users, $params)
1592
{
1593
	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...
1594
1595
	// Do not waste my time...
1596
	if (empty($users) || empty($params))
1597
		return false;
1598
1599
	// Make sure it's an array.
1600
	$users = !is_array($users) ? array($users) : array_unique($users);
1601
	$params = !is_array($params) ? array($params) : array_unique($params);
1602
	$return = array();
1603
1604
	$request = $smcFunc['db_query']('', '
1605
		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,
1606
		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
1607
		FROM {db_prefix}themes AS t
1608
			LEFT JOIN {db_prefix}custom_fields AS c ON (c.col_name = t.variable)
1609
		WHERE id_member IN ({array_int:loaded_ids})
1610
			AND variable IN ({array_string:params})
1611
		ORDER BY field_order',
1612
		array(
1613
			'loaded_ids' => $users,
1614
			'params' => $params,
1615
		)
1616
	);
1617
1618
	while ($row = $smcFunc['db_fetch_assoc']($request))
1619
	{
1620
		$fieldOptions = array();
1621
		$currentKey = 0;
1622
1623
		// Create a key => value array for multiple options fields
1624 View Code Duplication
		if (!empty($row['field_options']))
1625
			foreach (explode(',', $row['field_options']) as $k => $v)
1626
			{
1627
				$fieldOptions[] = $v;
1628
				$currentKey = $v == $row['value'] ? $k : 0;
1629
			}
1630
1631
		// BBC?
1632
		if (!empty($row['bbc']))
1633
			$row['value'] = parse_bbc($row['value']);
1634
1635
		// ... 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...
1636
		elseif (isset($row['type']) && $row['type'] == 'check')
1637
			$row['value'] = !empty($row['value']) ? $txt['yes'] : $txt['no'];
1638
1639
		// Enclosing the user input within some other text?
1640
		if (!empty($row['enclose']))
1641
			$row['value'] = strtr($row['enclose'], array(
1642
				'{SCRIPTURL}' => $scripturl,
1643
				'{IMAGES_URL}' => $settings['images_url'],
1644
				'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1645
				'{INPUT}' => un_htmlspecialchars($row['value']),
1646
				'{KEY}' => $currentKey,
1647
			));
1648
1649
		// Send a simple array if there is just 1 param
1650
		if (count($params) == 1)
1651
			$return[$row['id_member']] = $row;
1652
1653
		// More than 1? knock yourself out...
1654
		else
1655
		{
1656
			if (!isset($return[$row['id_member']]))
1657
				$return[$row['id_member']] = array();
1658
1659
			$return[$row['id_member']][$row['variable']] = $row;
1660
		}
1661
	}
1662
1663
	$smcFunc['db_free_result']($request);
1664
1665
	return !empty($return) ? $return : false;
1666
}
1667
1668
/**
1669
 * Loads information about what browser the user is viewing with and places it in $context
1670
 *  - uses the class from {@link Class-BrowserDetect.php}
1671
 */
1672
function detectBrowser()
1673
{
1674
	// Load the current user's browser of choice
1675
	$detector = new browser_detector;
1676
	$detector->detectBrowser();
1677
}
1678
1679
/**
1680
 * Are we using this browser?
1681
 *
1682
 * Wrapper function for detectBrowser
1683
 * @param string $browser The browser we are checking for.
1684
 * @return bool Whether or not the current browser is what we're looking for
1685
*/
1686
function isBrowser($browser)
1687
{
1688
	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...
1689
1690
	// Don't know any browser!
1691
	if (empty($context['browser']))
1692
		detectBrowser();
1693
1694
	return !empty($context['browser'][$browser]) || !empty($context['browser']['is_' . $browser]) ? true : false;
1695
}
1696
1697
/**
1698
 * Load a theme, by ID.
1699
 *
1700
 * @param int $id_theme The ID of the theme to load
1701
 * @param bool $initialize Whether or not to initialize a bunch of theme-related variables/settings
1702
 */
1703
function loadTheme($id_theme = 0, $initialize = true)
1704
{
1705
	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...
1706
	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...
1707
	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...
1708
1709
	// The theme was specified by parameter.
1710
	if (!empty($id_theme))
1711
		$id_theme = (int) $id_theme;
1712
	// The theme was specified by REQUEST.
1713 View Code Duplication
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1714
	{
1715
		$id_theme = (int) $_REQUEST['theme'];
1716
		$_SESSION['id_theme'] = $id_theme;
1717
	}
1718
	// The theme was specified by REQUEST... previously.
1719 View Code Duplication
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1720
		$id_theme = (int) $_SESSION['id_theme'];
1721
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1722
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']))
1723
		$id_theme = $user_info['theme'];
1724
	// The theme was specified by the board.
1725
	elseif (!empty($board_info['theme']))
1726
		$id_theme = $board_info['theme'];
1727
	// The theme is the forum's default.
1728
	else
1729
		$id_theme = $modSettings['theme_guests'];
1730
1731
	// We already load the basic stuff?
1732
	if (empty($settings['theme_id']) || $settings['theme_id'] != $id_theme )
1733
	{
1734
		// Verify the id_theme... no foul play.
1735
		// Always allow the board specific theme, if they are overriding.
1736
		if (!empty($board_info['theme']) && $board_info['override_theme'])
1737
			$id_theme = $board_info['theme'];
1738
		// If they have specified a particular theme to use with SSI allow it to be used.
1739
		elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1740
			$id_theme = (int) $id_theme;
1741
		elseif (!empty($modSettings['enableThemes']) && !allowedTo('admin_forum'))
1742
		{
1743
			$themes = explode(',', $modSettings['enableThemes']);
1744
			if (!in_array($id_theme, $themes))
1745
				$id_theme = $modSettings['theme_guests'];
1746
			else
1747
				$id_theme = (int) $id_theme;
1748
		}
1749
		else
1750
			$id_theme = (int) $id_theme;
1751
1752
		$member = empty($user_info['id']) ? -1 : $user_info['id'];
1753
1754
		// Disable image proxy if we don't have SSL enabled
1755
		if (empty($modSettings['force_ssl']) || $modSettings['force_ssl'] < 2)
1756
			$image_proxy_enabled = false;
1757
1758
		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'])
1759
		{
1760
			$themeData = $temp;
1761
			$flag = true;
1762
		}
1763
		elseif (($temp = cache_get_data('theme_settings-' . $id_theme, 90)) != null && time() - 60 > $modSettings['settings_updated'])
1764
			$themeData = $temp + array($member => array());
1765
		else
1766
			$themeData = array(-1 => array(), 0 => array(), $member => array());
1767
1768
		if (empty($flag))
1769
		{
1770
			// Load variables from the current or default theme, global or this user's.
1771
			$result = $smcFunc['db_query']('', '
1772
				SELECT variable, value, id_member, id_theme
1773
				FROM {db_prefix}themes
1774
				WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1775
					AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1776
				array(
1777
					'id_theme' => $id_theme,
1778
					'id_member' => $member,
1779
				)
1780
			);
1781
			// Pick between $settings and $options depending on whose data it is.
1782
			while ($row = $smcFunc['db_fetch_assoc']($result))
1783
			{
1784
				// There are just things we shouldn't be able to change as members.
1785
				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')))
1786
					continue;
1787
1788
				// If this is the theme_dir of the default theme, store it.
1789
				if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1790
					$themeData[0]['default_' . $row['variable']] = $row['value'];
1791
1792
				// If this isn't set yet, is a theme option, or is not the default theme..
1793
				if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1794
					$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1795
			}
1796
			$smcFunc['db_free_result']($result);
1797
1798
			if (!empty($themeData[-1]))
1799
				foreach ($themeData[-1] as $k => $v)
1800
				{
1801
					if (!isset($themeData[$member][$k]))
1802
						$themeData[$member][$k] = $v;
1803
				}
1804
1805
			if (!empty($modSettings['cache_enable']) && $modSettings['cache_enable'] >= 2)
1806
				cache_put_data('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1807
			// Only if we didn't already load that part of the cache...
1808
			elseif (!isset($temp))
1809
				cache_put_data('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1810
		}
1811
1812
		$settings = $themeData[0];
1813
		$options = $themeData[$member];
1814
1815
		$settings['theme_id'] = $id_theme;
1816
1817
		$settings['actual_theme_url'] = $settings['theme_url'];
1818
		$settings['actual_images_url'] = $settings['images_url'];
1819
		$settings['actual_theme_dir'] = $settings['theme_dir'];
1820
1821
		$settings['template_dirs'] = array();
1822
		// This theme first.
1823
		$settings['template_dirs'][] = $settings['theme_dir'];
1824
1825
		// Based on theme (if there is one).
1826
		if (!empty($settings['base_theme_dir']))
1827
			$settings['template_dirs'][] = $settings['base_theme_dir'];
1828
1829
		// Lastly the default theme.
1830 View Code Duplication
		if ($settings['theme_dir'] != $settings['default_theme_dir'])
1831
			$settings['template_dirs'][] = $settings['default_theme_dir'];
1832
	}
1833
1834
1835
	if (!$initialize)
1836
		return;
1837
1838
	// Check to see if we're forcing SSL
1839
	if (!empty($modSettings['force_ssl']) && $modSettings['force_ssl'] == 2 && empty($maintenance) &&
1840
		(!isset($_SERVER['HTTPS']) || $_SERVER['HTTPS'] == 'off') && SMF != 'SSI')
1841
	{
1842
		if (isset($_GET['sslRedirect']))
1843
		{
1844
			loadLanguage('Errors');
1845
			fatal_lang_error($txt['login_ssl_required']);
1846
		}
1847
1848
		redirectexit(strtr($_SERVER['REQUEST_URL'], array('http://' => 'https://')) . (strpos($_SERVER['REQUEST_URL'], '?') > 0 ? ';' : '?') . 'sslRedirect');
1849
	}
1850
1851
	// Check to see if they're accessing it from the wrong place.
1852
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1853
	{
1854
		$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://';
1855
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1856
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1857
		if ($temp != '/')
1858
			$detected_url .= $temp;
1859
	}
1860
	if (isset($detected_url) && $detected_url != $boardurl)
1861
	{
1862
		// Try #1 - check if it's in a list of alias addresses.
1863
		if (!empty($modSettings['forum_alias_urls']))
1864
		{
1865
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1866
1867
			foreach ($aliases as $alias)
1868
			{
1869
				// Rip off all the boring parts, spaces, etc.
1870
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1871
					$do_fix = true;
1872
			}
1873
		}
1874
1875
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1876
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && SMF != 'SSI')
1877
		{
1878
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1879
			if (empty($_GET))
1880
				redirectexit('wwwRedirect');
1881
			else
1882
			{
1883
				$k = key($_GET);
1884
				$v = current($_GET);
1885
1886
				if ($k != 'wwwRedirect')
1887
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1888
			}
1889
		}
1890
1891
		// #3 is just a check for SSL...
1892
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1893
			$do_fix = true;
1894
1895
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1896
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1897
		{
1898
			// Caching is good ;).
1899
			$oldurl = $boardurl;
1900
1901
			// Fix $boardurl and $scripturl.
1902
			$boardurl = $detected_url;
1903
			$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1904
			$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1905
1906
			// Fix the theme urls...
1907
			$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1908
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1909
			$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1910
			$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1911
			$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1912
			$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1913
1914
			// And just a few mod settings :).
1915
			$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1916
			$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1917
			$modSettings['custom_avatar_url'] = strtr($modSettings['custom_avatar_url'], array($oldurl => $boardurl));
1918
1919
			// Clean up after loadBoard().
1920
			if (isset($board_info['moderators']))
1921
			{
1922
				foreach ($board_info['moderators'] as $k => $dummy)
1923
				{
1924
					$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1925
					$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1926
				}
1927
			}
1928
			foreach ($context['linktree'] as $k => $dummy)
1929
				$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1930
		}
1931
	}
1932
	// Set up the contextual user array.
1933
	if (!empty($user_info))
1934
	{
1935
		$context['user'] = array(
1936
			'id' => $user_info['id'],
1937
			'is_logged' => !$user_info['is_guest'],
1938
			'is_guest' => &$user_info['is_guest'],
1939
			'is_admin' => &$user_info['is_admin'],
1940
			'is_mod' => &$user_info['is_mod'],
1941
			// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1942
			'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'])))),
1943
			'name' => $user_info['username'],
1944
			'language' => $user_info['language'],
1945
			'email' => $user_info['email'],
1946
			'ignoreusers' => $user_info['ignoreusers'],
1947
		);
1948
		if (!$context['user']['is_guest'])
1949
			$context['user']['name'] = $user_info['name'];
1950 View Code Duplication
		elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1951
			$context['user']['name'] = $txt['guest_title'];
1952
1953
		// Determine the current smiley set.
1954
		$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'];
1955
		$context['user']['smiley_set'] = $user_info['smiley_set'];
1956
	}
1957
	else
1958
	{
1959
		// What to do when there is no $user_info (e.g., an error very early in the login process)
1960
		$context['user'] = array(
1961
			'id' => -1,
1962
			'is_logged' => false,
1963
			'is_guest' => true,
1964
			'is_mod' => false,
1965
			'can_mod' => false,
1966
			'name' => $txt['guest_title'],
1967
			'language' => $language,
1968
			'email' => '',
1969
			'ignoreusers' => array(),
1970
		);
1971
		// Note we should stuff $user_info with some guest values also...
1972
		$user_info = array(
1973
			'id' => 0,
1974
			'is_guest' => true,
1975
			'is_admin' => false,
1976
			'is_mod' => false,
1977
			'username' => $txt['guest_title'],
1978
			'language' => $language,
1979
			'email' => '',
1980
			'smiley_set' => '',
1981
			'permissions' => array(),
1982
			'groups' => array(),
1983
			'ignoreusers' => array(),
1984
			'possibly_robot' => true,
1985
			'time_offset' => 0,
1986
			'time_format' => $modSettings['time_format'],
1987
		);
1988
	}
1989
1990
	// Some basic information...
1991
	if (!isset($context['html_headers']))
1992
		$context['html_headers'] = '';
1993
	if (!isset($context['javascript_files']))
1994
		$context['javascript_files'] = array();
1995
	if (!isset($context['css_files']))
1996
		$context['css_files'] = array();
1997
	if (!isset($context['css_header']))
1998
		$context['css_header'] = array();
1999
	if (!isset($context['javascript_inline']))
2000
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
2001
	if (!isset($context['javascript_vars']))
2002
		$context['javascript_vars'] = array();
2003
2004
	$context['login_url'] = (!empty($modSettings['force_ssl']) && $modSettings['force_ssl'] < 2 ? strtr($scripturl, array('http://' => 'https://')) : $scripturl) . '?action=login2';
2005
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
2006
	$context['session_var'] = $_SESSION['session_var'];
2007
	$context['session_id'] = $_SESSION['session_value'];
2008
	$context['forum_name'] = $mbname;
2009
	$context['forum_name_html_safe'] = $smcFunc['htmlspecialchars']($context['forum_name']);
2010
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? '' : $smcFunc['htmlspecialchars']($settings['header_logo_url']);
2011
	$context['current_action'] = isset($_REQUEST['action']) ? $smcFunc['htmlspecialchars']($_REQUEST['action']) : null;
2012
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
2013
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
2014
	if (isset($modSettings['load_average']))
2015
		$context['load_average'] = $modSettings['load_average'];
2016
2017
	// Detect the browser. This is separated out because it's also used in attachment downloads
2018
	detectBrowser();
2019
2020
	// Set the top level linktree up.
2021
	// Note that if we're dealing with certain very early errors (e.g., login) the linktree might not be set yet...
2022
	if (empty($context['linktree']))
2023
		$context['linktree'] = array();
2024
	array_unshift($context['linktree'], array(
2025
		'url' => $scripturl,
2026
		'name' => $context['forum_name_html_safe']
2027
	));
2028
2029
	// This allows sticking some HTML on the page output - useful for controls.
2030
	$context['insert_after_template'] = '';
2031
2032
	if (!isset($txt))
2033
		$txt = array();
2034
2035
	$simpleActions = array(
2036
		'findmember',
2037
		'helpadmin',
2038
		'printpage',
2039
		'spellcheck',
2040
	);
2041
2042
	// Parent action => array of areas
2043
	$simpleAreas = array(
2044
		'profile' => array('popup', 'alerts_popup',),
2045
	);
2046
2047
	// Parent action => array of subactions
2048
	$simpleSubActions = array(
2049
		'pm' => array('popup',),
2050
		'signup' => array('usernamecheck'),
2051
	);
2052
2053
	// Extra params like ;preview ;js, etc.
2054
	$extraParams = array(
2055
		'preview',
2056
		'splitjs',
2057
	);
2058
2059
	// Actions that specifically uses XML output.
2060
	$xmlActions = array(
2061
		'quotefast',
2062
		'jsmodify',
2063
		'xmlhttp',
2064
		'post2',
2065
		'suggest',
2066
		'stats',
2067
		'notifytopic',
2068
		'notifyboard',
2069
	);
2070
2071
	call_integration_hook('integrate_simple_actions', array(&$simpleActions, &$simpleAreas, &$simpleSubActions, &$extraParams, &$xmlActions));
2072
2073
	$context['simple_action'] = in_array($context['current_action'], $simpleActions) ||
2074
	(isset($simpleAreas[$context['current_action']]) && isset($_REQUEST['area']) && in_array($_REQUEST['area'], $simpleAreas[$context['current_action']])) ||
2075
	(isset($simpleSubActions[$context['current_action']]) && in_array($context['current_subaction'], $simpleSubActions[$context['current_action']]));
2076
2077
	// See if theres any extra param to check.
2078
	$requiresXML = false;
2079
	foreach ($extraParams as $key => $extra)
2080
		if (isset($_REQUEST[$extra]))
2081
			$requiresXML = true;
2082
2083
	// Output is fully XML, so no need for the index template.
2084
	if (isset($_REQUEST['xml']) && (in_array($context['current_action'], $xmlActions) || $requiresXML))
2085
	{
2086
		loadLanguage('index+Modifications');
2087
		loadTemplate('Xml');
2088
		$context['template_layers'] = array();
2089
	}
2090
2091
	// These actions don't require the index template at all.
2092
	elseif (!empty($context['simple_action']))
2093
	{
2094
		loadLanguage('index+Modifications');
2095
		$context['template_layers'] = array();
2096
	}
2097
2098
	else
2099
	{
2100
		// Custom templates to load, or just default?
2101
		if (isset($settings['theme_templates']))
2102
			$templates = explode(',', $settings['theme_templates']);
2103
		else
2104
			$templates = array('index');
2105
2106
		// Load each template...
2107
		foreach ($templates as $template)
2108
			loadTemplate($template);
2109
2110
		// ...and attempt to load their associated language files.
2111
		$required_files = implode('+', array_merge($templates, array('Modifications')));
2112
		loadLanguage($required_files, '', false);
2113
2114
		// Custom template layers?
2115
		if (isset($settings['theme_layers']))
2116
			$context['template_layers'] = explode(',', $settings['theme_layers']);
2117
		else
2118
			$context['template_layers'] = array('html', 'body');
2119
	}
2120
2121
	// Initialize the theme.
2122
	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...
2123
2124
	// Allow overriding the board wide time/number formats.
2125
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
2126
		$user_info['time_format'] = $txt['time_format'];
2127
2128
	// Set the character set from the template.
2129
	$context['character_set'] = empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set'];
2130
	$context['utf8'] = $context['character_set'] === 'UTF-8';
2131
	$context['right_to_left'] = !empty($txt['lang_rtl']);
2132
2133
	// Guests may still need a name.
2134 View Code Duplication
	if ($context['user']['is_guest'] && empty($context['user']['name']))
2135
		$context['user']['name'] = $txt['guest_title'];
2136
2137
	// Any theme-related strings that need to be loaded?
2138
	if (!empty($settings['require_theme_strings']))
2139
		loadLanguage('ThemeStrings', '', false);
2140
2141
	// Make a special URL for the language.
2142
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
2143
2144
	// And of course, let's load the default CSS file.
2145
	loadCSSFile('index.css', array('minimize' => true), 'smf_index');
2146
2147
	// Here is my luvly Responsive CSS
2148
	loadCSSFile('responsive.css', array('force_current' => false, 'validate' => true, 'minimize' => true), 'smf_responsive');
2149
2150
	if ($context['right_to_left'])
2151
		loadCSSFile('rtl.css', array(), 'smf_rtl');
2152
2153
	// We allow theme variants, because we're cool.
2154
	$context['theme_variant'] = '';
2155
	$context['theme_variant_url'] = '';
2156
	if (!empty($settings['theme_variants']))
2157
	{
2158
		// Overriding - for previews and that ilk.
2159
		if (!empty($_REQUEST['variant']))
2160
			$_SESSION['id_variant'] = $_REQUEST['variant'];
2161
		// User selection?
2162
		if (empty($settings['disable_user_variant']) || allowedTo('admin_forum'))
2163
			$context['theme_variant'] = !empty($_SESSION['id_variant']) ? $_SESSION['id_variant'] : (!empty($options['theme_variant']) ? $options['theme_variant'] : '');
2164
		// If not a user variant, select the default.
2165
		if ($context['theme_variant'] == '' || !in_array($context['theme_variant'], $settings['theme_variants']))
2166
			$context['theme_variant'] = !empty($settings['default_variant']) && in_array($settings['default_variant'], $settings['theme_variants']) ? $settings['default_variant'] : $settings['theme_variants'][0];
2167
2168
		// Do this to keep things easier in the templates.
2169
		$context['theme_variant'] = '_' . $context['theme_variant'];
2170
		$context['theme_variant_url'] = $context['theme_variant'] . '/';
2171
2172
		if (!empty($context['theme_variant']))
2173
		{
2174
			loadCSSFile('index' . $context['theme_variant'] . '.css', array(), 'smf_index' . $context['theme_variant']);
2175
			if ($context['right_to_left'])
2176
				loadCSSFile('rtl' . $context['theme_variant'] . '.css', array(), 'smf_rtl' . $context['theme_variant']);
2177
		}
2178
	}
2179
2180
	// Let's be compatible with old themes!
2181
	if (!function_exists('template_html_above') && in_array('html', $context['template_layers']))
2182
		$context['template_layers'] = array('main');
2183
2184
	$context['tabindex'] = 1;
2185
2186
	// Compatibility.
2187
	if (!isset($settings['theme_version']))
2188
		$modSettings['memberCount'] = $modSettings['totalMembers'];
2189
2190
	// Default JS variables for use in every theme
2191
	$context['javascript_vars'] = array(
2192
		'smf_theme_url' => '"' . $settings['theme_url'] . '"',
2193
		'smf_default_theme_url' => '"' . $settings['default_theme_url'] . '"',
2194
		'smf_images_url' => '"' . $settings['images_url'] . '"',
2195
		'smf_smileys_url' => '"' . $modSettings['smileys_url'] . '"',
2196
		'smf_scripturl' => '"' . $scripturl . '"',
2197
		'smf_iso_case_folding' => $context['server']['iso_case_folding'] ? 'true' : 'false',
2198
		'smf_charset' => '"' . $context['character_set'] . '"',
2199
		'smf_session_id' => '"' . $context['session_id'] . '"',
2200
		'smf_session_var' => '"' . $context['session_var'] . '"',
2201
		'smf_member_id' => $context['user']['id'],
2202
		'ajax_notification_text' => JavaScriptEscape($txt['ajax_in_progress']),
2203
		'help_popup_heading_text' => JavaScriptEscape($txt['help_popup']),
2204
	);
2205
2206
	// Add the JQuery library to the list of files to load.
2207
	if (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'cdn')
2208
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2209
2210
	elseif (isset($modSettings['jquery_source']) && $modSettings['jquery_source'] == 'local')
2211
		loadJavaScriptFile('jquery-3.2.1.min.js', array('seed' => false), 'smf_jquery');
2212
2213
	elseif (isset($modSettings['jquery_source'], $modSettings['jquery_custom']) && $modSettings['jquery_source'] == 'custom')
2214
		loadJavaScriptFile($modSettings['jquery_custom'], array('external' => true), 'smf_jquery');
2215
2216
	// Auto loading? template_javascript() will take care of the local half of this.
2217
	else
2218
		loadJavaScriptFile('https://ajax.googleapis.com/ajax/libs/jquery/3.2.1/jquery.min.js', array('external' => true), 'smf_jquery');
2219
2220
	// Queue our JQuery plugins!
2221
	loadJavaScriptFile('smf_jquery_plugins.js', array('minimize' => true), 'smf_jquery_plugins');
2222
	if (!$user_info['is_guest'])
2223
	{
2224
		loadJavaScriptFile('jquery.custom-scrollbar.js', array(), 'smf_jquery_scrollbar');
2225
		loadCSSFile('jquery.custom-scrollbar.css', array('force_current' => false, 'validate' => true), 'smf_scrollbar');
2226
	}
2227
2228
	// script.js and theme.js, always required, so always add them! Makes index.template.php cleaner and all.
2229
	loadJavaScriptFile('script.js', array('defer' => false, 'minimize' => true), 'smf_script');
2230
	loadJavaScriptFile('theme.js', array('minimize' => true), 'smf_theme');
2231
2232
	// If we think we have mail to send, let's offer up some possibilities... robots get pain (Now with scheduled task support!)
2233
	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())
2234
	{
2235
		if (isBrowser('possibly_robot'))
2236
		{
2237
			// @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...
2238
			require_once($sourcedir . '/ScheduledTasks.php');
2239
2240
			// What to do, what to do?!
2241
			if (empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time())
2242
				AutoTask();
2243
			else
2244
				ReduceMailQueue();
2245
		}
2246
		else
2247
		{
2248
			$type = empty($modSettings['next_task_time']) || $modSettings['next_task_time'] < time() ? 'task' : 'mailq';
2249
			$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...
2250
2251
			addInlineJavaScript('
2252
		function smfAutoTask()
2253
		{
2254
			$.get(smf_scripturl + "?scheduled=' . $type . ';ts=' . $ts . '");
2255
		}
2256
		window.setTimeout("smfAutoTask();", 1);');
2257
		}
2258
	}
2259
2260
	// And we should probably trigger the cron too.
2261
	if (empty($modSettings['cron_is_real_cron']))
2262
	{
2263
		$ts = time();
2264
		$ts -= $ts % 15;
2265
		addInlineJavaScript('
2266
	function triggerCron()
2267
	{
2268
		$.get(' . JavaScriptEscape($boardurl) . ' + "/cron.php?ts=' . $ts . '");
2269
	}
2270
	window.setTimeout(triggerCron, 1);', true);
2271
	}
2272
2273
	// Filter out the restricted boards from the linktree
2274
	if (!$user_info['is_admin'] && !empty($board))
2275
	{
2276
		foreach ($context['linktree'] as $k => $element)
2277
		{
2278
			if (!empty($element['groups']) &&
2279
				(count(array_intersect($user_info['groups'], $element['groups'])) == 0 ||
2280
				(!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $element['deny_groups'])) != 0)))
2281
			{
2282
				$context['linktree'][$k]['name'] = $txt['restricted_board'];
2283
				$context['linktree'][$k]['extra_before'] = '<i>';
2284
				$context['linktree'][$k]['extra_after'] = '</i>';
2285
				unset($context['linktree'][$k]['url']);
2286
			}
2287
		}
2288
	}
2289
2290
	// Any files to include at this point?
2291 View Code Duplication
	if (!empty($modSettings['integrate_theme_include']))
2292
	{
2293
		$theme_includes = explode(',', $modSettings['integrate_theme_include']);
2294
		foreach ($theme_includes as $include)
2295
		{
2296
			$include = strtr(trim($include), array('$boarddir' => $boarddir, '$sourcedir' => $sourcedir, '$themedir' => $settings['theme_dir']));
2297
			if (file_exists($include))
2298
				require_once($include);
2299
		}
2300
	}
2301
2302
	// Call load theme integration functions.
2303
	call_integration_hook('integrate_load_theme');
2304
2305
	// We are ready to go.
2306
	$context['theme_loaded'] = true;
2307
}
2308
2309
/**
2310
 * Load a template - if the theme doesn't include it, use the default.
2311
 * What this function does:
2312
 *  - loads a template file with the name template_name from the current, default, or base theme.
2313
 *  - detects a wrong default theme directory and tries to work around it.
2314
 *
2315
 * @uses the template_include() function to include the file.
2316
 * @param string $template_name The name of the template to load
2317
 * @param array|string $style_sheets The name of a single stylesheet or an array of names of stylesheets to load
2318
 * @param bool $fatal If true, dies with an error message if the template cannot be found
2319
 * @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...
2320
 */
2321
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
2322
{
2323
	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...
2324
2325
	// Do any style sheets first, cause we're easy with those.
2326
	if (!empty($style_sheets))
2327
	{
2328
		if (!is_array($style_sheets))
2329
			$style_sheets = array($style_sheets);
2330
2331
		foreach ($style_sheets as $sheet)
2332
			loadCSSFile($sheet . '.css', array(), $sheet);
2333
	}
2334
2335
	// No template to load?
2336
	if ($template_name === false)
2337
		return true;
2338
2339
	$loaded = false;
2340
	foreach ($settings['template_dirs'] as $template_dir)
2341
	{
2342
		if (file_exists($template_dir . '/' . $template_name . '.template.php'))
2343
		{
2344
			$loaded = true;
2345
			template_include($template_dir . '/' . $template_name . '.template.php', true);
2346
			break;
2347
		}
2348
	}
2349
2350
	if ($loaded)
2351
	{
2352
		if ($db_show_debug === true)
2353
			$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 2340. 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...
2354
2355
		// If they have specified an initialization function for this template, go ahead and call it now.
2356
		if (function_exists('template_' . $template_name . '_init'))
2357
			call_user_func('template_' . $template_name . '_init');
2358
	}
2359
	// Hmmm... doesn't exist?!  I don't suppose the directory is wrong, is it?
2360
	elseif (!file_exists($settings['default_theme_dir']) && file_exists($boarddir . '/Themes/default'))
2361
	{
2362
		$settings['default_theme_dir'] = $boarddir . '/Themes/default';
2363
		$settings['template_dirs'][] = $settings['default_theme_dir'];
2364
2365 View Code Duplication
		if (!empty($context['user']['is_admin']) && !isset($_GET['th']))
2366
		{
2367
			loadLanguage('Errors');
2368
			echo '
2369
<div class="alert errorbox">
2370
	<a href="', $scripturl . '?action=admin;area=theme;sa=list;' . $context['session_var'] . '=' . $context['session_id'], '" class="alert">', $txt['theme_dir_wrong'], '</a>
2371
</div>';
2372
		}
2373
2374
		loadTemplate($template_name);
2375
	}
2376
	// Cause an error otherwise.
2377
	elseif ($template_name != 'Errors' && $template_name != 'index' && $fatal)
2378
		fatal_lang_error('theme_template_error', 'template', array((string) $template_name));
2379
	elseif ($fatal)
2380
		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'));
2381
	else
2382
		return false;
2383
}
2384
2385
/**
2386
 * Load a sub-template.
2387
 * What it does:
2388
 * 	- loads the sub template specified by sub_template_name, which must be in an already-loaded template.
2389
 *  - if ?debug is in the query string, shows administrators a marker after every sub template
2390
 *	for debugging purposes.
2391
 *
2392
 * @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...
2393
 *
2394
 * @param string $sub_template_name The name of the sub-template to load
2395
 * @param bool $fatal Whether to die with an error if the sub-template can't be loaded
2396
 */
2397
function loadSubTemplate($sub_template_name, $fatal = false)
2398
{
2399
	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...
2400
2401
	if ($db_show_debug === true)
2402
		$context['debug']['sub_templates'][] = $sub_template_name;
2403
2404
	// Figure out what the template function is named.
2405
	$theme_function = 'template_' . $sub_template_name;
2406
	if (function_exists($theme_function))
2407
		$theme_function();
2408
	elseif ($fatal === false)
2409
		fatal_lang_error('theme_template_error', 'template', array((string) $sub_template_name));
2410
	elseif ($fatal !== 'ignore')
2411
		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'));
2412
2413
	// Are we showing debugging for templates?  Just make sure not to do it before the doctype...
2414
	if (allowedTo('admin_forum') && isset($_REQUEST['debug']) && !in_array($sub_template_name, array('init', 'main_below')) && ob_get_length() > 0 && !isset($_REQUEST['xml']))
2415
	{
2416
		echo '
2417
<div class="warningbox">---- ', $sub_template_name, ' ends ----</div>';
2418
	}
2419
}
2420
2421
/**
2422
 * Add a CSS file for output later
2423
 *
2424
 * @param string $fileName The name of the file to load
2425
 * @param array $params An array of parameters
2426
 * Keys are the following:
2427
 * 	- ['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
2428
 * 	- ['default_theme'] (true/false): force use of default theme url
2429
 * 	- ['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
2430
 *  - ['validate'] (true/false): if true script will validate the local file exists
2431
 *  - ['rtl'] (string): additional file to load in RTL mode
2432
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2433
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2434
 * @param string $id An ID to stick on the end of the filename for caching purposes
2435
 */
2436
function loadCSSFile($fileName, $params = array(), $id = '')
2437
{
2438
	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...
2439
2440
	$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']) : '');
2441
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2442
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2443
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2444
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2445
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2446
2447
	// If this is an external file, automatically set this to false.
2448
	if (!empty($params['external']))
2449
		$params['minimize'] = false;
2450
2451
	// Account for shorthand like admin.css?alp21 filenames
2452
	$has_seed = strpos($fileName, '.css?');
2453
	$id = empty($id) ? strtr(basename(str_replace('.css', '', $fileName)), '?', '_') : $id;
2454
2455
	// Is this a local file?
2456 View Code Duplication
	if (empty($params['external']))
2457
	{
2458
		// Are we validating the the file exists?
2459
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/css/' . $fileName))
2460
		{
2461
			// Maybe the default theme has it?
2462
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/css/' . $fileName))
2463
			{
2464
				$fileUrl = $settings['default_theme_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2465
				$filePath = $settings['default_theme_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2466
			}
2467
2468
			else
2469
				$fileUrl = false;
2470
		}
2471
2472
		else
2473
		{
2474
			$fileUrl = $settings[$themeRef . '_url'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2475
			$filePath = $settings[$themeRef . '_dir'] . '/css/' . $fileName . ($has_seed ? '' : $params['seed']);
2476
		}
2477
	}
2478
2479
	// An external file doesn't have a filepath. Mock one for simplicity.
2480
	else
2481
	{
2482
		$fileUrl = $fileName;
2483
		$filePath = $fileName;
2484
	}
2485
2486
	// Add it to the array for use in the template
2487 View Code Duplication
	if (!empty($fileName))
2488
		$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...
2489
2490
	if (!empty($context['right_to_left']) && !empty($params['rtl']))
2491
		loadCSSFile($params['rtl'], array_diff_key($params, array('rtl' => 0)));
2492
}
2493
2494
/**
2495
 * Add a block of inline css code to be executed later
2496
 *
2497
 * - only use this if you have to, generally external css files are better, but for very small changes
2498
 *   or for scripts that require help from PHP/whatever, this can be useful.
2499
 * - all code added with this function is added to the same <style> tag so do make sure your css is valid!
2500
 *
2501
 * @param string $css Some css code
2502
 * @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...
2503
 */
2504
function addInlineCss($css)
2505
{
2506
	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...
2507
2508
	// Gotta add something...
2509
	if (empty($css))
2510
		return false;
2511
2512
	$context['css_header'][] = $css;
2513
}
2514
2515
/**
2516
 * Add a Javascript file for output later
2517
 *
2518
 * @param string $fileName The name of the file to load
2519
 * @param array $params An array of parameter info
2520
 * Keys are the following:
2521
 * 	- ['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
2522
 * 	- ['default_theme'] (true/false): force use of default theme url
2523
 * 	- ['defer'] (true/false): define if the file should load in <head> or before the closing <html> tag
2524
 * 	- ['force_current'] (true/false): if this is false, we will attempt to load the file from the
2525
 *	default theme if not found in the current theme
2526
 *	- ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2527
 *  - ['validate'] (true/false): if true script will validate the local file exists
2528
 *  - ['seed'] (true/false/string): if true or null, use cache stale, false do not, or used a supplied string
2529
 *  - ['minimize'] boolean to add your file to the main minimized file. Useful when you have a file thats loaded everywhere and for everyone.
2530
 *
2531
 * @param string $id An ID to stick on the end of the filename
2532
 */
2533
function loadJavaScriptFile($fileName, $params = array(), $id = '')
2534
{
2535
	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...
2536
2537
	$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']) : '');
2538
	$params['force_current'] = isset($params['force_current']) ? $params['force_current'] : false;
2539
	$themeRef = !empty($params['default_theme']) ? 'default_theme' : 'theme';
2540
	$params['minimize'] = isset($params['minimize']) ? $params['minimize'] : false;
2541
	$params['external'] = isset($params['external']) ? $params['external'] : false;
2542
	$params['validate'] = isset($params['validate']) ? $params['validate'] : true;
2543
2544
	// If this is an external file, automatically set this to false.
2545
	if (!empty($params['external']))
2546
		$params['minimize'] = false;
2547
2548
	// Account for shorthand like admin.js?alp21 filenames
2549
	$has_seed = strpos($fileName, '.js?');
2550
	$id = empty($id) ? strtr(basename(str_replace('.js', '', $fileName)), '?', '_') : $id;
2551
2552
	// Is this a local file?
2553 View Code Duplication
	if (empty($params['external']))
2554
	{
2555
		// Are we validating it exists on disk?
2556
		if (!empty($params['validate']) && !file_exists($settings[$themeRef . '_dir'] . '/scripts/' . $fileName))
2557
		{
2558
			// Can't find it in this theme, how about the default?
2559
			if ($themeRef === 'theme' && !$params['force_current'] && file_exists($settings['default_theme_dir'] . '/scripts/' . $fileName))
2560
			{
2561
				$fileUrl = $settings['default_theme_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2562
				$filePath = $settings['default_theme_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2563
			}
2564
2565
			else
2566
			{
2567
				$fileUrl = false;
2568
				$filePath = false;
2569
			}
2570
		}
2571
2572
		else
2573
		{
2574
			$fileUrl = $settings[$themeRef . '_url'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2575
			$filePath = $settings[$themeRef . '_dir'] . '/scripts/' . $fileName . ($has_seed ? '' : $params['seed']);
2576
		}
2577
	}
2578
2579
	// An external file doesn't have a filepath. Mock one for simplicity.
2580
	else
2581
	{
2582
		$fileUrl = $fileName;
2583
		$filePath = $fileName;
2584
	}
2585
2586
	// Add it to the array for use in the template
2587 View Code Duplication
	if (!empty($fileName))
2588
		$context['javascript_files'][$id] = array('fileUrl' => $fileUrl, 'filePath' => $filePath, 'fileName' => $fileName, 'options' => $params);
2589
}
2590
2591
/**
2592
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2593
 * Cleaner and easier (for modders) than to use the function below.
2594
 *
2595
 * @param string $key The key for this variable
2596
 * @param string $value The value
2597
 * @param bool $escape Whether or not to escape the value
2598
 */
2599
function addJavaScriptVar($key, $value, $escape = false)
2600
{
2601
	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...
2602
2603
	if (!empty($key) && (!empty($value) || $value === '0'))
2604
		$context['javascript_vars'][$key] = !empty($escape) ? JavaScriptEscape($value) : $value;
2605
}
2606
2607
/**
2608
 * Add a block of inline Javascript code to be executed later
2609
 *
2610
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2611
 *   or for scripts that require help from PHP/whatever, this can be useful.
2612
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2613
 *
2614
 * @param string $javascript Some JS code
2615
 * @param bool $defer Whether the script should load in <head> or before the closing <html> tag
2616
 * @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...
2617
 */
2618
function addInlineJavaScript($javascript, $defer = false)
2619
{
2620
	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...
2621
2622
	if (empty($javascript))
2623
		return false;
2624
2625
	$context['javascript_inline'][($defer === true ? 'defer' : 'standard')][] = $javascript;
2626
}
2627
2628
/**
2629
 * Load a language file.  Tries the current and default themes as well as the user and global languages.
2630
 *
2631
 * @param string $template_name The name of a template file
2632
 * @param string $lang A specific language to load this file from
2633
 * @param bool $fatal Whether to die with an error if it can't be loaded
2634
 * @param bool $force_reload Whether to load the file again if it's already loaded
2635
 * @return string The language actually loaded.
2636
 */
2637
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2638
{
2639
	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...
2640
	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...
2641
	static $already_loaded = array();
2642
2643
	// Default to the user's language.
2644
	if ($lang == '')
2645
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2646
2647
	// Do we want the English version of language file as fallback?
2648
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2649
		loadLanguage($template_name, 'english', false);
2650
2651
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2652
		return $lang;
2653
2654
	// Make sure we have $settings - if not we're in trouble and need to find it!
2655
	if (empty($settings['default_theme_dir']))
2656
	{
2657
		require_once($sourcedir . '/ScheduledTasks.php');
2658
		loadEssentialThemeData();
2659
	}
2660
2661
	// What theme are we in?
2662
	$theme_name = basename($settings['theme_url']);
2663
	if (empty($theme_name))
2664
		$theme_name = 'unknown';
2665
2666
	// For each file open it up and write it out!
2667
	foreach (explode('+', $template_name) as $template)
2668
	{
2669
		// Obviously, the current theme is most important to check.
2670
		$attempts = array(
2671
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2672
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2673
		);
2674
2675
		// Do we have a base theme to worry about?
2676
		if (isset($settings['base_theme_dir']))
2677
		{
2678
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2679
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2680
		}
2681
2682
		// Fall back on the default theme if necessary.
2683
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2684
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2685
2686
		// Fall back on the English language if none of the preferred languages can be found.
2687
		if (!in_array('english', array($lang, $language)))
2688
		{
2689
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2690
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2691
		}
2692
2693
		// Try to find the language file.
2694
		$found = false;
2695
		foreach ($attempts as $k => $file)
2696
		{
2697
			if (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2698
			{
2699
				// Include it!
2700
				template_include($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2701
2702
				// Note that we found it.
2703
				$found = true;
2704
2705
				// setlocale is required for basename() & pathinfo() to work properly on the selected language
2706
				if (!empty($txt['lang_locale']) && !empty($modSettings['global_character_set']))
2707
					setlocale(LC_CTYPE, $txt['lang_locale'] . '.' . $modSettings['global_character_set']);
2708
2709
				break;
2710
			}
2711
		}
2712
2713
		// That couldn't be found!  Log the error, but *try* to continue normally.
2714
		if (!$found && $fatal)
2715
		{
2716
			log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2717
			break;
2718
		}
2719
2720
		// For the sake of backward compatibility
2721
		if (!empty($txt['emails']))
2722
		{
2723
			foreach ($txt['emails'] as $key => $value)
2724
			{
2725
				$txt[$key . '_subject'] = $value['subject'];
2726
				$txt[$key . '_body'] = $value['body'];
2727
			}
2728
			$txt['emails'] = array();
2729
		}
2730
		// For sake of backward compatibility: $birthdayEmails is supposed to be
2731
		// empty in a normal install. If it isn't it means the forum is using
2732
		// something "old" (it may be the translation, it may be a mod) and this
2733
		// code (like the piece above) takes care of converting it to the new format
2734
		if (!empty($birthdayEmails))
2735
		{
2736
			foreach ($birthdayEmails as $key => $value)
2737
			{
2738
				$txtBirthdayEmails[$key . '_subject'] = $value['subject'];
2739
				$txtBirthdayEmails[$key . '_body'] = $value['body'];
2740
				$txtBirthdayEmails[$key . '_author'] = $value['author'];
2741
			}
2742
			$birthdayEmails = array();
2743
		}
2744
	}
2745
2746
	// Keep track of what we're up to soldier.
2747
	if ($db_show_debug === true)
2748
		$context['debug']['language_files'][] = $template_name . '.' . $lang . ' (' . $theme_name . ')';
2749
2750
	// Remember what we have loaded, and in which language.
2751
	$already_loaded[$template_name] = $lang;
2752
2753
	// Return the language actually loaded.
2754
	return $lang;
2755
}
2756
2757
/**
2758
 * Get all parent boards (requires first parent as parameter)
2759
 * It finds all the parents of id_parent, and that board itself.
2760
 * Additionally, it detects the moderators of said boards.
2761
 *
2762
 * @param int $id_parent The ID of the parent board
2763
 * @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...
2764
 */
2765
function getBoardParents($id_parent)
2766
{
2767
	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...
2768
2769
	// First check if we have this cached already.
2770
	if (($boards = cache_get_data('board_parents-' . $id_parent, 480)) === null)
2771
	{
2772
		$boards = array();
2773
		$original_parent = $id_parent;
2774
2775
		// Loop while the parent is non-zero.
2776
		while ($id_parent != 0)
2777
		{
2778
			$result = $smcFunc['db_query']('', '
2779
				SELECT
2780
					b.id_parent, b.name, {int:board_parent} AS id_board, b.member_groups, b.deny_member_groups,
2781
					b.child_level, COALESCE(mem.id_member, 0) AS id_moderator, mem.real_name,
2782
					COALESCE(mg.id_group, 0) AS id_moderator_group, mg.group_name
2783
				FROM {db_prefix}boards AS b
2784
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2785
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2786
					LEFT JOIN {db_prefix}moderator_groups AS modgs ON (modgs.id_board = b.id_board)
2787
					LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = modgs.id_group)
2788
				WHERE b.id_board = {int:board_parent}',
2789
				array(
2790
					'board_parent' => $id_parent,
2791
				)
2792
			);
2793
			// In the EXTREMELY unlikely event this happens, give an error message.
2794
			if ($smcFunc['db_num_rows']($result) == 0)
2795
				fatal_lang_error('parent_not_found', 'critical');
2796
			while ($row = $smcFunc['db_fetch_assoc']($result))
2797
			{
2798
				if (!isset($boards[$row['id_board']]))
2799
				{
2800
					$id_parent = $row['id_parent'];
2801
					$boards[$row['id_board']] = array(
2802
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2803
						'name' => $row['name'],
2804
						'level' => $row['child_level'],
2805
						'groups' => explode(',', $row['member_groups']),
2806
						'deny_groups' => explode(',', $row['deny_member_groups']),
2807
						'moderators' => array(),
2808
						'moderator_groups' => array()
2809
					);
2810
				}
2811
				// If a moderator exists for this board, add that moderator for all children too.
2812 View Code Duplication
				if (!empty($row['id_moderator']))
2813
					foreach ($boards as $id => $dummy)
2814
					{
2815
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2816
							'id' => $row['id_moderator'],
2817
							'name' => $row['real_name'],
2818
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2819
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2820
						);
2821
					}
2822
2823
				// If a moderator group exists for this board, add that moderator group for all children too
2824 View Code Duplication
				if (!empty($row['id_moderator_group']))
2825
					foreach ($boards as $id => $dummy)
2826
					{
2827
						$boards[$id]['moderator_groups'][$row['id_moderator_group']] = array(
2828
							'id' => $row['id_moderator_group'],
2829
							'name' => $row['group_name'],
2830
							'href' => $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'],
2831
							'link' => '<a href="' . $scripturl . '?action=groups;sa=members;group=' . $row['id_moderator_group'] . '">' . $row['group_name'] . '</a>'
2832
						);
2833
					}
2834
			}
2835
			$smcFunc['db_free_result']($result);
2836
		}
2837
2838
		cache_put_data('board_parents-' . $original_parent, $boards, 480);
2839
	}
2840
2841
	return $boards;
2842
}
2843
2844
/**
2845
 * Attempt to reload our known languages.
2846
 * It will try to choose only utf8 or non-utf8 languages.
2847
 *
2848
 * @param bool $use_cache Whether or not to use the cache
2849
 * @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...
2850
 */
2851
function getLanguages($use_cache = true)
2852
{
2853
	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...
2854
2855
	// Either we don't use the cache, or its expired.
2856
	if (!$use_cache || ($context['languages'] = cache_get_data('known_languages', !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600)) == null)
2857
	{
2858
		// If we don't have our ucwords function defined yet, let's load the settings data.
2859
		if (empty($smcFunc['ucwords']))
2860
			reloadSettings();
2861
2862
		// If we don't have our theme information yet, let's get it.
2863
		if (empty($settings['default_theme_dir']))
2864
			loadTheme(0, false);
2865
2866
		// Default language directories to try.
2867
		$language_directories = array(
2868
			$settings['default_theme_dir'] . '/languages',
2869
		);
2870
		if (!empty($settings['actual_theme_dir']) && $settings['actual_theme_dir'] != $settings['default_theme_dir'])
2871
			$language_directories[] = $settings['actual_theme_dir'] . '/languages';
2872
2873
		// We possibly have a base theme directory.
2874
		if (!empty($settings['base_theme_dir']))
2875
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2876
2877
		// Remove any duplicates.
2878
		$language_directories = array_unique($language_directories);
2879
2880
		// Get a list of languages.
2881
		$langList = !empty($modSettings['langList']) ? $smcFunc['json_decode']($modSettings['langList'], true) : array();
2882
		$langList = is_array($langList) ? $langList : false;
2883
2884
		$catchLang = array();
2885
2886
		foreach ($language_directories as $language_dir)
2887
		{
2888
			// Can't look in here... doesn't exist!
2889
			if (!file_exists($language_dir))
2890
				continue;
2891
2892
			$dir = dir($language_dir);
2893
			while ($entry = $dir->read())
2894
			{
2895
				// Look for the index language file... For good measure skip any "index.language-utf8.php" files
2896
				if (!preg_match('~^index\.(.+[^-utf8])\.php$~', $entry, $matches))
2897
					continue;
2898
2899
				if (!empty($langList) && !empty($langList[$matches[1]]))
2900
					$langName = $langList[$matches[1]];
2901
2902
				else
2903
				{
2904
					$langName = $smcFunc['ucwords'](strtr($matches[1], array('_' => ' ')));
2905
2906
					// Get the line we need.
2907
					$fp = @fopen($language_dir . '/' . $entry);
2908
2909
					// Yay!
2910
					if ($fp)
2911
					{
2912
						while (($line = fgets($fp)) !== false)
2913
						{
2914
							preg_match('~\$txt\[\'native_name\'\] = \'(.+)\'\;~', $line, $matchNative);
2915
2916
							// Set the language's name.
2917
							if (!empty($matchNative) && !empty($matchNative[1]))
2918
							{
2919
								$langName = un_htmlspecialchars($matchNative[1]);
2920
								break;
2921
							}
2922
						}
2923
2924
						fclose($fp);
2925
					}
2926
2927
					// Catch the language name.
2928
					$catchLang[$matches[1]] = $langName;
2929
				}
2930
2931
				// Build this language entry.
2932
				$context['languages'][$matches[1]] = array(
2933
					'name' => $langName,
2934
					'selected' => false,
2935
					'filename' => $matches[1],
2936
					'location' => $language_dir . '/index.' . $matches[1] . '.php',
2937
				);
2938
			}
2939
			$dir->close();
2940
		}
2941
2942
		// Do we need to store the lang list?
2943
		if (empty($langList))
2944
			updateSettings(array('langList' => $smcFunc['json_encode']($catchLang)));
2945
2946
		// Let's cash in on this deal.
2947 View Code Duplication
		if (!empty($modSettings['cache_enable']))
2948
			cache_put_data('known_languages', $context['languages'], !empty($modSettings['cache_enable']) && $modSettings['cache_enable'] < 1 ? 86400 : 3600);
2949
	}
2950
2951
	return $context['languages'];
2952
}
2953
2954
/**
2955
 * Replace all vulgar words with respective proper words. (substring or whole words..)
2956
 * What this function does:
2957
 *  - it censors the passed string.
2958
 *  - if the theme setting allow_no_censored is on, and the theme option
2959
 *	show_no_censored is enabled, does not censor, unless force is also set.
2960
 *  - it caches the list of censored words to reduce parsing.
2961
 *
2962
 * @param string &$text The text to censor
2963
 * @param bool $force Whether to censor the text regardless of settings
2964
 * @return string The censored text
2965
 */
2966
function censorText(&$text, $force = false)
2967
{
2968
	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...
2969
	static $censor_vulgar = null, $censor_proper;
2970
2971
	if ((!empty($options['show_no_censored']) && !empty($modSettings['allow_no_censored']) && !$force) || empty($modSettings['censor_vulgar']) || trim($text) === '')
2972
		return $text;
2973
2974
	// If they haven't yet been loaded, load them.
2975
	if ($censor_vulgar == null)
2976
	{
2977
		$censor_vulgar = explode("\n", $modSettings['censor_vulgar']);
2978
		$censor_proper = explode("\n", $modSettings['censor_proper']);
2979
2980
		// Quote them for use in regular expressions.
2981
		if (!empty($modSettings['censorWholeWord']))
2982
		{
2983
			for ($i = 0, $n = count($censor_vulgar); $i < $n; $i++)
2984
			{
2985
				$censor_vulgar[$i] = str_replace(array('\\\\\\*', '\\*', '&', '\''), array('[*]', '[^\s]*?', '&amp;', '&#039;'), preg_quote($censor_vulgar[$i], '/'));
2986
				$censor_vulgar[$i] = '/(?<=^|\W)' . $censor_vulgar[$i] . '(?=$|\W)/' . (empty($modSettings['censorIgnoreCase']) ? '' : 'i') . ((empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8' ? 'u' : '');
2987
2988
				// @todo I'm thinking the old way is some kind of bug and this is actually fixing it.
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...
2989
				//if (strpos($censor_vulgar[$i], '\'') !== false)
2990
					//$censor_vulgar[$i] = str_replace('\'', '&#039;', $censor_vulgar[$i]);
0 ignored issues
show
Unused Code Comprehensibility introduced by
67% 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...
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;
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)
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
?>