Completed
Pull Request — development (#2528)
by Thorsten
15:29
created

Load.php ➔ reloadSettings()   F

Complexity

Conditions 28
Paths > 20000

Size

Total Lines 105
Code Lines 57

Duplication

Lines 6
Ratio 5.71 %

Code Coverage

Tests 1
CRAP Score 777.9313
Metric Value
cc 28
eloc 57
nc 35904
nop 0
dl 6
loc 105
rs 2
ccs 1
cts 68
cp 0.0147
crap 777.9313

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
0 ignored issues
show
Coding Style Compatibility introduced by
For compatibility and reusability of your code, PSR1 recommends that a file should introduce either new symbols (like classes, functions, etc.) or have side-effects (like outputting something, or including other files), but not both at the same time. The first symbol is defined on line 34 and the first side effect is on line 18.

The PSR-1: Basic Coding Standard recommends that a file should either introduce new symbols, that is classes, functions, constants or similar, or have side effects. Side effects are anything that executes logic, like for example printing output, changing ini settings or writing to a file.

The idea behind this recommendation is that merely auto-loading a class should not change the state of an application. It also promotes a cleaner style of programming and makes your code less prone to errors, because the logic is not spread out all over the place.

To learn more about the PSR-1, please see the PHP-FIG site on the PSR-1.

Loading history...
2
3
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * @name      ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause
9
 *
10
 * This file contains code covered by:
11
 * copyright:	2011 Simple Machines (http://www.simplemachines.org)
12
 * license:		BSD, See included LICENSE.TXT for terms and conditions.
13
 *
14
 * @version 1.1 beta 1
15
 *
16
 */
17
18
if (!defined('ELK'))
19
	die('No access...');
20
21
/**
22
 * Load the $modSettings array and many necessary forum settings.
23
 *
24
 * What it does:
25
 * - load the settings from cache if available, otherwise from the database.
26
 * - sets the timezone
27
 * - checks the load average settings if available.
28
 * - check whether post moderation is enabled.
29
 * - calls add_integration_function
30
 * - calls integrate_pre_include, integrate_pre_load,
31
 *
32
 * @global array $modSettings is a giant array of all of the forum-wide settings and statistics.
33
 */
34
function reloadSettings()
35
{
36
	global $modSettings;
37
38
	$db = database();
39
	$cache = Cache::instance();
40
	$hooks = Hooks::get();
41
42
	// Try to load it from the cache first; it'll never get cached if the setting is off.
43
	if (!$cache->getVar($modSettings, 'modSettings', 90))
44
	{
45
		$request = $db->query('', '
46
			SELECT variable, value
47
			FROM {db_prefix}settings',
48
			array(
49
			)
50
		);
51
		$modSettings = array();
52 1
		if (!$request)
53
			Errors::instance()->display_db_error();
54
		while ($row = $db->fetch_row($request))
55
			$modSettings[$row[0]] = $row[1];
56
		$db->free_result($request);
57
58
		// Do a few things to protect against missing settings or settings with invalid values...
59 View Code Duplication
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
60
			$modSettings['defaultMaxTopics'] = 20;
61 View Code Duplication
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
62
			$modSettings['defaultMaxMessages'] = 15;
63 View Code Duplication
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
64
			$modSettings['defaultMaxMembers'] = 30;
65
		if (empty($modSettings['subject_length']))
66
			$modSettings['subject_length'] = 24;
67
68
		$modSettings['warning_enable'] = $modSettings['warning_settings'][0];
69
70
		$cache->put('modSettings', $modSettings, 90);
71
	}
72
73
	$hooks->loadIntegrations();
74
75
	// Setting the timezone is a requirement for some functions in PHP >= 5.1.
76
	if (isset($modSettings['default_timezone']))
77
		date_default_timezone_set($modSettings['default_timezone']);
78
79
	// Check the load averages?
80
	if (!empty($modSettings['loadavg_enable']))
81
	{
82
		if (!$cache->getVar($modSettings['load_average'], 'loadavg', 90))
83
		{
84
			require_once(SUBSDIR . '/Server.subs.php');
85
			$modSettings['load_average'] = detectServerLoad();
86
87
			$cache->put('loadavg', $modSettings['load_average'], 90);
88
		}
89
90
		if ($modSettings['load_average'] !== false)
91
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
92
93
		// Let's have at least a zero
94
		if (empty($modSettings['loadavg_forum']) || $modSettings['load_average'] === false)
95
			$modSettings['current_load'] = 0;
96
		else
97
			$modSettings['current_load'] = $modSettings['load_average'];
98
99
		if (!empty($modSettings['loadavg_forum']) && $modSettings['current_load'] >= $modSettings['loadavg_forum'])
100
			Errors::instance()->display_loadavg_error();
101
	}
102
	else
103
		$modSettings['current_load'] = 0;
104
105
	// Is post moderation alive and well?
106
	$modSettings['postmod_active'] = isset($modSettings['admin_features']) ? in_array('pm', explode(',', $modSettings['admin_features'])) : true;
107
108
	// @deprecated since 1.0.6 compatibility setting for migration
109
	if (!isset($modSettings['avatar_max_height']))
110
		$modSettings['avatar_max_height'] = $modSettings['avatar_max_height_external'];
111
	if (!isset($modSettings['avatar_max_width']))
112
		$modSettings['avatar_max_width'] = $modSettings['avatar_max_width_external'];
113
114
	// Here to justify the name of this function. :P
115
	// It should be added to the install and upgrade scripts.
116
	// But since the converters need to be updated also. This is easier.
117
	if (empty($modSettings['currentAttachmentUploadDir']))
118
	{
119
		updateSettings(array(
120
			'attachmentUploadDir' => serialize(array(1 => $modSettings['attachmentUploadDir'])),
121
			'currentAttachmentUploadDir' => 1,
122
		));
123
	}
124
125
	// Integration is cool.
126
	if (defined('ELK_INTEGRATION_SETTINGS'))
127
	{
128
		$integration_settings = unserialize(ELK_INTEGRATION_SETTINGS);
129
		foreach ($integration_settings as $hook => $function)
130
			add_integration_function($hook, $function);
131
	}
132
133
	// Any files to pre include?
134
	call_integration_include_hook('integrate_pre_include');
135
136
	// Call pre load integration functions.
137
	call_integration_hook('integrate_pre_load');
138
}
139
140
/**
141
 * Load all the important user information.
142
 *
143
 * What it does:
144
 * - sets up the $user_info array
145
 * - assigns $user_info['query_wanna_see_board'] for what boards the user can see.
146
 * - first checks for cookie or integration validation.
147
 * - uses the current session if no integration function or cookie is found.
148
 * - checks password length, if member is activated and the login span isn't over.
149
 * - if validation fails for the user, $id_member is set to 0.
150
 * - updates the last visit time when needed.
151
 */
152
function loadUserSettings()
0 ignored issues
show
Coding Style introduced by
loadUserSettings uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadUserSettings uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadUserSettings uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadUserSettings uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
153
{
154
	global $context, $modSettings, $user_settings, $cookiename, $user_info, $language;
155
156
	$db = database();
157
	$cache = Cache::instance();
158
159
	// Check first the integration, then the cookie, and last the session.
160
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
161
	{
162
		$id_member = 0;
163
		foreach ($integration_ids as $integration_id)
164
		{
165
			$integration_id = (int) $integration_id;
166
			if ($integration_id > 0)
167
			{
168
				$id_member = $integration_id;
169
				$already_verified = true;
170
				break;
171
			}
172
		}
173
	}
174
	else
175
		$id_member = 0;
176
177
	// We'll need IPs and user agent and stuff, they came to visit us with!
178
	$req = request();
179
180
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
181
	{
182
		// Fix a security hole in PHP 4.3.9 and below...
183
		if (preg_match('~^a:[34]:\{i:0;i:\d{1,8};i:1;s:(0|64):"([a-fA-F0-9]{64})?";i:2;[id]:\d{1,14};(i:3;i:\d;)?\}$~i', $_COOKIE[$cookiename]) == 1)
184
		{
185
			list ($id_member, $password) = @unserialize($_COOKIE[$cookiename]);
0 ignored issues
show
Security Object Injection introduced by
$_COOKIE[$cookiename] can contain request data and is used in unserialized context(s) leading to a potential security vulnerability.

General Strategies to prevent injection

In general, it is advisable to prevent any user-data to reach this point. This can be done by white-listing certain values:

if ( ! in_array($value, array('this-is-allowed', 'and-this-too'), true)) {
    throw new \InvalidArgumentException('This input is not allowed.');
}

For numeric data, we recommend to explicitly cast the data:

$sanitized = (integer) $tainted;
Loading history...
186
			$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
187
		}
188
		else
189
			$id_member = 0;
190
	}
191
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && ($_SESSION['USER_AGENT'] == $req->user_agent() || !empty($modSettings['disableCheckUA'])))
192
	{
193
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
194
		list ($id_member, $password, $login_span) = @unserialize($_SESSION['login_' . $cookiename]);
195
		$id_member = !empty($id_member) && strlen($password) == 64 && $login_span > time() ? (int) $id_member : 0;
196
	}
197
198
	// Only load this stuff if the user isn't a guest.
199
	if ($id_member != 0)
200
	{
201
		// Is the member data cached?
202
		if (!$cache->checkLevel(2) || !$cache->getVar($user_settings, 'user_settings-' . $id_member, 60))
203
		{
204
			list ($user_settings) = $db->fetchQuery('
205
				SELECT mem.*, IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
206
				FROM {db_prefix}members AS mem
207
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
208
				WHERE mem.id_member = {int:id_member}
209
				LIMIT 1',
210
				array(
211
					'id_member' => $id_member,
212
				)
213
			);
214
215
			// Make the ID specifically an integer
216
			$user_settings['id_member'] = (int) $user_settings['id_member'];
217
218
			if ($cache->checkLevel(2))
219
				$cache->put('user_settings-' . $id_member, $user_settings, 60);
220
		}
221
222
		// Did we find 'im?  If not, junk it.
223
		if (!empty($user_settings))
224
		{
225
			// As much as the password should be right, we can assume the integration set things up.
226
			if (!empty($already_verified) && $already_verified === true)
227
				$check = true;
228
			// SHA-256 passwords should be 64 characters long.
229
			elseif (strlen($password) == 64)
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...
230
				$check = hash('sha256', ($user_settings['passwd'] . $user_settings['password_salt'])) == $password;
231
			else
232
				$check = false;
233
234
			// Wrong password or not activated - either way, you're going nowhere.
235
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
236
		}
237
		else
238
			$id_member = 0;
239
240
		// If we no longer have the member maybe they're being all hackey, stop brute force!
241
		if (!$id_member)
242
			validatePasswordFlood(!empty($user_settings['id_member']) ? $user_settings['id_member'] : $id_member, !empty($user_settings['passwd_flood']) ? $user_settings['passwd_flood'] : false, $id_member != 0);
0 ignored issues
show
Bug introduced by
It seems like !empty($user_settings['p...'passwd_flood'] : false can also be of type integer; however, validatePasswordFlood() does only seem to accept string|boolean, maybe add an additional type check?

If a method or function can return multiple different values and unless you are sure that you only can receive a single value in this context, we recommend to add an additional type check:

/**
 * @return array|string
 */
function returnsDifferentValues($x) {
    if ($x) {
        return 'foo';
    }

    return array();
}

$x = returnsDifferentValues($y);
if (is_array($x)) {
    // $x is an array.
}

If this a common case that PHP Analyzer should handle natively, please let us know by opening an issue.

Loading history...
243
	}
244
245
	// Found 'im, let's set up the variables.
246
	if ($id_member != 0)
247
	{
248
		// Let's not update the last visit time in these cases...
249
		// 1. SSI doesn't count as visiting the forum.
250
		// 2. RSS feeds and XMLHTTP requests don't count either.
251
		// 3. If it was set within this session, no need to set it again.
252
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
253
		if (ELK != 'SSI' && !isset($_REQUEST['xml']) && (!isset($_REQUEST['action']) || $_REQUEST['action'] != '.xml') && empty($_SESSION['id_msg_last_visit']) && (!$cache->isEnabled() || !$cache->getVar($_SESSION['id_msg_last_visit'], 'user_last_visit-' . $id_member, 5 * 3600)))
254
		{
255
			// @todo can this be cached?
256
			// Do a quick query to make sure this isn't a mistake.
257
			require_once(SUBSDIR . '/Messages.subs.php');
258
			$visitOpt = basicMessageInfo($user_settings['id_msg_last_visit'], true);
259
260
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
261
262
			// If it was *at least* five hours ago...
263
			if ($visitOpt['poster_time'] < time() - 5 * 3600)
264
			{
265
				require_once(SUBSDIR . '/Members.subs.php');
266
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $req->client_ip(), 'member_ip2' => $req->ban_ip()));
267
				$user_settings['last_login'] = time();
268
269
				if ($cache->checkLevel(2))
270
					$cache->put('user_settings-' . $id_member, $user_settings, 60);
271
272
				$cache->put('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
273
			}
274
		}
275
		elseif (empty($_SESSION['id_msg_last_visit']))
276
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
277
278
		$username = $user_settings['member_name'];
279
280
		if (empty($user_settings['additional_groups']))
281
			$user_info = array(
282
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
283
			);
284
		else
285
			$user_info = array(
286
				'groups' => array_merge(
287
					array($user_settings['id_group'], $user_settings['id_post_group']),
288
					explode(',', $user_settings['additional_groups'])
289
				)
290
			);
291
292
		// Because history has proven that it is possible for groups to go bad - clean up in case.
293
		foreach ($user_info['groups'] as $k => $v)
294
			$user_info['groups'][$k] = (int) $v;
295
296
		// This is a logged in user, so definitely not a spider.
297
		$user_info['possibly_robot'] = false;
298
	}
299
	// If the user is a guest, initialize all the critical user settings.
300
	else
301
	{
302
		// This is what a guest's variables should be.
303
		$username = '';
304
		$user_info = array('groups' => array(-1));
305
		$user_settings = array();
306
307
		if (isset($_COOKIE[$cookiename]))
308
			$_COOKIE[$cookiename] = '';
309
310
		// Create a login token if it doesn't exist yet.
311
		if (!isset($_SESSION['token']['post-login']))
312
			createToken('login');
313
		else
314
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
315
316
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
317
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
318
		{
319
			require_once(SUBSDIR . '/SearchEngines.subs.php');
320
			$user_info['possibly_robot'] = spiderCheck();
321
		}
322
		elseif (!empty($modSettings['spider_mode']))
323
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
324
		// If we haven't turned on proper spider hunts then have a guess!
325
		else
326
		{
327
			$ci_user_agent = strtolower($req->user_agent());
328
			$user_info['possibly_robot'] = (strpos($ci_user_agent, 'mozilla') === false && strpos($ci_user_agent, 'opera') === false) || preg_match('~(googlebot|slurp|crawl|msnbot|yandex|bingbot|baidu)~u', $ci_user_agent) == 1;
329
		}
330
	}
331
332
	// Set up the $user_info array.
333
	$user_info += array(
334
		'id' => $id_member,
335
		'username' => $username,
336
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
337
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
338
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
339
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
340
		'is_guest' => $id_member == 0,
341
		'is_admin' => in_array(1, $user_info['groups']),
342
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
343
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
344
		'ip' => $req->client_ip(),
345
		'ip2' => $req->ban_ip(),
346
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
347
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
348
		'time_offset' => empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'],
349
		'avatar' => array_merge(array(
350
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
351
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
352
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
353
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
354
		), determineAvatar($user_settings)),
355
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
356
		'messages' => empty($user_settings['personal_messages']) ? 0 : $user_settings['personal_messages'],
357
		'mentions' => empty($user_settings['mentions']) ? 0 : max(0, $user_settings['mentions']),
358
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
359
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
360
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
361
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
362
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
363
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
364
		'permissions' => array(),
365
	);
366
	$user_info['groups'] = array_unique($user_info['groups']);
367
368
	// 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.
369
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
370
		unset($user_info['ignoreboards'][$tmp]);
371
372
	// Do we have any languages to validate this?
373
	if (!empty($modSettings['userLanguage']) && (!empty($_GET['language']) || !empty($_SESSION['language'])))
374
		$languages = getLanguages();
375
376
	// Allow the user to change their language if its valid.
377
	if (!empty($modSettings['userLanguage']) && !empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
378
	{
379
		$user_info['language'] = strtr($_GET['language'], './\\:', '____');
380
		$_SESSION['language'] = $user_info['language'];
381
	}
382
	elseif (!empty($modSettings['userLanguage']) && !empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
383
		$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
384
385
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
386
	if ($user_info['is_admin'])
387
		$user_info['query_see_board'] = '1=1';
388
	// Otherwise just the groups in $user_info['groups'].
389
	else
390
		$user_info['query_see_board'] = '((FIND_IN_SET(' . implode(', b.member_groups) != 0 OR FIND_IN_SET(', $user_info['groups']) . ', b.member_groups) != 0)' . (!empty($modSettings['deny_boards_access']) ? ' AND (FIND_IN_SET(' . implode(', b.deny_member_groups) = 0 AND FIND_IN_SET(', $user_info['groups']) . ', b.deny_member_groups) = 0)' : '') . (isset($user_info['mod_cache']) ? ' OR ' . $user_info['mod_cache']['mq'] : '') . ')';
391
	// Build the list of boards they WANT to see.
392
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
393
394
	// If they aren't ignoring any boards then they want to see all the boards they can see
395
	if (empty($user_info['ignoreboards']))
396
		$user_info['query_wanna_see_board'] = $user_info['query_see_board'];
397
	// Ok I guess they don't want to see all the boards
398
	else
399
		$user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))';
400
401
	call_integration_hook('integrate_user_info');
402
}
403
404
/**
405
 * Check for moderators and see if they have access to the board.
406
 *
407
 * What it does:
408
 * - sets up the $board_info array for current board information.
409
 * - if cache is enabled, the $board_info array is stored in cache.
410
 * - redirects to appropriate post if only message id is requested.
411
 * - is only used when inside a topic or board.
412
 * - determines the local moderators for the board.
413
 * - adds group id 3 if the user is a local moderator for the board they are in.
414
 * - prevents access if user is not in proper group nor a local moderator of the board.
415
 */
416
function loadBoard()
0 ignored issues
show
Coding Style introduced by
loadBoard uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadBoard uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
417
{
418
	global $txt, $scripturl, $context, $modSettings;
419
	global $board_info, $board, $topic, $user_info;
420
421
	$db = database();
422
	$cache = Cache::instance();
423
424
	// Assume they are not a moderator.
425
	$user_info['is_mod'] = false;
426
	// @since 1.0.5 - is_mod takes into account only local (board) moderators,
427
	// and not global moderators, is_moderator is meant to take into account both.
428
	$user_info['is_moderator'] = false;
429
430
	// Start the linktree off empty..
431
	$context['linktree'] = array();
432
433
	// Have they by chance specified a message id but nothing else?
434
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
435
	{
436
		// Make sure the message id is really an int.
437
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
438
439
		// Looking through the message table can be slow, so try using the cache first.
440
		if (!$cache->getVar($topic, 'msg_topic-' . $_REQUEST['msg'], 120))
441
		{
442
			require_once(SUBSDIR . '/Messages.subs.php');
443
			$topic = associatedTopic($_REQUEST['msg']);
444
445
			// So did it find anything?
446
			if ($topic !== false)
447
			{
448
				// Save save save.
449
				$cache->put('msg_topic-' . $_REQUEST['msg'], $topic, 120);
450
			}
451
		}
452
453
		// Remember redirection is the key to avoiding fallout from your bosses.
454
		if (!empty($topic))
455
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
456
		else
457
		{
458
			loadPermissions();
459
			loadTheme();
460
			Errors::instance()->fatal_lang_error('topic_gone', false);
461
		}
462
	}
463
464
	// Load this board only if it is specified.
465
	if (empty($board) && empty($topic))
466
	{
467
		$board_info = array('moderators' => array());
468
		return;
469
	}
470
471
	if ($cache->isEnabled() && (empty($topic) || $cache->checkLevel(3)))
472
	{
473
		// @todo SLOW?
474
		if (!empty($topic))
475
			$temp = $cache->get('topic_board-' . $topic, 120);
476
		else
477
			$temp = $cache->get('board-' . $board, 120);
478
479
		if (!empty($temp))
480
		{
481
			$board_info = $temp;
482
			$board = (int) $board_info['id'];
483
		}
484
	}
485
486
	if (empty($temp))
487
	{
488
		$select_columns = array();
489
		$select_tables = array();
490
		// Wanna grab something more from the boards table or another table at all?
491
		call_integration_hook('integrate_load_board_query', array(&$select_columns, &$select_tables));
492
493
		$request = $db->query('', '
494
			SELECT
495
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
496
				b.id_parent, c.name AS cname, IFNULL(mem.id_member, 0) AS id_moderator,
497
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
498
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
499
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . (!empty($select_columns) ? ', ' . implode(', ', $select_columns) : '') . '
500
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
501
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . (!empty($select_tables) ? '
502
				' . implode("\n\t\t\t\t", $select_tables) : '') . '
503
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
504
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
505
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
506
			WHERE b.id_board = {raw:board_link}',
507
			array(
508
				'current_topic' => $topic,
509
				'board_link' => empty($topic) ? $db->quote('{int:current_board}', array('current_board' => $board)) : 't.id_board',
510
			)
511
		);
512
		// If there aren't any, skip.
513
		if ($db->num_rows($request) > 0)
514
		{
515
			$row = $db->fetch_assoc($request);
516
517
			// Set the current board.
518
			if (!empty($row['id_board']))
519
				$board = (int) $row['id_board'];
520
521
			// Basic operating information. (globals... :/)
522
			$board_info = array(
523
				'id' => $board,
524
				'moderators' => array(),
525
				'cat' => array(
526
					'id' => $row['id_cat'],
527
					'name' => $row['cname']
528
				),
529
				'name' => $row['bname'],
530
				'raw_description' => $row['description'],
531
				'description' => $row['description'],
532
				'num_topics' => $row['num_topics'],
533
				'unapproved_topics' => $row['unapproved_topics'],
534
				'unapproved_posts' => $row['unapproved_posts'],
535
				'unapproved_user_topics' => 0,
536
				'parent_boards' => getBoardParents($row['id_parent']),
537
				'parent' => $row['id_parent'],
538
				'child_level' => $row['child_level'],
539
				'theme' => $row['id_theme'],
540
				'override_theme' => !empty($row['override_theme']),
541
				'profile' => $row['id_profile'],
542
				'redirect' => $row['redirect'],
543
				'posts_count' => empty($row['count_posts']),
544
				'cur_topic_approved' => empty($topic) || $row['approved'],
545
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
546
			);
547
548
			// Load the membergroups allowed, and check permissions.
549
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
550
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
551
552
			do
553
			{
554
				if (!empty($row['id_moderator']))
555
					$board_info['moderators'][$row['id_moderator']] = array(
556
						'id' => $row['id_moderator'],
557
						'name' => $row['real_name'],
558
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
559
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
560
					);
561
			}
562
			while ($row = $db->fetch_assoc($request));
563
564
			// If the board only contains unapproved posts and the user can't approve then they can't see any topics.
565
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
566
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
567
			{
568
				// Free the previous result
569
				$db->free_result($request);
570
571
				// @todo why is this using id_topic?
572
				// @todo Can this get cached?
573
				$request = $db->query('', '
574
					SELECT COUNT(id_topic)
575
					FROM {db_prefix}topics
576
					WHERE id_member_started={int:id_member}
577
						AND approved = {int:unapproved}
578
						AND id_board = {int:board}',
579
					array(
580
						'id_member' => $user_info['id'],
581
						'unapproved' => 0,
582
						'board' => $board,
583
					)
584
				);
585
586
				list ($board_info['unapproved_user_topics']) = $db->fetch_row($request);
587
			}
588
589
			call_integration_hook('integrate_loaded_board', array(&$board_info, &$row));
590
591
			if ($cache->isEnabled() && (empty($topic) || $cache->checkLevel(3)))
592
			{
593
				// @todo SLOW?
594
				if (!empty($topic))
595
					$cache->put('topic_board-' . $topic, $board_info, 120);
596
				$cache->put('board-' . $board, $board_info, 120);
597
			}
598
		}
599
		else
600
		{
601
			// Otherwise the topic is invalid, there are no moderators, etc.
602
			$board_info = array(
603
				'moderators' => array(),
604
				'error' => 'exist'
605
			);
606
			$topic = null;
607
			$board = 0;
608
		}
609
		$db->free_result($request);
610
	}
611
612
	if (!empty($topic))
613
		$_GET['board'] = (int) $board;
614
615
	if (!empty($board))
616
	{
617
		// Now check if the user is a moderator.
618
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]);
619
620 View Code Duplication
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
621
			$board_info['error'] = 'access';
622 View Code Duplication
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
623
			$board_info['error'] = 'access';
624
625
		// Build up the linktree.
626
		$context['linktree'] = array_merge(
627
			$context['linktree'],
628
			array(array(
629
				'url' => $scripturl . '#c' . $board_info['cat']['id'],
630
				'name' => $board_info['cat']['name']
631
			)),
632
			array_reverse($board_info['parent_boards']),
633
			array(array(
634
				'url' => $scripturl . '?board=' . $board . '.0',
635
				'name' => $board_info['name']
636
			))
637
		);
638
	}
639
640
	// Set the template contextual information.
641
	$context['user']['is_mod'] = &$user_info['is_mod'];
642
	$context['user']['is_moderator'] = &$user_info['is_moderator'];
643
	$context['current_topic'] = $topic;
644
	$context['current_board'] = $board;
645
646
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
647
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_moderator']))
648
	{
649
		// The permissions and theme need loading, just to make sure everything goes smoothly.
650
		loadPermissions();
651
		loadTheme();
652
653
		$_GET['board'] = '';
654
		$_GET['topic'] = '';
655
656
		// The linktree should not give the game away mate!
657
		$context['linktree'] = array(
658
			array(
659
				'url' => $scripturl,
660
				'name' => $context['forum_name_html_safe']
661
			)
662
		);
663
664
		// If it's a prefetching agent, stop it
665
		stop_prefetching();
666
667
		// If we're requesting an attachment.
668
		if (!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...
669
		{
670
			ob_end_clean();
671
			header('HTTP/1.1 403 Forbidden');
672
			exit;
0 ignored issues
show
Coding Style Compatibility introduced by
The function loadBoard() contains an exit expression.

An exit expression should only be used in rare cases. For example, if you write a short command line script.

In most cases however, using an exit expression makes the code untestable and often causes incompatibilities with other libraries. Thus, unless you are absolutely sure it is required here, we recommend to refactor your code to avoid its usage.

Loading history...
673
		}
674
		elseif ($user_info['is_guest'])
675
		{
676
			loadLanguage('Errors');
677
			is_not_guest($txt['topic_gone']);
678
		}
679
		else
680
			Errors::instance()->fatal_lang_error('topic_gone', false);
681
	}
682
683
	if ($user_info['is_mod'])
684
		$user_info['groups'][] = 3;
685
}
686
687
/**
688
 * Load this user's permissions.
689
 *
690
 * What it does:
691
 * - If the user is an admin, validate that they have not been banned.
692
 * - Attempt to load permissions from cache for cache level > 2
693
 * - See if the user is possibly a robot and apply added permissions as needed
694
 * - Load permissions from the general permissions table.
695
 * - If inside a board load the necessary board permissions.
696
 * - If the user is not a guest, identify what other boards they have access to.
697
 */
698
function loadPermissions()
0 ignored issues
show
Coding Style introduced by
loadPermissions uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
699
{
700
	global $user_info, $board, $board_info, $modSettings;
701
702
	$db = database();
703
704
	if ($user_info['is_admin'])
705
	{
706
		banPermissions();
707
		return;
708
	}
709
710
	$removals = array();
711
712
	$cache = Cache::instance();
713
714
	if ($cache->isEnabled())
715
	{
716
		$cache_groups = $user_info['groups'];
717
		asort($cache_groups);
718
		$cache_groups = implode(',', $cache_groups);
719
720
		// If it's a spider then cache it different.
721
		if ($user_info['possibly_robot'])
722
			$cache_groups .= '-spider';
723
724
		if ($cache->checkLevel(2) && !empty($board) && $cache->getVar($temp, 'permissions:' . $cache_groups . ':' . $board, 240) && time() - 240 > $modSettings['settings_updated'])
725
		{
726
			list ($user_info['permissions']) = $temp;
0 ignored issues
show
Bug introduced by
The variable $temp does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
727
			banPermissions();
728
729
			return;
730
		}
731
		elseif ($cache->getVar($temp, 'permissions:' . $cache_groups, 240) && time() - 240 > $modSettings['settings_updated'])
732
			list ($user_info['permissions'], $removals) = $temp;
733
	}
734
735
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
736
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
737
738
	if (empty($user_info['permissions']))
739
	{
740
		// Get the general permissions.
741
		$request = $db->query('', '
742
			SELECT permission, add_deny
743
			FROM {db_prefix}permissions
744
			WHERE id_group IN ({array_int:member_groups})
745
				' . $spider_restrict,
746
			array(
747
				'member_groups' => $user_info['groups'],
748
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
749
			)
750
		);
751 View Code Duplication
		while ($row = $db->fetch_assoc($request))
752
		{
753
			if (empty($row['add_deny']))
754
				$removals[] = $row['permission'];
755
			else
756
				$user_info['permissions'][] = $row['permission'];
757
		}
758
		$db->free_result($request);
759
760
		if (isset($cache_groups))
761
			$cache->put('permissions:' . $cache_groups, array($user_info['permissions'], $removals), 240);
762
	}
763
764
	// Get the board permissions.
765
	if (!empty($board))
766
	{
767
		// Make sure the board (if any) has been loaded by loadBoard().
768
		if (!isset($board_info['profile']))
769
			Errors::instance()->fatal_lang_error('no_board');
770
771
		$request = $db->query('', '
772
			SELECT permission, add_deny
773
			FROM {db_prefix}board_permissions
774
			WHERE (id_group IN ({array_int:member_groups})
775
				' . $spider_restrict . ')
776
				AND id_profile = {int:id_profile}',
777
			array(
778
				'member_groups' => $user_info['groups'],
779
				'id_profile' => $board_info['profile'],
780
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
781
			)
782
		);
783 View Code Duplication
		while ($row = $db->fetch_assoc($request))
784
		{
785
			if (empty($row['add_deny']))
786
				$removals[] = $row['permission'];
787
			else
788
				$user_info['permissions'][] = $row['permission'];
789
		}
790
		$db->free_result($request);
791
	}
792
793
	// Remove all the permissions they shouldn't have ;).
794 View Code Duplication
	if (!empty($modSettings['permission_enable_deny']))
795
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
796
797
	if (isset($cache_groups) && !empty($board) && $cache->checkLevel(2))
798
		$cache->put('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
799
800
	// Banned?  Watch, don't touch..
801
	banPermissions();
802
803
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
804
	if (!$user_info['is_guest'])
805
	{
806
		$user_info['is_moderator'] = $user_info['is_mod'] || allowedTo('moderate_board');
807
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
808
		{
809
			require_once(SUBSDIR . '/Auth.subs.php');
810
			rebuildModCache();
811
		}
812
		else
813
			$user_info['mod_cache'] = $_SESSION['mc'];
814
	}
815
}
816
817
/**
818
 * Loads an array of users' data by ID or member_name.
819
 *
820
 * @param int[]|int|string[]|string $users An array of users by id or name
821
 * @param bool $is_name = false $users is by name or by id
822
 * @param string $set = 'normal' What kind of data to load (normal, profile, minimal)
823
 * @return array|bool The ids of the members loaded or false
824
 */
825
function loadMemberData($users, $is_name = false, $set = 'normal')
826
{
827 1
	global $user_profile, $modSettings, $board_info, $context;
828
829 1
	$db = database();
830 1
	$cache = Cache::instance();
831
832
	// Can't just look for no users :P.
833 1
	if (empty($users))
834 1
		return false;
835
836
	// Pass the set value
837 1
	$context['loadMemberContext_set'] = $set;
838
839
	// Make sure it's an array.
840 1
	$users = !is_array($users) ? array($users) : array_unique($users);
841 1
	$loaded_ids = array();
842
843 1
	if (!$is_name && $cache->isEnabled() && $cache->checkLevel(3))
844 1
	{
845
		$users = array_values($users);
846
		for ($i = 0, $n = count($users); $i < $n; $i++)
847
		{
848
			$data = $cache->get('member_data-' . $set . '-' . $users[$i], 240);
849
			if ($cache->isMiss())
850
				continue;
851
852
			$loaded_ids[] = $data['id_member'];
853
			$user_profile[$data['id_member']] = $data;
854
			unset($users[$i]);
855
		}
856
	}
857
858
	// Used by default
859
	$select_columns = '
860
			IFNULL(lo.log_time, 0) AS is_online, IFNULL(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
861
			mem.signature, mem.avatar, mem.id_member, mem.member_name,
862
			mem.real_name, mem.email_address, mem.hide_email, mem.date_registered, mem.website_title, mem.website_url,
863
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.likes_given, mem.likes_received,
864
			mem.karma_good, mem.id_post_group, mem.karma_bad, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online,
865
			mg.online_color AS member_group_color, IFNULL(mg.group_name, {string:blank_string}) AS member_group,
866
			pg.online_color AS post_group_color, IFNULL(pg.group_name, {string:blank_string}) AS post_group,
867 1
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
868 1
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
869
	$select_tables = '
870
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
871
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
872
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
873 1
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
874
875
	// We add or replace according to the set
876
	switch ($set)
877
	{
878 1
		case 'normal':
879
			$select_columns .= ', mem.buddy_list';
880
			break;
881 1
		case 'profile':
882
			$select_columns .= ', mem.openid_uri, mem.id_theme, mem.pm_ignore_list, mem.pm_email_notify, mem.receive_from,
883
			mem.time_format, mem.secret_question, mem.additional_groups, mem.smiley_set,
884
			mem.total_time_logged_in, mem.notify_announcements, mem.notify_regularity, mem.notify_send_body,
885 1
			mem.notify_types, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.otp_secret, mem.enable_otp';
886 1
			break;
887
		case 'minimal':
888
			$select_columns = '
889
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.hide_email, mem.date_registered,
890
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
891
			$select_tables = '';
892
			break;
893
		default:
894
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
895
	}
896
897
	// Allow addons to easily add to the selected member data
898 1
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, $set));
899
900 1
	if (!empty($users))
901 1
	{
902
		// Load the member's data.
903 1
		$request = $db->query('', '
904 1
			SELECT' . $select_columns . '
905 1
			FROM {db_prefix}members AS mem' . $select_tables . '
906 1
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . (count($users) == 1 ? ' = {' . ($is_name ? 'string' : 'int') . ':users}' : ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})'),
907
			array(
908 1
				'blank_string' => '',
909 1
				'users' => count($users) == 1 ? current($users) : $users,
910
			)
911 1
		);
912 1
		$new_loaded_ids = array();
913 1
		while ($row = $db->fetch_assoc($request))
914
		{
915 1
			$new_loaded_ids[] = $row['id_member'];
916 1
			$loaded_ids[] = $row['id_member'];
917 1
			$row['options'] = array();
918 1
			$user_profile[$row['id_member']] = $row;
919 1
		}
920 1
		$db->free_result($request);
921 1
	}
922
923
	// Custom profile fields as well
924 1
	if (!empty($new_loaded_ids) && $set !== 'minimal' && (in_array('cp', $context['admin_features'])))
925 1
	{
926
		$request = $db->query('', '
927
			SELECT id_member, variable, value
928
			FROM {db_prefix}custom_fields_data
929
			WHERE id_member' . (count($new_loaded_ids) == 1 ? ' = {int:loaded_ids}' : ' IN ({array_int:loaded_ids})'),
930
			array(
931
				'loaded_ids' => count($new_loaded_ids) == 1 ? $new_loaded_ids[0] : $new_loaded_ids,
932
			)
933
		);
934
		while ($row = $db->fetch_assoc($request))
935
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
936
		$db->free_result($request);
937
	}
938
939
	// Anything else integration may want to add to the user_profile array
940 1
	if (!empty($new_loaded_ids))
941 1
		call_integration_hook('integrate_add_member_data', array($new_loaded_ids, $set));
942
943 1
	if (!empty($new_loaded_ids) && $cache->checkLevel(3))
944 1
	{
945
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
946
			$cache->put('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
947
	}
948
949
	// Are we loading any moderators?  If so, fix their group data...
950 1
	if (!empty($loaded_ids) && !empty($board_info['moderators']) && $set === 'normal' && count($temp_mods = array_intersect($loaded_ids, array_keys($board_info['moderators']))) !== 0)
951 1
	{
952
		if (!$cache->getVar($group_info, 'moderator_group_info', 480))
0 ignored issues
show
Bug introduced by
The variable $group_info seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
953
		{
954
			require_once(SUBSDIR . '/Membergroups.subs.php');
955
			$group_info = membergroupById(3, true);
956
957
			$cache->put('moderator_group_info', $group_info, 480);
958
		}
959
960
		foreach ($temp_mods as $id)
961
		{
962
			// By popular demand, don't show admins or global moderators as moderators.
963
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
964
				$user_profile[$id]['member_group'] = $group_info['group_name'];
0 ignored issues
show
Bug introduced by
The variable $group_info 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...
965
966
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
967
			if (!empty($group_info['icons']))
968
				$user_profile[$id]['icons'] = $group_info['icons'];
969
			if (!empty($group_info['online_color']))
970
				$user_profile[$id]['member_group_color'] = $group_info['online_color'];
971
		}
972
	}
973
974 1
	return empty($loaded_ids) ? false : $loaded_ids;
975
}
976
977
/**
978
 * Loads the user's basic values... meant for template/theme usage.
979
 *
980
 * What it does:
981
 * - Always loads the minimal values of username, name, id, href, link, email, show_email, registered, registered_timestamp
982
 * - if $context['loadMemberContext_set'] is not minimal it will load in full a full set of user information
983
 * - prepares signature for display (censoring if enabled)
984
 * - loads in the members custom fields if any
985
 * - prepares the users buddy list, including reverse buddy flags
986
 *
987
 * @param int $user
988
 * @param bool $display_custom_fields = false
989
 * @return boolean
990
 */
991
function loadMemberContext($user, $display_custom_fields = false)
992
{
993
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
994
	global $context, $modSettings, $settings;
995
	static $dataLoaded = array();
996
997
	// If this person's data is already loaded, skip it.
998
	if (isset($dataLoaded[$user]))
999
		return true;
1000
1001
	// We can't load guests or members not loaded by loadMemberData()!
1002
	if ($user == 0)
1003
		return false;
1004
1005
	if (!isset($user_profile[$user]))
1006
	{
1007
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1008
		return false;
1009
	}
1010
1011
	$parsers = \BBC\ParserWrapper::getInstance();
1012
1013
	// Well, it's loaded now anyhow.
1014
	$dataLoaded[$user] = true;
1015
	$profile = $user_profile[$user];
1016
1017
	// Censor everything.
1018
	$profile['signature'] = censor($profile['signature']);
1019
1020
	// TODO: We should look into a censoring toggle for custom fields
1021
1022
	// Set things up to be used before hand.
1023
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br />', ''), $profile['signature']);
1024
	$profile['signature'] = $parsers->parseSignature($profile['signature'], true);
1025
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1026
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1027
1028
	// Setup the buddy status here (One whole in_array call saved :P)
1029
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1030
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1031
1032
	// These minimal values are always loaded
1033
	$memberContext[$user] = array(
1034
		'username' => $profile['member_name'],
1035
		'name' => $profile['real_name'],
1036
		'id' => $profile['id_member'],
1037
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1038
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . trim($profile['real_name']) . '">' . $profile['real_name'] . '</a>',
1039
		'email' => $profile['email_address'],
1040
		'show_email' => showEmailAddress(!empty($profile['hide_email']), $profile['id_member']),
1041
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : standardTime($profile['date_registered']),
1042
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1043
	);
1044
1045
	// If the set isn't minimal then load the monstrous array.
1046
	if ($context['loadMemberContext_set'] !== 'minimal')
1047
	{
1048
		$memberContext[$user] += array(
1049
			'username_color' => '<span '. (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] .';"' : '') . '>' . $profile['member_name'] .'</span>',
1050
			'name_color' => '<span '. (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] .';"' : '') . '>' . $profile['real_name'] .'</span>',
1051
			'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>',
1052
			'is_buddy' => $profile['buddy'],
1053
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1054
			'buddies' => $buddy_list,
1055
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1056
			'website' => array(
1057
				'title' => $profile['website_title'],
1058
				'url' => $profile['website_url'],
1059
			),
1060
			'birth_date' => empty($profile['birthdate']) || $profile['birthdate'] === '0001-01-01' ? '0000-00-00' : (substr($profile['birthdate'], 0, 4) === '0004' ? '0000' . substr($profile['birthdate'], 4) : $profile['birthdate']),
1061
			'signature' => $profile['signature'],
1062
			'real_posts' => $profile['posts'],
1063
			'posts' => comma_format($profile['posts']),
1064
			'avatar' => determineAvatar($profile),
1065
			'last_login' => empty($profile['last_login']) ? $txt['never'] : standardTime($profile['last_login']),
1066
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(false, $profile['last_login']),
1067
			'karma' => array(
1068
				'good' => $profile['karma_good'],
1069
				'bad' => $profile['karma_bad'],
1070
				'allow' => !$user_info['is_guest'] && !empty($modSettings['karmaMode']) && $user_info['id'] != $user && allowedTo('karma_edit') &&
1071
				($user_info['posts'] >= $modSettings['karmaMinPosts'] || $user_info['is_admin']),
1072
			),
1073
			'likes' => array(
1074
				'given' => $profile['likes_given'],
1075
				'received' => $profile['likes_received']
1076
			),
1077
			'ip' => htmlspecialchars($profile['member_ip'], ENT_COMPAT, 'UTF-8'),
1078
			'ip2' => htmlspecialchars($profile['member_ip2'], ENT_COMPAT, 'UTF-8'),
1079
			'online' => array(
1080
				'is_online' => $profile['is_online'],
1081
				'text' => Util::htmlspecialchars($txt[$profile['is_online'] ? 'online' : 'offline']),
1082
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], Util::htmlspecialchars($profile['real_name'])),
1083
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1084
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1085
				'image_href' => $settings['images_url'] . '/profile/' . ($profile['buddy'] ? 'buddy_' : '') . ($profile['is_online'] ? 'useron' : 'useroff') . '.png',
1086
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1087
			),
1088
			'language' => Util::ucwords(strtr($profile['lngfile'], array('_' => ' '))),
1089
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1090
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1091
			'options' => $profile['options'],
1092
			'is_guest' => false,
1093
			'group' => $profile['member_group'],
1094
			'group_color' => $profile['member_group_color'],
1095
			'group_id' => $profile['id_group'],
1096
			'post_group' => $profile['post_group'],
1097
			'post_group_color' => $profile['post_group_color'],
1098
			'group_icons' => str_repeat('<img src="' . str_replace('$language', $context['user']['language'], isset($profile['icons'][1]) ? $settings['images_url'] . '/group_icons/' . $profile['icons'][1] : '') . '" alt="[*]" />', empty($profile['icons'][0]) || empty($profile['icons'][1]) ? 0 : $profile['icons'][0]),
1099
			'warning' => $profile['warning'],
1100
			'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' : (''))),
1101
			'local_time' => standardTime(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1102
			'custom_fields' => array(),
1103
		);
1104
	}
1105
1106
	// Are we also loading the members custom fields into context?
1107
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1108
	{
1109
		if (!isset($context['display_fields']))
1110
			$context['display_fields'] = unserialize($modSettings['displayFields']);
1111
1112
		foreach ($context['display_fields'] as $custom)
1113
		{
1114
			if (!isset($custom['title']) || trim($custom['title']) == '' || empty($profile['options'][$custom['colname']]))
1115
				continue;
1116
1117
			$value = $profile['options'][$custom['colname']];
1118
1119
			// BBC?
1120
			if ($custom['bbc'])
1121
				$value = $parsers->parseCustomFields($value);
1122
			// ... 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...
1123
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1124
				$value = $value ? $txt['yes'] : $txt['no'];
1125
1126
			// Enclosing the user input within some other text?
1127 View Code Duplication
			if (!empty($custom['enclose']))
1128
				$value = strtr($custom['enclose'], array(
1129
					'{SCRIPTURL}' => $scripturl,
1130
					'{IMAGES_URL}' => $settings['images_url'],
1131
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1132
					'{INPUT}' => $value,
1133
				));
1134
1135
			$memberContext[$user]['custom_fields'][] = array(
1136
				'title' => $custom['title'],
1137
				'colname' => $custom['colname'],
1138
				'value' => $value,
1139
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1140
			);
1141
		}
1142
	}
1143
1144
	call_integration_hook('integrate_member_context', array($user, $display_custom_fields));
1145
	return true;
1146
}
1147
1148
/**
1149
 * Loads information about what browser the user is viewing with and places it in $context
1150
 *
1151
 * @uses the class from BrowserDetect.class.php
1152
 */
1153
function detectBrowser()
1154
{
1155
	// Load the current user's browser of choice
1156 1
	$detector = new Browser_Detector;
1157 1
	$detector->detectBrowser();
1158 1
}
1159
1160
/**
1161
 * Get the id of a theme
1162
 *
1163
 * @param int $id_theme
1164
 * @return int
1165
 */
1166
function getThemeId($id_theme = 0)
0 ignored issues
show
Coding Style introduced by
getThemeId uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
getThemeId uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1167
{
1168 1
	global $modSettings, $user_info, $board_info, $ssi_theme;
1169
1170
	// The theme was specified by parameter.
1171 1
	if (!empty($id_theme))
1172 1
		$id_theme = (int) $id_theme;
1173
	// The theme was specified by REQUEST.
1174 1 View Code Duplication
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1175
	{
1176
		$id_theme = (int) $_REQUEST['theme'];
1177
		$_SESSION['id_theme'] = $id_theme;
1178
	}
1179
	// The theme was specified by REQUEST... previously.
1180 1 View Code Duplication
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1181
		$id_theme = (int) $_SESSION['id_theme'];
1182
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1183 1 View Code Duplication
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1184
		$id_theme = $user_info['theme'];
1185
	// The theme was specified by the board.
1186 1
	elseif (!empty($board_info['theme']))
1187
		$id_theme = $board_info['theme'];
1188
	// The theme is the forum's default.
1189
	else
1190 1
		$id_theme = $modSettings['theme_guests'];
1191
1192
	// Verify the id_theme... no foul play.
1193
	// Always allow the board specific theme, if they are overriding.
1194 1
	if (!empty($board_info['theme']) && $board_info['override_theme'])
1195 1
		$id_theme = $board_info['theme'];
1196
	// If they have specified a particular theme to use with SSI allow it to be used.
1197 1
	elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1198
		$id_theme = (int) $id_theme;
1199 1 View Code Duplication
	elseif (!empty($modSettings['knownThemes']) && !allowedTo('admin_forum'))
1200
	{
1201 1
		$themes = explode(',', $modSettings['knownThemes']);
1202 1
		if (!in_array($id_theme, $themes))
1203 1
			$id_theme = $modSettings['theme_guests'];
1204
		else
1205 1
			$id_theme = (int) $id_theme;
1206 1
	}
1207
	else
1208
		$id_theme = (int) $id_theme;
1209
1210 1
	return $id_theme;
1211
}
1212
1213
/**
1214
 * Load in the theme variables for a given theme / member combination
1215
 *
1216
 * @param int $id_theme
1217
 * @param int $member
1218
 *
1219
 * @return array
1220
 */
1221
function getThemeData($id_theme, $member)
1222
{
1223 1
	global $modSettings;
1224
1225 1
	$cache = Cache::instance();
1226
1227
	// Do we already have this members theme data and specific options loaded (for aggressive cache settings)
1228 1
	if ($cache->checkLevel(2) && $cache->getVar($temp, 'theme_settings-' . $id_theme . ':' . $member, 60) && time() - 60 > $modSettings['settings_updated'])
1229 1
	{
1230
		$themeData = $temp;
0 ignored issues
show
Bug introduced by
The variable $temp does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1231
		$flag = true;
1232
	}
1233
	// Or do we just have the system wide theme settings cached
1234 1
	elseif ($cache->getVar($temp, 'theme_settings-' . $id_theme, 90) && time() - 60 > $modSettings['settings_updated'])
1235
		$themeData = $temp + array($member => array());
1236
	// Nothing at all then
1237
	else
1238 1
		$themeData = array(-1 => array(), 0 => array(), $member => array());
1239
1240 1
	if (empty($flag))
1241 1
	{
1242 1
		$db = database();
1243
1244
		// Load variables from the current or default theme, global or this user's.
1245 1
		$result = $db->query('', '
1246
			SELECT variable, value, id_member, id_theme
1247
			FROM {db_prefix}themes
1248 1
			WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1249 1
				AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1250
			array(
1251 1
				'id_theme' => $id_theme,
1252 1
				'id_member' => $member,
1253
			)
1254 1
		);
1255
1256 1
		$immutable_theme_data = 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');
1257
1258
		// Pick between $settings and $options depending on whose data it is.
1259 1
		while ($row = $db->fetch_assoc($result))
1260
		{
1261
			// There are just things we shouldn't be able to change as members.
1262 1
			if ($row['id_member'] != 0 && in_array($row['variable'], $immutable_theme_data))
1263 1
				continue;
1264
1265
			// If this is the theme_dir of the default theme, store it.
1266 1
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1267 1
				$themeData[0]['default_' . $row['variable']] = $row['value'];
1268
1269
			// If this isn't set yet, is a theme option, or is not the default theme..
1270 1
			if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1271 1
				$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1272 1
		}
1273 1
		$db->free_result($result);
1274
1275
		// Set the defaults if the user has not chosen on their own
1276 1
		if (!empty($themeData[-1]))
1277 1
		{
1278 1
			foreach ($themeData[-1] as $k => $v)
1279
			{
1280 1
				if (!isset($themeData[$member][$k]))
1281 1
					$themeData[$member][$k] = $v;
1282 1
			}
1283 1
		}
1284
1285
		// If being aggressive we save the site wide and member theme settings
1286 1
		if ($cache->checkLevel(2))
1287 1
			$cache->put('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1288
		// Only if we didn't already load that part of the cache...
1289 1
		elseif (!isset($temp))
1290 1
			$cache->put('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1291 1
	}
1292
1293 1
	return $themeData;
1294
}
1295
1296
/**
1297
 * Initialize a theme for use
1298
 *
1299
 * @param int $id_theme
1300
 */
1301
function initTheme($id_theme = 0)
1302
{
1303 1
	global $user_info, $settings, $options, $context;
1304
1305
	// Validate / fetch the themes id
1306 1
	$id_theme = getThemeId($id_theme);
1307
1308
	// Need to know who we are loading the theme for
1309 1
	$member = empty($user_info['id']) ? -1 : $user_info['id'];
1310
1311
	// Load in the theme variables for them
1312 1
	$themeData = getThemeData($id_theme, $member);
1313
1314 1
	$settings = $themeData[0];
1315 1
	$options = $themeData[$member];
1316
1317 1
	$settings['theme_id'] = $id_theme;
1318 1
	$settings['actual_theme_url'] = $settings['theme_url'];
1319 1
	$settings['actual_images_url'] = $settings['images_url'];
1320 1
	$settings['actual_theme_dir'] = $settings['theme_dir'];
1321
1322
	// Reload the templates
1323 1
	Templates::getInstance()->reloadDirectories($settings);
1324
1325
	// Setup the default theme file. In the future, this won't exist and themes will just have to extend it if they want
1326 1
	require_once($settings['default_theme_dir'] . '/Theme.php');
1327 1
	$context['default_theme_instance'] = new \Themes\DefaultTheme\Theme(1);
1328
1329
	// Check if there is a Theme file
1330 1
	if ($id_theme != 1 && !empty($settings['theme_dir']) && file_exists($settings['theme_dir'] . '/Theme.php'))
1331 1
	{
1332
		require_once($settings['theme_dir'] . '/Theme.php');
1333
1334
		$class = '\\Themes\\' . basename($settings['theme_dir']) . '\\Theme';
1335
1336
		$theme = new $class($id_theme);
1337
1338
		$context['theme_instance'] = $theme;
1339
	}
1340
	else
1341
	{
1342 1
		$context['theme_instance'] = $context['default_theme_instance'];
1343
	}
1344 1
}
1345
1346
/**
1347
 * Load a theme, by ID.
1348
 *
1349
 * What it does:
1350
 * - identify the theme to be loaded.
1351
 * - validate that the theme is valid and that the user has permission to use it
1352
 * - load the users theme settings and site settings into $options.
1353
 * - prepares the list of folders to search for template loading.
1354
 * - identify what smiley set to use.
1355
 * - sets up $context['user']
1356
 * - detects the users browser and sets a mobile friendly environment if needed
1357
 * - loads default JS variables for use in every theme
1358
 * - loads default JS scripts for use in every theme
1359
 *
1360
 * @param int $id_theme = 0
1361
 * @param bool $initialize = true
1362
 */
1363
function loadTheme($id_theme = 0, $initialize = true)
0 ignored issues
show
Coding Style introduced by
loadTheme uses the super-global variable $_COOKIE which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadTheme uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadTheme uses the super-global variable $_REQUEST which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1364
{
1365 1
	global $user_info, $user_settings;
1366 1
	global $txt, $scripturl, $mbname, $modSettings;
1367 1
	global $context, $settings, $options;
1368
1369 1
	initTheme($id_theme);
1370
1371 1
	if (!$initialize)
1372 1
		return;
1373
1374 1
	loadThemeUrls();
1375
1376 1
	loadUserContext();
1377
1378
	// Set up some additional interface preference context
1379 1
	$context['admin_preferences'] = !empty($options['admin_preferences']) ? unserialize($options['admin_preferences']) : array();
1380
1381 1
	if (!$user_info['is_guest'])
1382 1
		$context['minmax_preferences'] = !empty($options['minmax_preferences']) ? unserialize($options['minmax_preferences']) : array();
1383
	// Guest may have collapsed the header, check the cookie to prevent collapse jumping
1384 1
	elseif ($user_info['is_guest'] && isset($_COOKIE['upshrink']))
1385
		$context['minmax_preferences'] = array('upshrink' => $_COOKIE['upshrink']);
1386
1387
	// @todo when are these set before loadTheme(0, true)?
1388 1
	loadThemeContext();
1389
1390
	// @todo These really don't belong in loadTheme() since they are more general than the theme.
1391 1
	$context['session_var'] = $_SESSION['session_var'];
1392 1
	$context['session_id'] = $_SESSION['session_value'];
1393 1
	$context['forum_name'] = $mbname;
1394 1
	$context['forum_name_html_safe'] = $context['forum_name'];
1395 1
	$context['current_action'] = isset($_REQUEST['action']) ? $_REQUEST['action'] : null;
1396 1
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
1397
1398
	// Set some permission related settings.
1399 1
	if ($user_info['is_guest'] && !empty($modSettings['enableVBStyleLogin']))
1400 1
	{
1401 1
		$context['show_login_bar'] = true;
1402 1
		$context['theme_header_callbacks'][] = 'login_bar';
1403 1
		loadJavascriptFile('sha256.js', array('defer' => true));
1404 1
	}
1405
1406
	// This determines the server... not used in many places, except for login fixing.
1407 1
	detectServer();
1408
1409
	// Detect the browser. This is separated out because it's also used in attachment downloads
1410 1
	detectBrowser();
1411
1412
	// Set the top level linktree up.
1413 1
	array_unshift($context['linktree'], array(
1414 1
		'url' => $scripturl,
1415 1
		'name' => $context['forum_name']
1416 1
	));
1417
1418
	// Just some mobile-friendly settings
1419 1
	if ($context['browser_body_id'] == 'mobile')
1420 1
	{
1421
		// Disable the preview text.
1422
		$modSettings['message_index_preview'] = 0;
1423
		// Force the usage of click menu instead of a hover menu.
1424
		$options['use_click_menu'] = 1;
1425
		// No space left for a sidebar
1426
		$options['use_sidebar_menu'] = false;
1427
		// Disable the search dropdown.
1428
		$modSettings['search_dropdown'] = false;
1429
	}
1430
1431 1
	if (!isset($txt))
1432 1
		$txt = array();
1433
1434
	// Load the basic layers
1435 1
	theme()->loadDefaultLayers();
1436
1437
	// Defaults in case of odd things
1438 1
	$settings['avatars_on_indexes'] = 0;
1439
1440
	// Initialize the theme.
1441 1
	if (function_exists('template_init'))
1442 1
		$settings = array_merge($settings, template_init());
1443
1444
	// Call initialization theme integration functions.
1445 1
	call_integration_hook('integrate_init_theme', array($id_theme, &$settings));
1446
1447
	// Guests may still need a name.
1448 1 View Code Duplication
	if ($context['user']['is_guest'] && empty($context['user']['name']))
1449 1
		$context['user']['name'] = $txt['guest_title'];
1450
1451
	// Any theme-related strings that need to be loaded?
1452 1
	if (!empty($settings['require_theme_strings']))
1453 1
		loadLanguage('ThemeStrings', '', false);
1454
1455
	// Load font Awesome fonts
1456 1
	loadCSSFile('font-awesome.min.css');
1457
1458
	// We allow theme variants, because we're cool.
1459 1
	if (!empty($settings['theme_variants']))
1460 1
	{
1461 1
		theme()->loadThemeVariant();
1462 1
	}
1463
1464
	// A bit lonely maybe, though I think it should be set up *after* the theme variants detection
1465 1
	$context['header_logo_url_html_safe'] = empty($settings['header_logo_url']) ? $settings['images_url'] . '/' . $context['theme_variant_url'] . 'logo_elk.png' : Util::htmlspecialchars($settings['header_logo_url']);
1466
1467
	// Allow overriding the board wide time/number formats.
1468 1
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
1469 1
		$user_info['time_format'] = $txt['time_format'];
1470
1471 1 View Code Duplication
	if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'always')
1472 1
	{
1473
		$settings['theme_url'] = $settings['default_theme_url'];
1474
		$settings['images_url'] = $settings['default_images_url'];
1475
		$settings['theme_dir'] = $settings['default_theme_dir'];
1476
	}
1477
1478
	// Make a special URL for the language.
1479 1
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
1480
1481
	// RTL languages require an additional stylesheet.
1482 1
	if ($context['right_to_left'])
1483 1
		loadCSSFile('rtl.css');
1484
1485 1 View Code Duplication
	if (!empty($context['theme_variant']) && $context['right_to_left'])
1486 1
		loadCSSFile($context['theme_variant'] . '/rtl' . $context['theme_variant'] . '.css');
1487
1488
	// This allows us to change the way things look for the admin.
1489 1
	$context['admin_features'] = isset($modSettings['admin_features']) ? explode(',', $modSettings['admin_features']) : array('cd,cp,k,w,rg,ml,pm');
1490
1491 1
	if (!empty($modSettings['xmlnews_enable']) && (!empty($modSettings['allow_guestAccess']) || $context['user']['is_logged']))
1492 1
		$context['newsfeed_urls'] = array(
1493 1
			'rss' => $scripturl . '?action=.xml;type=rss2;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5),
1494 1
			'atom' => $scripturl . '?action=.xml;type=atom;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5)
1495 1
		);
1496
1497 1
	theme()->loadThemeJavascript();
1498
1499 1
	Hooks::get()->newPath(array('$themedir' => $settings['theme_dir']));
1500
1501
	// Any files to include at this point?
1502 1
	call_integration_include_hook('integrate_theme_include');
1503
1504
	// Call load theme integration functions.
1505 1
	call_integration_hook('integrate_load_theme');
1506
1507
	// We are ready to go.
1508 1
	$context['theme_loaded'] = true;
1509 1
}
1510
1511
function loadThemeUrls()
0 ignored issues
show
Coding Style introduced by
loadThemeUrls uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
Coding Style introduced by
loadThemeUrls uses the super-global variable $_GET which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1512
{
1513 1
	global $scripturl, $boardurl, $modSettings;
1514
1515
	// Check to see if they're accessing it from the wrong place.
1516 1
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1517 1
	{
1518
		$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://';
1519
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1520
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1521
		if ($temp != '/')
1522
			$detected_url .= $temp;
1523
	}
1524
1525 1
	if (isset($detected_url) && $detected_url != $boardurl)
1526 1
	{
1527
		// Try #1 - check if it's in a list of alias addresses.
1528
		if (!empty($modSettings['forum_alias_urls']))
1529
		{
1530
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1531
			foreach ($aliases as $alias)
1532
			{
1533
				// Rip off all the boring parts, spaces, etc.
1534
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1535
					$do_fix = true;
1536
			}
1537
		}
1538
1539
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1540
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && ELK != 'SSI')
1541
		{
1542
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1543
			if (empty($_GET))
1544
				redirectexit('wwwRedirect');
1545
			else
1546
			{
1547
				list ($k, $v) = each($_GET);
1548
				if ($k != 'wwwRedirect')
1549
					redirectexit('wwwRedirect;' . $k . '=' . $v);
1550
			}
1551
		}
1552
1553
		// #3 is just a check for SSL...
1554
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1555
			$do_fix = true;
1556
1557
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1558
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1559
		{
1560
			fixThemeUrls($detected_url);
1561
		}
1562
	}
1563 1
}
1564
1565
function loadThemeContext()
1566
{
1567 1
	global $context, $settings, $modSettings;
1568
1569
	// Some basic information...
1570 1
	if (!isset($context['html_headers']))
1571 1
		$context['html_headers'] = '';
1572 1
	if (!isset($context['links']))
1573 1
		$context['links'] = array();
1574 1
	if (!isset($context['javascript_files']))
1575 1
		$context['javascript_files'] = array();
1576 1
	if (!isset($context['css_files']))
1577 1
		$context['css_files'] = array();
1578 1
	if (!isset($context['css_rules']))
1579 1
		$context['css_rules'] = array();
1580 1
	if (!isset($context['javascript_inline']))
1581 1
		$context['javascript_inline'] = array('standard' => array(), 'defer' => array());
1582 1
	if (!isset($context['javascript_vars']))
1583 1
		$context['javascript_vars'] = array();
1584
1585
	// Set a couple of bits for the template.
1586 1
	$context['right_to_left'] = !empty($txt['lang_rtl']);
0 ignored issues
show
Bug introduced by
The variable $txt seems to never exist, and therefore empty should always return true. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1587 1
	$context['tabindex'] = 1;
1588
1589 1
	$context['theme_variant'] = '';
1590 1
	$context['theme_variant_url'] = '';
1591
1592 1
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
1593 1
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
1594
1595 1
	foreach (array('theme_header', 'upper_content') as $call)
1596
	{
1597 1
		if (!isset($context[$call . '_callbacks']))
1598 1
			$context[$call . '_callbacks'] = array();
1599 1
	}
1600
1601
	// This allows sticking some HTML on the page output - useful for controls.
1602 1
	$context['insert_after_template'] = '';
1603 1
}
1604
1605
function loadUserContext()
1606
{
1607 1
	global $context, $user_info, $txt, $modSettings;
1608
1609
	// Set up the contextual user array.
1610 1
	$context['user'] = array(
1611 1
		'id' => $user_info['id'],
1612 1
		'is_logged' => !$user_info['is_guest'],
1613 1
		'is_guest' => &$user_info['is_guest'],
1614 1
		'is_admin' => &$user_info['is_admin'],
1615 1
		'is_mod' => &$user_info['is_mod'],
1616 1
		'is_moderator' => &$user_info['is_moderator'],
1617
		// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1618 1
		'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'])))),
1619 1
		'username' => $user_info['username'],
1620 1
		'language' => $user_info['language'],
1621 1
		'email' => $user_info['email'],
1622 1
		'ignoreusers' => $user_info['ignoreusers'],
1623
	);
1624
1625
	// Something for the guests
1626 1
	if (!$context['user']['is_guest'])
1627 1
		$context['user']['name'] = $user_info['name'];
1628 1 View Code Duplication
	elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1629 1
		$context['user']['name'] = $txt['guest_title'];
1630
1631 1
	$context['user']['smiley_set'] = determineSmileySet($user_info['smiley_set'], $modSettings['smiley_sets_known']);
1632 1
	$context['smiley_enabled'] = $user_info['smiley_set'] !== 'none';
1633 1
	$context['user']['smiley_path'] = $modSettings['smileys_url'] . '/' . $context['user']['smiley_set'] . '/';
1634 1
}
1635
1636
function fixThemeUrls($detected_url)
0 ignored issues
show
Coding Style introduced by
fixThemeUrls uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
1637
{
1638
	global $boardurl, $scripturl, $settings, $modSettings, $context;
1639
1640
	// Caching is good ;).
1641
	$oldurl = $boardurl;
1642
1643
	// Fix $boardurl and $scripturl.
1644
	$boardurl = $detected_url;
1645
	$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1646
	$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1647
1648
	// Fix the theme urls...
1649
	$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1650
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1651
	$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1652
	$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1653
	$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1654
	$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1655
1656
	// And just a few mod settings :).
1657
	$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1658
	$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1659
1660
	// Clean up after loadBoard().
1661
	if (isset($board_info['moderators']))
0 ignored issues
show
Bug introduced by
The variable $board_info seems to never exist, and therefore isset should always return false. Did you maybe rename this variable?

This check looks for calls to isset(...) or empty() on variables that are yet undefined. These calls will always produce the same result and can be removed.

This is most likely caused by the renaming of a variable or the removal of a function/method parameter.

Loading history...
1662
	{
1663
		foreach ($board_info['moderators'] as $k => $dummy)
1664
		{
1665
			$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1666
			$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1667
		}
1668
	}
1669
1670
	foreach ($context['linktree'] as $k => $dummy)
1671
		$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1672
}
1673
1674
/**
1675
 * Determine the current user's smiley set
1676
 *
1677
 * @param mixed[] $user_smiley_set
1678
 * @param mixed[] $known_smiley_sets
1679
 *
1680
 * @return mixed
1681
 */
1682
function determineSmileySet($user_smiley_set, $known_smiley_sets)
1683
{
1684 1
	global $modSettings, $settings;
1685
1686 1
	if ((!in_array($user_smiley_set, explode(',', $known_smiley_sets)) && $user_smiley_set !== 'none') || empty($modSettings['smiley_sets_enable']))
1687 1
	{
1688 1
		$set = !empty($settings['smiley_sets_default']) ? $settings['smiley_sets_default'] : $modSettings['smiley_sets_default'];
1689 1
	}
1690
	else
1691
	{
1692
		$set = $user_smiley_set;
1693
	}
1694
1695 1
	return $set;
1696
}
1697
1698
/**
1699
 * This loads the bare minimum data.
1700
 *
1701
 * - Needed by scheduled tasks,
1702
 * - Needed by any other code that needs language files before the forum (the theme) is loaded.
1703
 */
1704
function loadEssentialThemeData()
1705
{
1706
	global $settings, $modSettings, $mbname, $context;
1707
1708
	$db = database();
1709
1710
	// Get all the default theme variables.
1711
	$db->fetchQueryCallback('
1712
		SELECT id_theme, variable, value
1713
		FROM {db_prefix}themes
1714
		WHERE id_member = {int:no_member}
1715
			AND id_theme IN (1, {int:theme_guests})',
1716
		array(
1717
			'no_member' => 0,
1718
			'theme_guests' => $modSettings['theme_guests'],
1719
		),
1720
		function($row)
1721
		{
1722
			global $settings;
1723
1724
			$settings[$row['variable']] = $row['value'];
1725
1726
			// Is this the default theme?
1727
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
1728
				$settings['default_' . $row['variable']] = $row['value'];
1729
		}
1730
	);
1731
1732
	// Check we have some directories setup.
1733
	if (!Templates::getInstance()->hasDirectories())
1734
	{
1735
		Templates::getInstance()->reloadDirectories($settings);
1736
	}
1737
1738
	// Assume we want this.
1739
	$context['forum_name'] = $mbname;
1740
	$context['forum_name_html_safe'] = $context['forum_name'];
1741
1742
	loadLanguage('index+Addons');
1743
}
1744
1745
/**
1746
 * Load a template - if the theme doesn't include it, use the default.
1747
 *
1748
 * What it does:
1749
 * - loads a template file with the name template_name from the current, default, or base theme.
1750
 * - detects a wrong default theme directory and tries to work around it.
1751
 * - can be used to only load style sheets by using false as the template name
1752
 *   loading of style sheets with this function is deprecated, use loadCSSFile instead
1753
 * - if $settings['template_dirs'] is empty, it delays the loading of the template
1754
 *
1755
 * @uses the requireTemplate() function to actually load the file.
1756
 * @param string|false $template_name
1757
 * @param string[]|string $style_sheets any style sheets to load with the template
1758
 * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
1759
 *
1760
 * @return boolean|null
1761
 */
1762
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
1763
{
1764 1
	return Templates::getInstance()->load($template_name, $style_sheets, $fatal);
1765
}
1766
1767
/**
1768
 * Load a sub-template.
1769
 *
1770
 * What it does:
1771
 * - loads the sub template specified by sub_template_name, which must be in an already-loaded template.
1772
 * - if ?debug is in the query string, shows administrators a marker after every sub template
1773
 * for debugging purposes.
1774
 *
1775
 * @param string $sub_template_name
1776
 * @param bool|string $fatal = false
1777
 * - $fatal = true is for templates that shouldn't get a 'pretty' error screen
1778
 * - $fatal = 'ignore' to skip
1779
 */
1780
function loadSubTemplate($sub_template_name, $fatal = false)
1781
{
1782
	return Templates::getInstance()->loadSubTemplate($sub_template_name, $fatal);
1783
}
1784
1785
/**
1786
 * Add a CSS file for output later
1787
 *
1788
 * @param string[]|string $filenames string or array of filenames to work on
1789
 * @param mixed[] $params = array()
1790
 * Keys are the following:
1791
 * - ['local'] (true/false): define if the file is local
1792
 * - ['fallback'] (true/false): if false  will attempt to load the file
1793
 *   from the default theme if not found in the current theme
1794
 * - ['stale'] (true/false/string): if true or null, use cache stale,
1795
 *   false do not, or used a supplied string
1796
 * @param string $id optional id to use in html id=""
1797
 */
1798
function loadCSSFile($filenames, $params = array(), $id = '')
1799
{
1800 1
	global $context;
1801
1802 1
	if (empty($filenames))
1803 1
		return;
1804
1805 1
	if (!is_array($filenames))
1806 1
		$filenames = array($filenames);
1807
1808 1
	if (in_array('admin.css', $filenames))
1809 1
		$filenames[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css';
1810
1811 1
	$params['subdir'] = 'css';
1812 1
	$params['extension'] = 'css';
1813 1
	$params['index_name'] = 'css_files';
1814 1
	$params['debug_index'] = 'sheets';
1815
1816 1
	loadAssetFile($filenames, $params, $id);
1817 1
}
1818
1819
/**
1820
 * Add a Javascript file for output later
1821
 *
1822
 * What it does:
1823
 * - Can be passed an array of filenames, all which will have the same
1824
 *   parameters applied,
1825
 * - if you need specific parameters on a per file basis, call it multiple times
1826
 *
1827
 * @param string[]|string $filenames string or array of filenames to work on
1828
 * @param mixed[] $params = array()
1829
 * Keys are the following:
1830
 * - ['local'] (true/false): define if the file is local, if file does not
1831
 *     start with http its assumed local
1832
 * - ['defer'] (true/false): define if the file should load in <head> or before
1833
 *     the closing <html> tag
1834
 * - ['fallback'] (true/false): if true will attempt to load the file from the
1835
 *     default theme if not found in the current this is the default behavior
1836
 *     if this is not supplied
1837
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
1838
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
1839
 *     not, or used a supplied string
1840
 * @param string $id = '' optional id to use in html id=""
1841
 */
1842
function loadJavascriptFile($filenames, $params = array(), $id = '')
1843
{
1844 1
	if (empty($filenames))
1845 1
		return;
1846
1847 1
	$params['subdir'] = 'scripts';
1848 1
	$params['extension'] = 'js';
1849 1
	$params['index_name'] = 'js_files';
1850 1
	$params['debug_index'] = 'javascript';
1851
1852 1
	loadAssetFile($filenames, $params, $id);
1853 1
}
1854
1855
/**
1856
 * Add an asset (css, js or other) file for output later
1857
 *
1858
 * What it does:
1859
 * - Can be passed an array of filenames, all which will have the same
1860
 *   parameters applied,
1861
 * - If you need specific parameters on a per file basis, call it multiple times
1862
 *
1863
 * @param string[]|string $filenames string or array of filenames to work on
1864
 * @param mixed[] $params = array()
1865
 * Keys are the following:
1866
 * - ['subdir'] (string): the subdirectory of the theme dir the file is in
1867
 * - ['extension'] (string): the extension of the file (e.g. css)
1868
 * - ['index_name'] (string): the $context index that holds the array of loaded
1869
 *     files
1870
 * - ['debug_index'] (string): the index that holds the array of loaded
1871
 *     files for debugging debug
1872
 * - ['local'] (true/false): define if the file is local, if file does not
1873
 *     start with http or // (schema-less URLs) its assumed local.
1874
 *     The parameter is in fact useful only for files whose name starts with
1875
 *     http, in any other case (e.g. passing a local URL) the parameter would
1876
 *     fail in properly adding the file to the list.
1877
 * - ['defer'] (true/false): define if the file should load in <head> or before
1878
 *     the closing <html> tag
1879
 * - ['fallback'] (true/false): if true will attempt to load the file from the
1880
 *     default theme if not found in the current this is the default behavior
1881
 *     if this is not supplied
1882
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
1883
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
1884
 *     not, or used a supplied string
1885
 * @param string $id = '' optional id to use in html id=""
1886
 */
1887
function loadAssetFile($filenames, $params = array(), $id = '')
1888
{
1889 1
	global $settings, $context, $db_show_debug;
1890
1891 1
	if (empty($filenames))
1892 1
		return;
1893
1894 1
	$cache = Cache::instance();
1895
1896 1
	if (!is_array($filenames))
1897 1
		$filenames = array($filenames);
1898
1899
	// Static values for all these settings
1900 1
	if (!isset($params['stale']) || $params['stale'] === true)
1901 1
		$staler_string = CACHE_STALE;
1902
	elseif (is_string($params['stale']))
1903
		$staler_string = ($params['stale'][0] === '?' ? $params['stale'] : '?' . $params['stale']);
1904
	else
1905
		$staler_string = '';
1906
1907 1
	$fallback = (!empty($params['fallback']) && ($params['fallback'] === false)) ? false : true;
1908 1
	$dir = '/' . $params['subdir'] . '/';
1909
1910
	// Whoa ... we've done this before yes?
1911 1
	$cache_name = 'load_' . $params['extension'] . '_' . hash('md5', $settings['theme_dir'] . implode('_', $filenames));
1912 1
	if ($cache->getVar($temp, $cache_name, 600))
1913 1
	{
1914
		if (empty($context[$params['index_name']]))
1915
			$context[$params['index_name']] = array();
1916
1917
		$context[$params['index_name']] += $temp;
0 ignored issues
show
Bug introduced by
The variable $temp does not exist. Did you forget to declare it?

This check marks access to variables or properties that have not been declared yet. While PHP has no explicit notion of declaring a variable, accessing it before a value is assigned to it is most likely a bug.

Loading history...
1918
1919
		if ($db_show_debug === true)
1920
		{
1921
			foreach ($temp as $temp_params)
1922
			{
1923
				$context['debug'][$params['debug_index']][] = $temp_params['options']['basename'] . '(' . (!empty($temp_params['options']['local']) ? (!empty($temp_params['options']['url']) ? basename($temp_params['options']['url']) : basename($temp_params['options']['dir'])) : '') . ')';
1924
			}
1925
		}
1926
	}
1927
	else
1928
	{
1929 1
		$this_build = array();
1930
1931
		// All the files in this group use the above parameters
1932 1
		foreach ($filenames as $filename)
1933
		{
1934
			// Account for shorthand like admin.ext?xyz11 filenames
1935 1
			$has_cache_staler = strpos($filename, '.' . $params['extension'] . '?');
1936
			if ($has_cache_staler)
1937 1
			{
1938
				$cache_staler = $staler_string;
1939
				$params['basename'] = substr($filename, 0, $has_cache_staler + strlen($params['extension']) + 1);
1940
			}
1941
			else
1942
			{
1943 1
				$cache_staler = '';
1944 1
				$params['basename'] = $filename;
1945
			}
1946 1
			$this_id = empty($id) ? strtr(basename($filename), '?', '_') : $id;
1947
1948
			// Is this a local file?
1949 1
			if (!empty($params['local']) || (substr($filename, 0, 4) !== 'http' && substr($filename, 0, 2) !== '//'))
1950 1
			{
1951 1
				$params['local'] = true;
1952 1
				$params['dir'] = $settings['theme_dir'] . $dir;
1953 1
				$params['url'] = $settings['theme_url'];
1954
1955
				// Fallback if we are not already in the default theme
1956 1
				if ($fallback && ($settings['theme_dir'] !== $settings['default_theme_dir']) && !file_exists($settings['theme_dir'] . $dir . $params['basename']))
1957 1
				{
1958
					// Can't find it in this theme, how about the default?
1959
					if (file_exists($settings['default_theme_dir'] . $dir . $params['basename']))
1960
					{
1961
						$filename = $settings['default_theme_url'] . $dir . $params['basename'] . $cache_staler;
1962
						$params['dir'] = $settings['default_theme_dir'] . $dir;
1963
						$params['url'] = $settings['default_theme_url'];
1964
					}
1965
					else
1966
						$filename = false;
1967
				}
1968
				else
1969 1
					$filename = $settings['theme_url'] . $dir . $params['basename'] . $cache_staler;
1970 1
			}
1971
1972
			// Add it to the array for use in the template
1973 1
			if (!empty($filename))
1974 1
			{
1975 1
				$this_build[$this_id] = $context[$params['index_name']][$this_id] = array('filename' => $filename, 'options' => $params);
1976
1977 1
				if ($db_show_debug === true)
1978 1
					Debug::get()->add($params['debug_index'], $params['basename'] . '(' . (!empty($params['local']) ? (!empty($params['url']) ? basename($params['url']) : basename($params['dir'])) : '') . ')');
1979 1
			}
1980
1981
			// Save it so we don't have to build this so often
1982 1
			$cache->put($cache_name, $this_build, 600);
1983 1
		}
1984
	}
1985 1
}
1986
1987
/**
1988
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
1989
 *
1990
 * @param mixed[] $vars array of vars to include in the output done as 'varname' => 'var value'
1991
 * @param bool $escape = false, whether or not to escape the value
1992
 */
1993
function addJavascriptVar($vars, $escape = false)
1994
{
1995
	theme()->addJavascriptVar($vars, $escape);
1996
}
1997
1998
/**
1999
 * Add a block of inline Javascript code to be executed later
2000
 *
2001
 * What it does:
2002
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2003
 *   or for scripts that require help from PHP/whatever, this can be useful.
2004
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2005
 *
2006
 * @param string $javascript
2007
 * @param bool $defer = false, define if the script should load in <head> or before the closing <html> tag
2008
 */
2009
function addInlineJavascript($javascript, $defer = false)
2010
{
2011
	theme()->addInlineJavascript($javascript, $defer);
2012
}
2013
2014
/**
2015
 * Load a language file.
2016
 *
2017
 * - Tries the current and default themes as well as the user and global languages.
2018
 *
2019
 * @param string $template_name
2020
 * @param string $lang = ''
2021
 * @param bool $fatal = true
2022
 * @param bool $force_reload = false
2023
 * @return string The language actually loaded.
2024
 */
2025
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2026
{
2027 28
	global $user_info, $language, $settings, $modSettings;
2028 28
	global $db_show_debug, $txt;
2029 28
	static $already_loaded = array();
2030
2031
	// Default to the user's language.
2032 28
	if ($lang == '')
2033 28
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2034
2035 28
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2036 28
		return $lang;
2037
2038
	// Do we want the English version of language file as fallback?
2039 6
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2040 6
		loadLanguage($template_name, 'english', false);
2041
2042
	// Make sure we have $settings - if not we're in trouble and need to find it!
2043 6
	if (empty($settings['default_theme_dir']))
2044 6
		loadEssentialThemeData();
2045
2046
	// What theme are we in?
2047 6
	$theme_name = basename($settings['theme_url']);
2048 6
	if (empty($theme_name))
2049 6
		$theme_name = 'unknown';
2050
2051 6
	$fix_arrays = false;
2052
	// For each file open it up and write it out!
2053 6
	foreach (explode('+', $template_name) as $template)
2054
	{
2055 6
		if ($template === 'index')
2056 6
			$fix_arrays = true;
2057
2058
		// Obviously, the current theme is most important to check.
2059
		$attempts = array(
2060 6
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2061 6
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2062 6
		);
2063
2064
		// Do we have a base theme to worry about?
2065 6
		if (isset($settings['base_theme_dir']))
2066 6
		{
2067
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2068
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2069
		}
2070
2071
		// Fall back on the default theme if necessary.
2072 6
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2073 6
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2074
2075
		// Fall back on the English language if none of the preferred languages can be found.
2076 6
		if (!in_array('english', array($lang, $language)))
2077 6
		{
2078
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2079
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2080
		}
2081
2082 6
		$templates = Templates::getInstance();
2083
2084
		// Try to find the language file.
2085 6
		$found = false;
2086 6
		foreach ($attempts as $k => $file)
2087
		{
2088 6
			if (file_exists($file[0] . '/languages/' . $file[2] . '/' . $file[1] . '.' . $file[2] . '.php'))
2089 6
			{
2090
				// Include it!
2091 6
				$templates->templateInclude($file[0] . '/languages/' . $file[2] . '/' . $file[1] . '.' . $file[2] . '.php');
2092
2093
				// Note that we found it.
2094 6
				$found = true;
2095
2096 6
				break;
2097
			}
2098
			// @deprecated since 1.0 - old way of archiving language files, all in one directory
2099
			elseif (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2100
			{
2101
				// Include it!
2102
				$templates->templateInclude($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2103
2104
				// Note that we found it.
2105
				$found = true;
2106
2107
				break;
2108
			}
2109 6
		}
2110
2111
		// That couldn't be found!  Log the error, but *try* to continue normally.
2112 6
		if (!$found && $fatal)
2113 6
		{
2114
			Errors::instance()->log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2115
			break;
2116
		}
2117 6
	}
2118
2119
	if ($fix_arrays)
2120 6
		fix_calendar_text();
2121
2122
	// Keep track of what we're up to soldier.
2123 6
	if ($db_show_debug === true)
2124 6
		Debug::get()->add('language_files', $template_name . '.' . $lang . ' (' . $theme_name . ')');
2125
2126
	// Remember what we have loaded, and in which language.
2127 6
	$already_loaded[$template_name] = $lang;
2128
2129
	// Return the language actually loaded.
2130 6
	return $lang;
2131
}
2132
2133
/**
2134
 * Loads / Sets arrays for use in date display
2135
 */
2136
function fix_calendar_text()
2137
{
2138
	global $txt;
2139
2140
	$txt['days'] = array(
2141
		$txt['sunday'],
2142
		$txt['monday'],
2143
		$txt['tuesday'],
2144
		$txt['wednesday'],
2145
		$txt['thursday'],
2146
		$txt['friday'],
2147
		$txt['saturday'],
2148
	);
2149
	$txt['days_short'] = array(
2150
		$txt['sunday_short'],
2151
		$txt['monday_short'],
2152
		$txt['tuesday_short'],
2153
		$txt['wednesday_short'],
2154
		$txt['thursday_short'],
2155
		$txt['friday_short'],
2156
		$txt['saturday_short'],
2157
	);
2158
	$txt['months'] = array(
2159
		1 => $txt['january'],
2160
		$txt['february'],
2161
		$txt['march'],
2162
		$txt['april'],
2163
		$txt['may'],
2164
		$txt['june'],
2165
		$txt['july'],
2166
		$txt['august'],
2167
		$txt['september'],
2168
		$txt['october'],
2169
		$txt['november'],
2170
		$txt['december'],
2171
	);
2172
	$txt['months_titles'] = array(
2173
		1 => $txt['january_titles'],
2174
		$txt['february_titles'],
2175
		$txt['march_titles'],
2176
		$txt['april_titles'],
2177
		$txt['may_titles'],
2178
		$txt['june_titles'],
2179
		$txt['july_titles'],
2180
		$txt['august_titles'],
2181
		$txt['september_titles'],
2182
		$txt['october_titles'],
2183
		$txt['november_titles'],
2184
		$txt['december_titles'],
2185
	);
2186
	$txt['months_short'] = array(
2187
		1 => $txt['january_short'],
2188
		$txt['february_short'],
2189
		$txt['march_short'],
2190
		$txt['april_short'],
2191
		$txt['may_short'],
2192
		$txt['june_short'],
2193
		$txt['july_short'],
2194
		$txt['august_short'],
2195
		$txt['september_short'],
2196
		$txt['october_short'],
2197
		$txt['november_short'],
2198
		$txt['december_short'],
2199
	);
2200
}
2201
2202
/**
2203
 * Get all parent boards (requires first parent as parameter)
2204
 *
2205
 * What it does:
2206
 * - It finds all the parents of id_parent, and that board itself.
2207
 * - Additionally, it detects the moderators of said boards.
2208
 * - Returns an array of information about the boards found.
2209
 *
2210
 * @param int $id_parent
2211
 */
2212
function getBoardParents($id_parent)
2213
{
2214 10
	global $scripturl;
2215
2216 10
	$db = database();
2217 10
	$cache = Cache::instance();
2218
2219
	// First check if we have this cached already.
2220 10
	if (!$cache->getVar($boards, 'board_parents-' . $id_parent, 480))
0 ignored issues
show
Bug introduced by
The variable $boards seems only to be defined at a later point. Did you maybe move this code here without moving the variable definition?

This error can happen if you refactor code and forget to move the variable initialization.

Let’s take a look at a simple example:

function someFunction() {
    $x = 5;
    echo $x;
}

The above code is perfectly fine. Now imagine that we re-order the statements:

function someFunction() {
    echo $x;
    $x = 5;
}

In that case, $x would be read before it is initialized. This was a very basic example, however the principle is the same for the found issue.

Loading history...
2221 10
	{
2222 10
		$boards = array();
2223 10
		$original_parent = $id_parent;
2224
2225
		// Loop while the parent is non-zero.
2226 10
		while ($id_parent != 0)
2227
		{
2228 10
			$result = $db->query('', '
2229
				SELECT
2230
					b.id_parent, b.name, {int:board_parent} AS id_board, IFNULL(mem.id_member, 0) AS id_moderator,
2231
					mem.real_name, b.child_level
2232
				FROM {db_prefix}boards AS b
2233
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2234
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2235 10
				WHERE b.id_board = {int:board_parent}',
2236
				array(
2237 10
					'board_parent' => $id_parent,
2238
				)
2239 10
			);
2240
			// In the EXTREMELY unlikely event this happens, give an error message.
2241 10
			if ($db->num_rows($result) == 0)
2242 10
				Errors::instance()->fatal_lang_error('parent_not_found', 'critical');
2243 10
			while ($row = $db->fetch_assoc($result))
2244
			{
2245 10
				if (!isset($boards[$row['id_board']]))
2246 10
				{
2247 10
					$id_parent = $row['id_parent'];
2248 10
					$boards[$row['id_board']] = array(
2249 10
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2250 10
						'name' => $row['name'],
2251 10
						'level' => $row['child_level'],
2252 10
						'moderators' => array()
2253 10
					);
2254 10
				}
2255
				// If a moderator exists for this board, add that moderator for all children too.
2256 10
				if (!empty($row['id_moderator']))
2257 10
					foreach ($boards as $id => $dummy)
2258
					{
2259
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2260
							'id' => $row['id_moderator'],
2261
							'name' => $row['real_name'],
2262
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2263
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2264
						);
2265
					}
2266 10
			}
2267 10
			$db->free_result($result);
2268 10
		}
2269
2270 10
		$cache->put('board_parents-' . $original_parent, $boards, 480);
2271 10
	}
2272
2273 10
	return $boards;
0 ignored issues
show
Bug introduced by
The variable $boards 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...
2274
}
2275
2276
/**
2277
 * Attempt to reload our known languages.
2278
 *
2279
 * @param bool $use_cache = true
2280
 */
2281
function getLanguages($use_cache = true)
2282
{
2283
	global $settings;
2284
2285
	$cache = Cache::instance();
2286
2287
	// Either we don't use the cache, or its expired.
2288
	$languages = array();
2289
2290
	if (!$use_cache || !$cache->getVar($languages, 'known_languages', !$cache->checkLevel(1) ? 86400 : 3600))
2291
	{
2292
		// If we don't have our theme information yet, lets get it.
2293
		if (empty($settings['default_theme_dir']))
2294
			loadTheme(0, false);
2295
2296
		// Default language directories to try.
2297
		$language_directories = array(
2298
			$settings['default_theme_dir'] . '/languages',
2299
			$settings['actual_theme_dir'] . '/languages',
2300
		);
2301
2302
		// We possibly have a base theme directory.
2303
		if (!empty($settings['base_theme_dir']))
2304
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2305
2306
		// Remove any duplicates.
2307
		$language_directories = array_unique($language_directories);
2308
2309
		foreach ($language_directories as $language_dir)
2310
		{
2311
			// Can't look in here... doesn't exist!
2312
			if (!file_exists($language_dir))
2313
				continue;
2314
2315
			$dir = dir($language_dir);
2316
			while ($entry = $dir->read())
2317
			{
2318
				// Only directories are interesting
2319
				if ($entry == '..' || !is_dir($dir->path . '/' . $entry))
0 ignored issues
show
Bug introduced by
The property path does not seem to exist in Directory.

An attempt at access to an undefined property has been detected. This may either be a typographical error or the property has been renamed but there are still references to its old name.

If you really want to allow access to undefined properties, you can define magic methods to allow access. See the php core documentation on Overloading.

Loading history...
2320
					continue;
2321
2322
				// @todo at some point we may want to simplify that stuff (I mean scanning all the files just for index)
2323
				$file_dir = dir($dir->path . '/' . $entry);
2324
				while ($file_entry = $file_dir->read())
2325
				{
2326
					// Look for the index language file....
2327
					if (!preg_match('~^index\.(.+)\.php$~', $file_entry, $matches))
2328
						continue;
2329
2330
					$languages[$matches[1]] = array(
2331
						'name' => Util::ucwords(strtr($matches[1], array('_' => ' '))),
2332
						'selected' => false,
2333
						'filename' => $matches[1],
2334
						'location' => $language_dir . '/' . $entry . '/index.' . $matches[1] . '.php',
2335
					);
2336
				}
2337
				$file_dir->close();
2338
			}
2339
			$dir->close();
2340
		}
2341
2342
		// Lets cash in on this deal.
2343
		$cache->put('known_languages', $languages, $cache->isEnabled() && !Cache::instance()->checkLevel(1) ? 86400 : 3600);
2344
	}
2345
2346
	return $languages;
2347
}
2348
2349
/**
2350
 * Initialize a database connection.
2351
 */
2352
function loadDatabase()
2353
{
2354
	global $db_persist, $db_server, $db_user, $db_passwd, $db_port;
2355
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix;
2356
2357
	// Database stuffs
2358
	require_once(SOURCEDIR . '/database/Database.subs.php');
2359
2360
	// Figure out what type of database we are using.
2361
	if (empty($db_type) || !file_exists(SOURCEDIR . '/database/Db-' . $db_type . '.class.php'))
2362
		$db_type = 'mysql';
2363
2364
	// 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.
2365
	if (ELK === 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
2366
		$connection = elk_db_initiate($db_server, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix, array('persist' => $db_persist, 'non_fatal' => true, 'dont_select_db' => true, 'port' => $db_port), $db_type);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $connection is correct as elk_db_initiate($db_serv...=> $db_port), $db_type) (which targets elk_db_initiate()) seems to always return null.

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

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

}

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

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

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

Loading history...
2367
2368
	// Either we aren't in SSI mode, or it failed.
2369
	if (empty($connection))
2370
		$connection = elk_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, array('persist' => $db_persist, 'dont_select_db' => ELK === 'SSI', 'port' => $db_port), $db_type);
0 ignored issues
show
Bug introduced by
Are you sure the assignment to $connection is correct as elk_db_initiate($db_serv...=> $db_port), $db_type) (which targets elk_db_initiate()) seems to always return null.

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

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

}

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

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

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

Loading history...
2371
2372
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
2373
	if (!$connection)
2374
		Errors::instance()->display_db_error();
2375
2376
	// If in SSI mode fix up the prefix.
2377
	$db = database();
2378
	if (ELK === 'SSI')
2379
		$db_prefix = $db->fix_prefix($db_prefix, $db_name);
2380
2381
	// Case sensitive database? Let's define a constant.
2382
	if ($db->db_case_sensitive() && !defined('DB_CASE_SENSITIVE'))
2383
		DEFINE('DB_CASE_SENSITIVE', '1');
2384
}
2385
2386
/**
2387
 * Determine the user's avatar type and return the information as an array
2388
 *
2389
 * @todo this function seems more useful than expected, it should be improved. :P
2390
 *
2391
 * @param mixed[] $profile array containing the users profile data
2392
 * @return mixed[] $avatar
2393
 */
2394
function determineAvatar($profile)
2395
{
2396 1
	global $modSettings, $scripturl, $settings;
2397
2398 1
	if (empty($profile))
2399 1
		return array();
2400
2401 1
	$avatar_protocol = substr(strtolower($profile['avatar']), 0, 7);
2402
2403
	// uploaded avatar?
2404 1
	if ($profile['id_attach'] > 0 && empty($profile['avatar']))
2405 1
	{
2406
		// where are those pesky avatars?
2407
		$avatar_url = empty($profile['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $profile['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
2408
2409
		$avatar = array(
2410
			'name' => $profile['avatar'],
2411
			'image' => '<img class="avatar avatarresize" src="' . $avatar_url . '" alt="" />',
2412
			'href' => $avatar_url,
2413
			'url' => '',
2414
		);
2415
	}
2416
	// remote avatar?
2417 1
	elseif ($avatar_protocol === 'http://' || $avatar_protocol === 'https:/')
2418
	{
2419
		$avatar = array(
2420
			'name' => $profile['avatar'],
2421
			'image' => '<img class="avatar avatarresize" src="' . $profile['avatar'] . '" alt="" />',
2422
			'href' => $profile['avatar'],
2423
			'url' => $profile['avatar'],
2424
		);
2425
	}
2426
	// Gravatar instead?
2427 1
	elseif (!empty($profile['avatar']) && $profile['avatar'] === 'gravatar')
2428
	{
2429
		// Gravatars URL.
2430
		$gravatar_url = '//www.gravatar.com/avatar/' . hash('md5', strtolower($profile['email_address'])) . '?s=' . $modSettings['avatar_max_height'] . (!empty($modSettings['gravatar_rating']) ? ('&amp;r=' . $modSettings['gravatar_rating']) : '');
2431
2432
		$avatar = array(
2433
			'name' => $profile['avatar'],
2434
			'image' => '<img class="avatar avatarresize" src="' . $gravatar_url . '" alt="" />',
2435
			'href' => $gravatar_url,
2436
			'url' => $gravatar_url,
2437
		);
2438
	}
2439
	// an avatar from the gallery?
2440 1
	elseif (!empty($profile['avatar']) && !($avatar_protocol === 'http://' || $avatar_protocol === 'https:/'))
2441
	{
2442
		$avatar = array(
2443
			'name' => $profile['avatar'],
2444
			'image' => '<img class="avatar avatarresize" src="' . $modSettings['avatar_url'] . '/' . $profile['avatar'] . '" alt="" />',
2445
			'href' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
2446
			'url' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
2447
		);
2448
	}
2449
	// no custom avatar found yet, maybe a default avatar?
2450 1
	elseif (!empty($modSettings['avatar_default']) && empty($profile['avatar']) && empty($profile['filename']))
2451
	{
2452
		// $settings not initialized? We can't do anything further..
2453
		if (!empty($settings))
2454
		{
2455
			// Let's proceed with the default avatar.
2456
			$avatar = array(
2457
				'name' => '',
2458
				'image' => '<img class="avatar avatarresize" src="' . $settings['images_url'] . '/default_avatar.png" alt="" />',
2459
				'href' => $settings['images_url'] . '/default_avatar.png',
2460
				'url' => 'http://',
2461
			);
2462
		}
2463
		else
2464
		{
2465
			$avatar = array();
2466
		}
2467
	}
2468
	// finally ...
2469
	else
2470
		$avatar = array(
2471 1
			'name' => '',
2472 1
			'image' => '',
2473 1
			'href' => '',
2474
			'url' => ''
2475 1
		);
2476
2477
	// Make sure there's a preview for gravatars available.
2478 1
	$avatar['gravatar_preview'] = '//www.gravatar.com/avatar/' . hash('md5', strtolower($profile['email_address'])) . '?s=' . $modSettings['avatar_max_height'] . (!empty($modSettings['gravatar_rating']) ? ('&amp;r=' . $modSettings['gravatar_rating']) : '');
2479
2480 1
	call_integration_hook('integrate_avatar', array(&$avatar, $profile));
2481
2482 1
	return $avatar;
2483
}
2484
2485
/**
2486
 * Get information about the server
2487
 */
2488
function detectServer()
0 ignored issues
show
Coding Style introduced by
detectServer uses the super-global variable $_SERVER which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2489
{
2490 8
	global $context;
2491 8
	static $server = null;
2492
2493 8
	if ($server === null)
2494 8
	{
2495 1
		$server = new Server($_SERVER);
2496 1
		$servers = array('iis', 'apache', 'litespeed', 'lighttpd', 'nginx', 'cgi', 'windows');
2497 1
		$context['server'] = array();
2498 1
		foreach ($servers as $name)
2499
		{
2500 1
			$context['server']['is_' . $name] = $server->is($name);
2501 1
		}
2502
2503 1
		$context['server']['iso_case_folding'] = $server->is('iso_case_folding');
2504
		// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
2505 1
		$context['server']['needs_login_fix'] = $server->is('needs_login_fix');
2506 1
	}
2507
2508 8
	return $server;
2509
}
2510
2511
function serverIs($server)
2512
{
2513
	return detectServer()->is($server);
2514
}
2515
2516
/**
2517
 * Do some important security checks:
2518
 *
2519
 * What it does:
2520
 * - checks the existence of critical files e.g. install.php
2521
 * - checks for an active admin session.
2522
 * - checks cache directory is writable.
2523
 * - calls secureDirectory to protect attachments & cache.
2524
 * - checks if the forum is in maintenance mode.
2525
 */
2526
function doSecurityChecks()
0 ignored issues
show
Coding Style introduced by
doSecurityChecks uses the super-global variable $_SESSION which is generally not recommended.

Instead of super-globals, we recommend to explicitly inject the dependencies of your class. This makes your code less dependent on global state and it becomes generally more testable:

// Bad
class Router
{
    public function generate($path)
    {
        return $_SERVER['HOST'].$path;
    }
}

// Better
class Router
{
    private $host;

    public function __construct($host)
    {
        $this->host = $host;
    }

    public function generate($path)
    {
        return $this->host.$path;
    }
}

class Controller
{
    public function myAction(Request $request)
    {
        // Instead of
        $page = isset($_GET['page']) ? intval($_GET['page']) : 1;

        // Better (assuming you use the Symfony2 request)
        $page = $request->query->get('page', 1);
    }
}
Loading history...
2527
{
2528
	global $modSettings, $context, $maintenance, $user_info, $txt, $scripturl, $user_settings, $options;
2529
2530
	$show_warnings = false;
2531
2532
	$cache = Cache::instance();
2533
2534
	if (allowedTo('admin_forum') && !$user_info['is_guest'])
2535
	{
2536
		// If agreement is enabled, at least the english version shall exists
2537 View Code Duplication
		if ($modSettings['requireAgreement'] && !file_exists(BOARDDIR . '/agreement.txt'))
2538
		{
2539
			$context['security_controls_files']['title'] = $txt['generic_warning'];
2540
			$context['security_controls_files']['errors']['agreement'] = $txt['agreement_missing'];
2541
			$show_warnings = true;
2542
		}
2543
2544
		// Cache directory writable?
2545 View Code Duplication
		if ($cache->isEnabled() && !is_writable(CACHEDIR))
2546
		{
2547
			$context['security_controls_files']['title'] = $txt['generic_warning'];
2548
			$context['security_controls_files']['errors']['cache'] = $txt['cache_writable'];
2549
			$show_warnings = true;
2550
		}
2551
2552
		if (checkSecurityFiles())
2553
			$show_warnings = true;
2554
2555
		// We are already checking so many files...just few more doesn't make any difference! :P
2556
		require_once(SUBSDIR . '/Attachments.subs.php');
2557
		$path = getAttachmentPath();
2558
		secureDirectory($path, true);
2559
		secureDirectory(CACHEDIR);
2560
2561
		// Active admin session?
2562
		if (isAdminSessionActive())
2563
			$context['warning_controls']['admin_session'] = sprintf($txt['admin_session_active'], ($scripturl . '?action=admin;area=adminlogoff;redir;' . $context['session_var'] . '=' . $context['session_id']));
2564
2565
		// Maintenance mode enabled?
2566
		if (!empty($maintenance))
2567
			$context['warning_controls']['maintenance'] = sprintf($txt['admin_maintenance_active'], ($scripturl . '?action=admin;area=serversettings;' . $context['session_var'] . '=' . $context['session_id']));
2568
2569
		// New updates
2570
		if (defined('FORUM_VERSION'))
2571
		{
2572
			$index = 'new_in_' . str_replace(array('ElkArte ', '.'), array('', '_'), FORUM_VERSION);
2573
			if (!empty($modSettings[$index]) && empty($options['dismissed_' . $index]))
2574
			{
2575
				$show_warnings = true;
2576
				$context['new_version_updates'] = array(
2577
					'title' => $txt['new_version_updates'],
2578
					'errors' => array(replaceBasicActionUrl($txt['new_version_updates_text'])),
2579
				);
2580
			}
2581
		}
2582
	}
2583
2584
	// Check for database errors.
2585
	if (!empty($_SESSION['query_command_denied']))
2586
	{
2587
		if ($user_info['is_admin'])
2588
		{
2589
			$context['security_controls_query']['title'] = $txt['query_command_denied'];
2590
			$show_warnings = true;
2591 View Code Duplication
			foreach ($_SESSION['query_command_denied'] as $command => $error)
2592
				$context['security_controls_query']['errors'][$command] = '<pre>' . Util::htmlspecialchars($error) . '</pre>';
2593
		}
2594
		else
2595
		{
2596
			$context['security_controls_query']['title'] = $txt['query_command_denied_guests'];
2597 View Code Duplication
			foreach ($_SESSION['query_command_denied'] as $command => $error)
2598
				$context['security_controls_query']['errors'][$command] = '<pre>' . sprintf($txt['query_command_denied_guests_msg'], Util::htmlspecialchars($command)) . '</pre>';
2599
		}
2600
	}
2601
2602
	// Are there any members waiting for approval?
2603
	if (allowedTo('moderate_forum') && ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) && !empty($modSettings['unapprovedMembers']))
2604
		$context['warning_controls']['unapproved_members'] = sprintf($txt[$modSettings['unapprovedMembers'] == 1 ? 'approve_one_member_waiting' : 'approve_many_members_waiting'], $scripturl . '?action=admin;area=viewmembers;sa=browse;type=approve', $modSettings['unapprovedMembers']);
2605
2606
	if (!empty($context['open_mod_reports']) && (empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1))
2607
		$context['warning_controls']['open_mod_reports'] = '<a href="' . $scripturl . '?action=moderate;area=reports">' . sprintf($txt['mod_reports_waiting'], $context['open_mod_reports']) . '</a>';
2608
2609
	if (isset($_SESSION['ban']['cannot_post']))
2610
	{
2611
		// An admin cannot be banned (technically he could), and if it is better he knows.
2612
		$context['security_controls_ban']['title'] = sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
2613
		$show_warnings = true;
2614
2615
		$context['security_controls_ban']['errors']['reason'] = '';
2616
2617
		if (!empty($_SESSION['ban']['cannot_post']['reason']))
2618
			$context['security_controls_ban']['errors']['reason'] = $_SESSION['ban']['cannot_post']['reason'];
2619
2620
		if (!empty($_SESSION['ban']['expire_time']))
2621
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . sprintf($txt['your_ban_expires'], standardTime($_SESSION['ban']['expire_time'], false)) . '</span>';
2622
		else
2623
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . $txt['your_ban_expires_never'] . '</span>';
2624
	}
2625
2626
	// Finally, let's show the layer.
2627
	if ($show_warnings || !empty($context['warning_controls']))
2628
		\Template_Layers::getInstance()->addAfter('admin_warning', 'body');
2629
}
2630
2631
/**
2632
 * Load everything necessary for the BBC parsers
2633
 */
2634
function loadBBCParsers()
2635
{
2636
	global $modSettings;
2637
2638
	// Set the default disabled BBC
2639
	if (!empty($modSettings['disabledBBC']))
2640
	{
2641
		if (!is_array($modSettings['disabledBBC']))
2642
			$disabledBBC = explode(',', $modSettings['disabledBBC']);
2643
		else
2644
			$disabledBBC = $modSettings['disabledBBC'];
2645
		\BBC\ParserWrapper::getInstance()->setDisabled($disabledBBC);
2646
	}
2647
}