Completed
Pull Request — patch_1-1-4 (#3200)
by Emanuele
11:55
created

Load.php ➔ loadThemeContext()   C

Complexity

Conditions 7
Paths 36

Size

Total Lines 40
Code Lines 22

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 24
CRAP Score 7.0222

Importance

Changes 0
Metric Value
cc 7
eloc 22
nc 36
nop 0
dl 0
loc 40
ccs 24
cts 26
cp 0.9231
crap 7.0222
rs 6.7272
c 0
b 0
f 0
1
<?php
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.2
15
 *
16
 */
17
18
/**
19
 * Load the $modSettings array and many necessary forum settings.
20
 *
21
 * What it does:
22
 *
23
 * - load the settings from cache if available, otherwise from the database.
24
 * - sets the timezone
25
 * - checks the load average settings if available.
26
 * - check whether post moderation is enabled.
27
 * - calls add_integration_function
28
 * - calls integrate_pre_include, integrate_pre_load,
29
 *
30
 * @event integrate_load_average is called if load average is enabled
31
 * @event integrate_pre_include to allow including files at startup
32
 * @event integrate_pre_load to call any pre load integration functions.
33
 *
34
 * @global array $modSettings is a giant array of all of the forum-wide settings and statistics.
35
 */
36
function reloadSettings()
37
{
38
	global $modSettings;
39
40
	$db = database();
41
	$cache = Cache::instance();
42
	$hooks = Hooks::instance();
43
44
	// Try to load it from the cache first; it'll never get cached if the setting is off.
45
	if (!$cache->getVar($modSettings, 'modSettings', 90))
46
	{
47
		$request = $db->query('', '
48
			SELECT variable, value
49
			FROM {db_prefix}settings',
50
			array(
51
			)
52
		);
53
		$modSettings = array();
54
		if (!$request)
55
			Errors::instance()->display_db_error();
56
		while ($row = $db->fetch_row($request))
57
			$modSettings[$row[0]] = $row[1];
58
		$db->free_result($request);
59
60
		// Do a few things to protect against missing settings or settings with invalid values...
61 View Code Duplication
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
62
			$modSettings['defaultMaxTopics'] = 20;
63 View Code Duplication
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
64
			$modSettings['defaultMaxMessages'] = 15;
65 View Code Duplication
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
66
			$modSettings['defaultMaxMembers'] = 30;
67
		if (empty($modSettings['subject_length']))
68
			$modSettings['subject_length'] = 24;
69
70
		$modSettings['warning_enable'] = $modSettings['warning_settings'][0];
71
72
		// @deprecated since 1.1.0 - Just in case the upgrade script was run before B3
73
		if (empty($modSettings['cal_limityear']))
74
		{
75
			$modSettings['cal_limityear'] = 10;
76
			updateSettings(array(
77
				'cal_limityear' => 10
78
			));
79
		}
80
81
		$cache->put('modSettings', $modSettings, 90);
82
	}
83
84
	$hooks->loadIntegrations();
85
86
	// Setting the timezone is a requirement for some functions in PHP >= 5.1.
87
	if (isset($modSettings['default_timezone']))
88
		date_default_timezone_set($modSettings['default_timezone']);
89
90
	// Check the load averages?
91
	if (!empty($modSettings['loadavg_enable']))
92
	{
93
		if (!$cache->getVar($modSettings['load_average'], 'loadavg', 90))
94
		{
95
			require_once(SUBSDIR . '/Server.subs.php');
96
			$modSettings['load_average'] = detectServerLoad();
97
98
			$cache->put('loadavg', $modSettings['load_average'], 90);
99
		}
100
101
		if ($modSettings['load_average'] !== false)
102
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
103
104
		// Let's have at least a zero
105
		if (empty($modSettings['loadavg_forum']) || $modSettings['load_average'] === false)
106
			$modSettings['current_load'] = 0;
107
		else
108
			$modSettings['current_load'] = $modSettings['load_average'];
109
110
		if (!empty($modSettings['loadavg_forum']) && $modSettings['current_load'] >= $modSettings['loadavg_forum'])
111
			Errors::instance()->display_loadavg_error();
112
	}
113
	else
114
		$modSettings['current_load'] = 0;
115
116
	// Is post moderation alive and well?
117
	$modSettings['postmod_active'] = isset($modSettings['admin_features']) ? in_array('pm', explode(',', $modSettings['admin_features'])) : true;
118
119
	// @deprecated since 1.0.6 compatibility setting for migration
120
	if (!isset($modSettings['avatar_max_height']))
121
	{
122
		$modSettings['avatar_max_height'] = isset($modSettings['avatar_max_height_external']) ? $modSettings['avatar_max_height_external'] : 65;
123
	}
124
	if (!isset($modSettings['avatar_max_width']))
125
	{
126
		$modSettings['avatar_max_width'] = isset($modSettings['avatar_max_width_external']) ? $modSettings['avatar_max_width_external'] : 65;
127
	}
128
129
	if (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) == 'off')
130
	{
131
		$modSettings['secureCookies'] = 0;
132
	}
133
134
	// Here to justify the name of this function. :P
135
	// It should be added to the install and upgrade scripts.
136
	// But since the converters need to be updated also. This is easier.
137
	if (empty($modSettings['currentAttachmentUploadDir']))
138
	{
139
		updateSettings(array(
140
			'attachmentUploadDir' => serialize(array(1 => $modSettings['attachmentUploadDir'])),
141
			'currentAttachmentUploadDir' => 1,
142
		));
143
	}
144
145
	// Integration is cool.
146
	if (defined('ELK_INTEGRATION_SETTINGS'))
147
	{
148
		$integration_settings = Util::unserialize(ELK_INTEGRATION_SETTINGS);
149
		foreach ($integration_settings as $hook => $function)
150
			add_integration_function($hook, $function);
151
	}
152
153
	// Any files to pre include?
154
	call_integration_include_hook('integrate_pre_include');
155
156
	// Call pre load integration functions.
157
	call_integration_hook('integrate_pre_load');
158
}
159
160
/**
161
 * Load all the important user information.
162
 *
163
 * What it does:
164
 *
165
 * - sets up the $user_info array
166
 * - assigns $user_info['query_wanna_see_board'] for what boards the user can see.
167
 * - first checks for cookie or integration validation.
168
 * - uses the current session if no integration function or cookie is found.
169
 * - checks password length, if member is activated and the login span isn't over.
170
 * - if validation fails for the user, $id_member is set to 0.
171
 * - updates the last visit time when needed.
172
 *
173
 * @event integrate_verify_user allow for integration to verify a user
174
 * @event integrate_user_info to allow for adding to $user_info array
175
 */
176
function loadUserSettings()
177
{
178
	global $context, $modSettings, $user_settings, $cookiename, $user_info, $language;
179
180
	$db = database();
181
	$cache = Cache::instance();
182
183
	// Check first the integration, then the cookie, and last the session.
184
	if (count($integration_ids = call_integration_hook('integrate_verify_user')) > 0)
185
	{
186
		$id_member = 0;
187
		foreach ($integration_ids as $integration_id)
188
		{
189
			$integration_id = (int) $integration_id;
190
			if ($integration_id > 0)
191
			{
192
				$id_member = $integration_id;
193
				$already_verified = true;
194
				break;
195
			}
196
		}
197
	}
198
	else
199
		$id_member = 0;
200
201
	// We'll need IPs and user agent and stuff, they came to visit us with!
202
	$req = request();
203
204
	if (empty($id_member) && isset($_COOKIE[$cookiename]))
205
	{
206 View Code Duplication
		list ($id_member, $password) = serializeToJson($_COOKIE[$cookiename], function ($array_from) use ($cookiename) {
207
			global $modSettings;
208
209
			require_once(SUBSDIR . '/Auth.subs.php');
210
			$_COOKIE[$cookiename] = json_encode($array_from);
211
			setLoginCookie(60 * $modSettings['cookieTime'], $array_from[0], $array_from[1]);
212
		});
213
		$id_member = !empty($id_member) && strlen($password) > 0 ? (int) $id_member : 0;
214
	}
215
	elseif (empty($id_member) && isset($_SESSION['login_' . $cookiename]) && (!empty($modSettings['disableCheckUA']) || $_SESSION['USER_AGENT'] == $req->user_agent()))
216
	{
217
		// @todo Perhaps we can do some more checking on this, such as on the first octet of the IP?
218
		list ($id_member, $password, $login_span) = serializeToJson($_SESSION['login_' . $cookiename], function ($array_from) use ($cookiename) {
219
			$_SESSION['login_' . $cookiename] = json_encode($array_from);
220
		});
221
		$id_member = !empty($id_member) && strlen($password) == 64 && $login_span > time() ? (int) $id_member : 0;
222
	}
223
224
	// Only load this stuff if the user isn't a guest.
225
	if ($id_member != 0)
226
	{
227
		// Is the member data cached?
228
		if ($cache->levelLowerThan(2) || $cache->getVar($user_settings, 'user_settings-' . $id_member, 60) === false)
229
		{
230
			$this_user = $db->fetchQuery('
231
				SELECT mem.*, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type
232
				FROM {db_prefix}members AS mem
233
					LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = {int:id_member})
234
				WHERE mem.id_member = {int:id_member}
235
				LIMIT 1',
236
				array(
237
					'id_member' => $id_member,
238
				)
239
			);
240
241
			if (!empty($this_user))
242
			{
243
				list ($user_settings) = $this_user;
244
245
				// Make the ID specifically an integer
246
				$user_settings['id_member'] = (int) $user_settings['id_member'];
247
			}
248
249
			if ($cache->levelHigherThan(1))
250
				$cache->put('user_settings-' . $id_member, $user_settings, 60);
251
		}
252
253
		// Did we find 'im?  If not, junk it.
254
		if (!empty($user_settings))
255
		{
256
			// As much as the password should be right, we can assume the integration set things up.
257
			if (!empty($already_verified) && $already_verified === true)
258
				$check = true;
259
			// SHA-256 passwords should be 64 characters long.
260
			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...
261
				$check = hash('sha256', ($user_settings['passwd'] . $user_settings['password_salt'])) == $password;
262
			else
263
				$check = false;
264
265
			// Wrong password or not activated - either way, you're going nowhere.
266
			$id_member = $check && ($user_settings['is_activated'] == 1 || $user_settings['is_activated'] == 11) ? (int) $user_settings['id_member'] : 0;
267
		}
268
		else
269
			$id_member = 0;
270
271
		// If we no longer have the member maybe they're being all hackey, stop brute force!
272
		if (!$id_member)
273
			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...
274
	}
275
276
	// Found 'im, let's set up the variables.
277
	if ($id_member != 0)
278
	{
279
		// Let's not update the last visit time in these cases...
280
		// 1. SSI doesn't count as visiting the forum.
281
		// 2. RSS feeds and XMLHTTP requests don't count either.
282
		// 3. If it was set within this session, no need to set it again.
283
		// 4. New session, yet updated < five hours ago? Maybe cache can help.
284
		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)))
285
		{
286
			// @todo can this be cached?
287
			// Do a quick query to make sure this isn't a mistake.
288
			require_once(SUBSDIR . '/Messages.subs.php');
289
			$visitOpt = basicMessageInfo($user_settings['id_msg_last_visit'], true);
290
291
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
292
293
			// If it was *at least* five hours ago...
294
			if ($visitOpt['poster_time'] < time() - 5 * 3600)
295
			{
296
				require_once(SUBSDIR . '/Members.subs.php');
297
				updateMemberData($id_member, array('id_msg_last_visit' => (int) $modSettings['maxMsgID'], 'last_login' => time(), 'member_ip' => $req->client_ip(), 'member_ip2' => $req->ban_ip()));
298
				$user_settings['last_login'] = time();
299
300
				if ($cache->levelHigherThan(1))
301
					$cache->put('user_settings-' . $id_member, $user_settings, 60);
302
303
				$cache->put('user_last_visit-' . $id_member, $_SESSION['id_msg_last_visit'], 5 * 3600);
304
			}
305
		}
306
		elseif (empty($_SESSION['id_msg_last_visit']))
307
			$_SESSION['id_msg_last_visit'] = $user_settings['id_msg_last_visit'];
308
309
		$username = $user_settings['member_name'];
310
311
		if (empty($user_settings['additional_groups']))
312
			$user_info = array(
313
				'groups' => array($user_settings['id_group'], $user_settings['id_post_group'])
314
			);
315
		else
316
			$user_info = array(
317
				'groups' => array_merge(
318
					array($user_settings['id_group'], $user_settings['id_post_group']),
319
					explode(',', $user_settings['additional_groups'])
320
				)
321
			);
322
323
		// Because history has proven that it is possible for groups to go bad - clean up in case.
324
		foreach ($user_info['groups'] as $k => $v)
325
			$user_info['groups'][$k] = (int) $v;
326
327
		// This is a logged in user, so definitely not a spider.
328
		$user_info['possibly_robot'] = false;
329
	}
330
	// If the user is a guest, initialize all the critical user settings.
331
	else
332
	{
333
		// This is what a guest's variables should be.
334
		$username = '';
335
		$user_info = array('groups' => array(-1));
336
		$user_settings = array();
337
338
		if (isset($_COOKIE[$cookiename]))
339
			$_COOKIE[$cookiename] = '';
340
341
		// Create a login token if it doesn't exist yet.
342
		if (!isset($_SESSION['token']['post-login']))
343
			createToken('login');
344
		else
345
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
346
347
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
348
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
349
		{
350
			require_once(SUBSDIR . '/SearchEngines.subs.php');
351
			$user_info['possibly_robot'] = spiderCheck();
352
		}
353
		elseif (!empty($modSettings['spider_mode']))
354
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
355
		// If we haven't turned on proper spider hunts then have a guess!
356
		else
357
		{
358
			$ci_user_agent = strtolower($req->user_agent());
359
			$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;
360
		}
361
	}
362
363
	// Set up the $user_info array.
364
	$user_info += array(
365
		'id' => $id_member,
366
		'username' => $username,
367
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
368
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
369
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
370
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
371
		'is_guest' => $id_member == 0,
372
		'is_admin' => in_array(1, $user_info['groups']),
373
		'is_mod' => false,
374
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
375
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
376
		'ip' => $req->client_ip(),
377
		'ip2' => $req->ban_ip(),
378
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
379
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
380
		'time_offset' => empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'],
381
		'avatar' => array_merge(array(
382
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
383
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
384
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
385
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
386
		), determineAvatar($user_settings)),
387
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
388
		'messages' => empty($user_settings['personal_messages']) ? 0 : $user_settings['personal_messages'],
389
		'mentions' => empty($user_settings['mentions']) ? 0 : max(0, $user_settings['mentions']),
390
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
391
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
392
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
393
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
394
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
395
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
396
		'permissions' => array(),
397
	);
398
	$user_info['groups'] = array_unique($user_info['groups']);
399
400
	// 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.
401
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
402
		unset($user_info['ignoreboards'][$tmp]);
403
404
	// Do we have any languages to validate this?
405
	if (!empty($modSettings['userLanguage']) && (!empty($_GET['language']) || !empty($_SESSION['language'])))
406
		$languages = getLanguages();
407
408
	// Allow the user to change their language if its valid.
409
	if (!empty($modSettings['userLanguage']) && !empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
410
	{
411
		$user_info['language'] = strtr($_GET['language'], './\\:', '____');
412
		$_SESSION['language'] = $user_info['language'];
413
	}
414
	elseif (!empty($modSettings['userLanguage']) && !empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
415
		$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
416
417
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
418
	if ($user_info['is_admin'])
419
		$user_info['query_see_board'] = '1=1';
420
	// Otherwise just the groups in $user_info['groups'].
421
	else
422
		$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'] : '') . ')';
423
	// Build the list of boards they WANT to see.
424
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
425
426
	// If they aren't ignoring any boards then they want to see all the boards they can see
427
	if (empty($user_info['ignoreboards']))
428
		$user_info['query_wanna_see_board'] = $user_info['query_see_board'];
429
	// Ok I guess they don't want to see all the boards
430
	else
431
		$user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))';
432
433
	if ($user_info['is_guest'] === false)
434
	{
435
		$http_request = HttpReq::instance();
436 View Code Duplication
		if (!empty($modSettings['force_accept_agreement']))
437
		{
438
			if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->action, array('reminder', 'register')) === false)
0 ignored issues
show
Documentation introduced by
The property action does not exist on object<HttpReq>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
439
			{
440
				if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount')
0 ignored issues
show
Documentation introduced by
The property action does not exist on object<HttpReq>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property area does not exist on object<HttpReq>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
441
				{
442
					$agreement = new \Agreement($user_info['language']);
443
					if (false === $agreement->checkAccepted($id_member, $modSettings['agreementRevision']))
444
					{
445
						setOldUrl('agreement_url_redirect');
446
						redirectexit('action=register;sa=agreement', true);
447
					}
448
				}
449
			}
450
		}
451 View Code Duplication
		if (!empty($modSettings['force_accept_privacy_policy']))
452
		{
453
			if (!empty($modSettings['privacypolicyRevision']) && !empty($modSettings['requirePrivacypolicy']) && in_array($http_request->action, array('reminder', 'register')) === false)
0 ignored issues
show
Documentation introduced by
The property action does not exist on object<HttpReq>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
454
			{
455
				if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount')
0 ignored issues
show
Documentation introduced by
The property action does not exist on object<HttpReq>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
Documentation introduced by
The property area does not exist on object<HttpReq>. Since you implemented __get, maybe consider adding a @property annotation.

Since your code implements the magic getter _get, this function will be called for any read access on an undefined variable. You can add the @property annotation to your class or interface to document the existence of this variable.

<?php

/**
 * @property int $x
 * @property int $y
 * @property string $text
 */
class MyLabel
{
    private $properties;

    private $allowedProperties = array('x', 'y', 'text');

    public function __get($name)
    {
        if (isset($properties[$name]) && in_array($name, $this->allowedProperties)) {
            return $properties[$name];
        } else {
            return null;
        }
    }

    public function __set($name, $value)
    {
        if (in_array($name, $this->allowedProperties)) {
            $properties[$name] = $value;
        } else {
            throw new \LogicException("Property $name is not defined.");
        }
    }

}

If the property has read access only, you can use the @property-read annotation instead.

Of course, you may also just have mistyped another name, in which case you should fix the error.

See also the PhpDoc documentation for @property.

Loading history...
456
				{
457
					$privacypol = new \PrivacyPolicy($user_info['language']);
458
					if (false === $privacypol->checkAccepted($id_member, $modSettings['privacypolicyRevision']))
459
					{
460
						setOldUrl('agreement_url_redirect');
461
						redirectexit('action=register;sa=privacypol', true);
462
					}
463
				}
464
			}
465
		}
466
	}
467
	call_integration_hook('integrate_user_info');
468
}
469
470
/**
471
 * Check for moderators and see if they have access to the board.
472
 *
473
 * What it does:
474
 *
475
 * - sets up the $board_info array for current board information.
476
 * - if cache is enabled, the $board_info array is stored in cache.
477
 * - redirects to appropriate post if only message id is requested.
478
 * - is only used when inside a topic or board.
479
 * - determines the local moderators for the board.
480
 * - adds group id 3 if the user is a local moderator for the board they are in.
481
 * - prevents access if user is not in proper group nor a local moderator of the board.
482
 *
483
 * @event integrate_load_board_query allows to add tables and columns to the query, used
484
 * to add to the $board_info array
485
 * @event integrate_loaded_board called after board_info is populated, allows to add
486
 * directly to $board_info
487
 *
488
 */
489
function loadBoard()
490
{
491 1
	global $txt, $scripturl, $context, $modSettings;
492 1
	global $board_info, $board, $topic, $user_info;
493
494 1
	$db = database();
495 1
	$cache = Cache::instance();
496
497
	// Assume they are not a moderator.
498 1
	$user_info['is_mod'] = false;
499
	// @since 1.0.5 - is_mod takes into account only local (board) moderators,
500
	// and not global moderators, is_moderator is meant to take into account both.
501 1
	$user_info['is_moderator'] = false;
502
503
	// Start the linktree off empty..
504 1
	$context['linktree'] = array();
505
506
	// Have they by chance specified a message id but nothing else?
507 1
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
508 1
	{
509
		// Make sure the message id is really an int.
510
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
511
512
		// Looking through the message table can be slow, so try using the cache first.
513
		if (!$cache->getVar($topic, 'msg_topic-' . $_REQUEST['msg'], 120))
514
		{
515
			require_once(SUBSDIR . '/Messages.subs.php');
516
			$topic = associatedTopic($_REQUEST['msg']);
517
518
			// So did it find anything?
519
			if ($topic !== false)
520
			{
521
				// Save save save.
522
				$cache->put('msg_topic-' . $_REQUEST['msg'], $topic, 120);
523
			}
524
		}
525
526
		// Remember redirection is the key to avoiding fallout from your bosses.
527
		if (!empty($topic))
528
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
529
		else
530
		{
531
			loadPermissions();
532
			loadTheme();
533
			throw new Elk_Exception('topic_gone', false);
534
		}
535
	}
536
537
	// Load this board only if it is specified.
538 1
	if (empty($board) && empty($topic))
539 1
	{
540
		$board_info = array('moderators' => array());
541
		return;
542
	}
543
544 1
	if ($cache->isEnabled() && (empty($topic) || $cache->levelHigherThan(2)))
545 1
	{
546
		// @todo SLOW?
547
		if (!empty($topic))
548
			$temp = $cache->get('topic_board-' . $topic, 120);
549
		else
550
			$temp = $cache->get('board-' . $board, 120);
551
552
		if (!empty($temp))
553
		{
554
			$board_info = $temp;
555
			$board = (int) $board_info['id'];
556
		}
557
	}
558
559 1
	if (empty($temp))
560 1
	{
561 1
		$select_columns = array();
562 1
		$select_tables = array();
563
		// Wanna grab something more from the boards table or another table at all?
564 1
		call_integration_hook('integrate_load_board_query', array(&$select_columns, &$select_tables));
565
566 1
		$request = $db->query('', '
567
			SELECT
568
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
569
				b.id_parent, c.name AS cname, COALESCE(mem.id_member, 0) AS id_moderator,
570 1
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
571
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
572 1
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . (!empty($select_columns) ? ', ' . implode(', ', $select_columns) : '') . '
573 1
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
574 1
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . (!empty($select_tables) ? '
575 1
				' . implode("\n\t\t\t\t", $select_tables) : '') . '
576
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
577
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
578
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
579 1
			WHERE b.id_board = {raw:board_link}',
580
			array(
581 1
				'current_topic' => $topic,
582 1
				'board_link' => empty($topic) ? $db->quote('{int:current_board}', array('current_board' => $board)) : 't.id_board',
583
			)
584 1
		);
585
		// If there aren't any, skip.
586 1
		if ($db->num_rows($request) > 0)
587 1
		{
588 1
			$row = $db->fetch_assoc($request);
589
590
			// Set the current board.
591 1
			if (!empty($row['id_board']))
592 1
				$board = (int) $row['id_board'];
593
594
			// Basic operating information. (globals... :/)
595
			$board_info = array(
596 1
				'id' => $board,
597 1
				'moderators' => array(),
598
				'cat' => array(
599 1
					'id' => $row['id_cat'],
600 1
					'name' => $row['cname']
601 1
				),
602 1
				'name' => $row['bname'],
603 1
				'raw_description' => $row['description'],
604 1
				'description' => $row['description'],
605 1
				'num_topics' => $row['num_topics'],
606 1
				'unapproved_topics' => $row['unapproved_topics'],
607 1
				'unapproved_posts' => $row['unapproved_posts'],
608 1
				'unapproved_user_topics' => 0,
609 1
				'parent_boards' => getBoardParents($row['id_parent']),
610 1
				'parent' => $row['id_parent'],
611 1
				'child_level' => $row['child_level'],
612 1
				'theme' => $row['id_theme'],
613 1
				'override_theme' => !empty($row['override_theme']),
614 1
				'profile' => $row['id_profile'],
615 1
				'redirect' => $row['redirect'],
616 1
				'posts_count' => empty($row['count_posts']),
617 1
				'cur_topic_approved' => empty($topic) || $row['approved'],
618 1
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
619 1
			);
620
621
			// Load the membergroups allowed, and check permissions.
622 1
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
623 1
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
624
625 1
			call_integration_hook('integrate_loaded_board', array(&$board_info, &$row));
626
627
			do
628
			{
629 1
				if (!empty($row['id_moderator']))
630 1
					$board_info['moderators'][$row['id_moderator']] = array(
631
						'id' => $row['id_moderator'],
632
						'name' => $row['real_name'],
633
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
634
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
635
					);
636
			}
637 1
			while ($row = $db->fetch_assoc($request));
638
639
			// If the board only contains unapproved posts and the user can't approve then they can't see any topics.
640
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
641 1
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
642 1
			{
643
				// Free the previous result
644
				$db->free_result($request);
645
646
				// @todo why is this using id_topic?
647
				// @todo Can this get cached?
648
				$request = $db->query('', '
649
					SELECT COUNT(id_topic)
650
					FROM {db_prefix}topics
651
					WHERE id_member_started={int:id_member}
652
						AND approved = {int:unapproved}
653
						AND id_board = {int:board}',
654
					array(
655
						'id_member' => $user_info['id'],
656
						'unapproved' => 0,
657
						'board' => $board,
658
					)
659
				);
660
661
				list ($board_info['unapproved_user_topics']) = $db->fetch_row($request);
662
			}
663
664 1
			if ($cache->isEnabled() && (empty($topic) || $cache->levelHigherThan(2)))
665 1
			{
666
				// @todo SLOW?
667
				if (!empty($topic))
668
					$cache->put('topic_board-' . $topic, $board_info, 120);
669
				$cache->put('board-' . $board, $board_info, 120);
670
			}
671 1
		}
672
		else
673
		{
674
			// Otherwise the topic is invalid, there are no moderators, etc.
675
			$board_info = array(
676
				'moderators' => array(),
677
				'error' => 'exist'
678
			);
679
			$topic = null;
680
			$board = 0;
681
		}
682 1
		$db->free_result($request);
683 1
	}
684
685 1
	if (!empty($topic))
686 1
		$_GET['board'] = (int) $board;
687
688 1
	if (!empty($board))
689 1
	{
690
		// Now check if the user is a moderator.
691 1
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]);
692
693 1 View Code Duplication
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
694 1
			$board_info['error'] = 'access';
695 1 View Code Duplication
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
696 1
			$board_info['error'] = 'access';
697
698
		// Build up the linktree.
699 1
		$context['linktree'] = array_merge(
700 1
			$context['linktree'],
701
			array(array(
702 1
				'url' => $scripturl . $modSettings['default_forum_action'] . '#c' . $board_info['cat']['id'],
703 1
				'name' => $board_info['cat']['name']
704 1
			)),
705 1
			array_reverse($board_info['parent_boards']),
706
			array(array(
707 1
				'url' => $scripturl . '?board=' . $board . '.0',
708 1
				'name' => $board_info['name']
709 1
			))
710 1
		);
711 1
	}
712
713
	// Set the template contextual information.
714 1
	$context['user']['is_mod'] = &$user_info['is_mod'];
715 1
	$context['user']['is_moderator'] = &$user_info['is_moderator'];
716 1
	$context['current_topic'] = $topic;
717 1
	$context['current_board'] = $board;
718
719
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
720 1
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_moderator']))
721 1
	{
722
		// The permissions and theme need loading, just to make sure everything goes smoothly.
723
		loadPermissions();
724
		loadTheme();
725
726
		$_GET['board'] = '';
727
		$_GET['topic'] = '';
728
729
		// The linktree should not give the game away mate!
730
		$context['linktree'] = array(
731
			array(
732
				'url' => $scripturl,
733
				'name' => $context['forum_name_html_safe']
734
			)
735
		);
736
737
		// If it's a prefetching agent, stop it
738
		stop_prefetching();
739
740
		// If we're requesting an attachment.
741
		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...
742
		{
743
			ob_end_clean();
744
			header('HTTP/1.1 403 Forbidden');
745
			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...
746
		}
747
		elseif ($user_info['is_guest'])
748
		{
749
			loadLanguage('Errors');
750
			is_not_guest($txt['topic_gone']);
751
		}
752
		else
753
			throw new Elk_Exception('topic_gone', false);
754
	}
755
756 1
	if ($user_info['is_mod'])
757 1
		$user_info['groups'][] = 3;
758 1
}
759
760
/**
761
 * Load this user's permissions.
762
 *
763
 * What it does:
764
 *
765
 * - If the user is an admin, validate that they have not been banned.
766
 * - Attempt to load permissions from cache for cache level > 2
767
 * - See if the user is possibly a robot and apply added permissions as needed
768
 * - Load permissions from the general permissions table.
769
 * - If inside a board load the necessary board permissions.
770
 * - If the user is not a guest, identify what other boards they have access to.
771
 */
772
function loadPermissions()
773
{
774
	global $user_info, $board, $board_info, $modSettings;
775
776
	$db = database();
777
778
	if ($user_info['is_admin'])
779
	{
780
		banPermissions();
781
		return;
782
	}
783
784
	$removals = array();
785
786
	$cache = Cache::instance();
787
788
	if ($cache->isEnabled())
789
	{
790
		$cache_groups = $user_info['groups'];
791
		asort($cache_groups);
792
		$cache_groups = implode(',', $cache_groups);
793
794
		// If it's a spider then cache it different.
795
		if ($user_info['possibly_robot'])
796
			$cache_groups .= '-spider';
797
798
		$temp = array();
799
		if ($cache->levelHigherThan(1) && !empty($board) && $cache->getVar($temp, 'permissions:' . $cache_groups . ':' . $board, 240) && time() - 240 > $modSettings['settings_updated'])
800
		{
801
			list ($user_info['permissions']) = $temp;
802
			banPermissions();
803
804
			return;
805
		}
806
		elseif ($cache->getVar($temp, 'permissions:' . $cache_groups, 240) && time() - 240 > $modSettings['settings_updated'])
807
		{
808
			if (is_array($temp))
809
			{
810
				list ($user_info['permissions'], $removals) = $temp;
811
			}
812
		}
813
	}
814
815
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
816
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
817
818
	if (empty($user_info['permissions']))
819
	{
820
		// Get the general permissions.
821
		$request = $db->query('', '
822
			SELECT 
823
				permission, add_deny
824
			FROM {db_prefix}permissions
825
			WHERE id_group IN ({array_int:member_groups})
826
				' . $spider_restrict,
827
			array(
828
				'member_groups' => $user_info['groups'],
829
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
830
			)
831
		);
832 View Code Duplication
		while ($row = $db->fetch_assoc($request))
833
		{
834
			if (empty($row['add_deny']))
835
				$removals[] = $row['permission'];
836
			else
837
				$user_info['permissions'][] = $row['permission'];
838
		}
839
		$db->free_result($request);
840
841
		if (isset($cache_groups))
842
			$cache->put('permissions:' . $cache_groups, array($user_info['permissions'], !empty($removals) ? $removals : array()), 2);
843
	}
844
845
	// Get the board permissions.
846
	if (!empty($board))
847
	{
848
		// Make sure the board (if any) has been loaded by loadBoard().
849
		if (!isset($board_info['profile']))
850
			throw new Elk_Exception('no_board');
851
852
		$request = $db->query('', '
853
			SELECT 
854
				permission, add_deny
855
			FROM {db_prefix}board_permissions
856
			WHERE (id_group IN ({array_int:member_groups})
857
				' . $spider_restrict . ')
858
				AND id_profile = {int:id_profile}',
859
			array(
860
				'member_groups' => $user_info['groups'],
861
				'id_profile' => $board_info['profile'],
862
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
863
			)
864
		);
865 View Code Duplication
		while ($row = $db->fetch_assoc($request))
866
		{
867
			if (empty($row['add_deny']))
868
				$removals[] = $row['permission'];
869
			else
870
				$user_info['permissions'][] = $row['permission'];
871
		}
872
		$db->free_result($request);
873
	}
874
875
	// Remove all the permissions they shouldn't have ;).
876 View Code Duplication
	if (!empty($modSettings['permission_enable_deny']))
877
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
878
879
	if (isset($cache_groups) && !empty($board) && $cache->levelHigherThan(1))
880
		$cache->put('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
881
882
	// Banned?  Watch, don't touch..
883
	banPermissions();
884
885
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
886
	if (!$user_info['is_guest'])
887
	{
888
		$user_info['is_moderator'] = $user_info['is_mod'] || allowedTo('moderate_board');
889
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
890
		{
891
			require_once(SUBSDIR . '/Auth.subs.php');
892
			rebuildModCache();
893
		}
894
		else
895
			$user_info['mod_cache'] = $_SESSION['mc'];
896
	}
897
}
898
899
/**
900
 * Loads an array of users' data by ID or member_name.
901
 *
902
 * @event integrate_load_member_data allows to add to the columns & tables for $user_profile
903
 * array population
904
 * @event integrate_add_member_data called after data is loaded, allows integration
905
 * to directly add to the user_profile array
906
 *
907
 * @param int[]|int|string[]|string $users An array of users by id or name
908
 * @param bool $is_name = false $users is by name or by id
909
 * @param string $set = 'normal' What kind of data to load (normal, profile, minimal)
910
 *
911
 * @return array|bool The ids of the members loaded or false
912
 */
913
function loadMemberData($users, $is_name = false, $set = 'normal')
914
{
915 2
	global $user_profile, $modSettings, $board_info, $context, $user_info;
916
917 2
	$db = database();
918 2
	$cache = Cache::instance();
919
920
	// Can't just look for no users :P.
921 2
	if (empty($users))
922 2
		return false;
923
924
	// Pass the set value
925 2
	$context['loadMemberContext_set'] = $set;
926
927
	// Make sure it's an array.
928 2
	$users = !is_array($users) ? array($users) : array_unique($users);
929 2
	$loaded_ids = array();
930
931 2
	if (!$is_name && $cache->isEnabled() && $cache->levelHigherThan(2))
932 2
	{
933
		$users = array_values($users);
934
		for ($i = 0, $n = count($users); $i < $n; $i++)
935
		{
936
			$data = $cache->get('member_data-' . $set . '-' . $users[$i], 240);
937
			if ($cache->isMiss())
938
				continue;
939
940
			$loaded_ids[] = $data['id_member'];
941
			$user_profile[$data['id_member']] = $data;
942
			unset($users[$i]);
943
		}
944
	}
945
946
	// Used by default
947
	$select_columns = '
948
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
949
			mem.signature, mem.avatar, mem.id_member, mem.member_name,
950
			mem.real_name, mem.email_address, mem.hide_email, mem.date_registered, mem.website_title, mem.website_url,
951
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.likes_given, mem.likes_received,
952
			mem.karma_good, mem.id_post_group, mem.karma_bad, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online,
953
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
954
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
955 2
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
956 2
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
957
	$select_tables = '
958
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
959
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
960
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
961 2
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
962
963
	// We add or replace according to the set
964
	switch ($set)
965
	{
966 2
		case 'normal':
967
			$select_columns .= ', mem.buddy_list';
968
			break;
969 2
		case 'profile':
970
			$select_columns .= ', mem.openid_uri, mem.id_theme, mem.pm_ignore_list, mem.pm_email_notify, mem.receive_from,
971
			mem.time_format, mem.secret_question, mem.additional_groups, mem.smiley_set,
972
			mem.total_time_logged_in, mem.notify_announcements, mem.notify_regularity, mem.notify_send_body,
973 2
			mem.notify_types, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.otp_secret, mem.enable_otp';
974 2
			break;
975
		case 'minimal':
976
			$select_columns = '
977
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.hide_email, mem.date_registered,
978
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
979
			$select_tables = '';
980
			break;
981
		default:
982
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
983
	}
984
985
	// Allow addons to easily add to the selected member data
986 2
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, $set));
987
988 2
	if (!empty($users))
989 2
	{
990
		// Load the member's data.
991 2
		$request = $db->query('', '
992 2
			SELECT' . $select_columns . '
993 2
			FROM {db_prefix}members AS mem' . $select_tables . '
994 2
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . (count($users) == 1 ? ' = {' . ($is_name ? 'string' : 'int') . ':users}' : ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})'),
995
			array(
996 2
				'blank_string' => '',
997 2
				'users' => count($users) == 1 ? current($users) : $users,
998
			)
999 2
		);
1000 2
		$new_loaded_ids = array();
1001 2
		while ($row = $db->fetch_assoc($request))
1002
		{
1003 2
			$new_loaded_ids[] = $row['id_member'];
1004 2
			$loaded_ids[] = $row['id_member'];
1005 2
			$row['options'] = array();
1006 2
			$user_profile[$row['id_member']] = $row;
1007 2
		}
1008 2
		$db->free_result($request);
1009 2
	}
1010
1011
	// Custom profile fields as well
1012 2
	if (!empty($new_loaded_ids) && !empty($user_info['id']) && $set !== 'minimal' && (in_array('cp', $context['admin_features'])))
1013 2
	{
1014 1
		$request = $db->query('', '
1015
			SELECT cfd.id_member, cfd.variable, cfd.value, cf.field_options, cf.field_type
1016
			FROM {db_prefix}custom_fields_data AS cfd
1017
			JOIN {db_prefix}custom_fields AS cf ON (cf.col_name = cfd.variable)
1018 1
			WHERE id_member' . (count($new_loaded_ids) == 1 ? ' = {int:loaded_ids}' : ' IN ({array_int:loaded_ids})'),
1019
			array(
1020 1
				'loaded_ids' => count($new_loaded_ids) == 1 ? $new_loaded_ids[0] : $new_loaded_ids,
1021
			)
1022 1
		);
1023 1
		while ($row = $db->fetch_assoc($request))
1024
		{
1025
			if (!empty($row['field_options']))
1026
			{
1027
				$field_options = explode(',', $row['field_options']);
1028
				$key = (int) array_search($row['value'], $field_options);
1029
			}
1030
			else
1031
			{
1032
				$key = 0;
1033
			}
1034
1035
			$user_profile[$row['id_member']]['options'][$row['variable'] . '_key'] = $row['variable'] . '_' . $key;
1036
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1037
		}
1038 1
		$db->free_result($request);
1039 1
	}
1040
1041
	// Anything else integration may want to add to the user_profile array
1042 2
	if (!empty($new_loaded_ids))
1043 2
		call_integration_hook('integrate_add_member_data', array($new_loaded_ids, $set));
1044
1045 2
	if (!empty($new_loaded_ids) && $cache->levelHigherThan(2))
1046 2
	{
1047
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1048
			$cache->put('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1049
	}
1050
1051
	// Are we loading any moderators?  If so, fix their group data...
1052 2
	if (!empty($loaded_ids) && !empty($board_info['moderators']) && $set === 'normal' && count($temp_mods = array_intersect($loaded_ids, array_keys($board_info['moderators']))) !== 0)
1053 2
	{
1054
		$group_info = array();
1055
		if (!$cache->getVar($group_info, 'moderator_group_info', 480))
1056
		{
1057
			require_once(SUBSDIR . '/Membergroups.subs.php');
1058
			$group_info = membergroupById(3, true);
1059
1060
			$cache->put('moderator_group_info', $group_info, 480);
1061
		}
1062
1063
		foreach ($temp_mods as $id)
1064
		{
1065
			// By popular demand, don't show admins or global moderators as moderators.
1066
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1067
				$user_profile[$id]['member_group'] = $group_info['group_name'];
1068
1069
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1070
			if (!empty($group_info['icons']))
1071
				$user_profile[$id]['icons'] = $group_info['icons'];
1072
			if (!empty($group_info['online_color']))
1073
				$user_profile[$id]['member_group_color'] = $group_info['online_color'];
1074
		}
1075
	}
1076
1077 2
	return empty($loaded_ids) ? false : $loaded_ids;
1078
}
1079
1080
/**
1081
 * Loads the user's basic values... meant for template/theme usage.
1082
 *
1083
 * What it does:
1084
 *
1085
 * - Always loads the minimal values of username, name, id, href, link, email, show_email, registered, registered_timestamp
1086
 * - if $context['loadMemberContext_set'] is not minimal it will load in full a full set of user information
1087
 * - prepares signature for display (censoring if enabled)
1088
 * - loads in the members custom fields if any
1089
 * - prepares the users buddy list, including reverse buddy flags
1090
 *
1091
 * @event integrate_member_context allows to manipulate $memberContext[user]
1092
 * @param int $user
1093
 * @param bool $display_custom_fields = false
1094
 *
1095
 * @return boolean
1096
 */
1097
function loadMemberContext($user, $display_custom_fields = false)
1098
{
1099 1
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1100 1
	global $context, $modSettings, $settings;
1101 1
	static $dataLoaded = array();
1102
1103
	// If this person's data is already loaded, skip it.
1104 1
	if (isset($dataLoaded[$user]))
1105 1
		return true;
1106
1107
	// We can't load guests or members not loaded by loadMemberData()!
1108 1
	if ($user == 0)
1109 1
		return false;
1110
1111 1
	if (!isset($user_profile[$user]))
1112 1
	{
1113
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1114
		return false;
1115
	}
1116
1117 1
	$parsers = \BBC\ParserWrapper::instance();
1118
1119
	// Well, it's loaded now anyhow.
1120 1
	$dataLoaded[$user] = true;
1121 1
	$profile = $user_profile[$user];
1122
1123
	// Censor everything.
1124 1
	$profile['signature'] = censor($profile['signature']);
1125
1126
	// TODO: We should look into a censoring toggle for custom fields
1127
1128
	// Set things up to be used before hand.
1129 1
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br />', ''), $profile['signature']);
1130 1
	$profile['signature'] = $parsers->parseSignature($profile['signature'], true);
1131 1
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1132 1
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1133
1134
	// Setup the buddy status here (One whole in_array call saved :P)
1135 1
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1136 1
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1137
1138
	// These minimal values are always loaded
1139 1
	$memberContext[$user] = array(
1140 1
		'username' => $profile['member_name'],
1141 1
		'name' => $profile['real_name'],
1142 1
		'id' => $profile['id_member'],
1143 1
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1144 1
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . trim($profile['real_name']) . '">' . $profile['real_name'] . '</a>',
1145 1
		'email' => $profile['email_address'],
1146 1
		'show_email' => showEmailAddress(!empty($profile['hide_email']), $profile['id_member']),
1147 1
		'registered_raw' => empty($profile['date_registered']) ? 0 : $profile['date_registered'],
1148 1
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : standardTime($profile['date_registered']),
1149 1
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1150
	);
1151
1152
	// If the set isn't minimal then load the monstrous array.
1153 1
	if ($context['loadMemberContext_set'] !== 'minimal')
1154 1
	{
1155
		$memberContext[$user] += array(
1156 1
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1157 1
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1158 1
			'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>',
1159 1
			'is_buddy' => $profile['buddy'],
1160 1
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1161 1
			'buddies' => $buddy_list,
1162 1
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1163
			'website' => array(
1164 1
				'title' => $profile['website_title'],
1165 1
				'url' => $profile['website_url'],
1166 1
			),
1167 1
			'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']),
1168 1
			'signature' => $profile['signature'],
1169 1
			'real_posts' => $profile['posts'],
1170 1
			'posts' => comma_format($profile['posts']),
1171 1
			'avatar' => determineAvatar($profile),
1172 1
			'last_login' => empty($profile['last_login']) ? $txt['never'] : standardTime($profile['last_login']),
1173 1
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(false, $profile['last_login']),
1174
			'karma' => array(
1175 1
				'good' => $profile['karma_good'],
1176 1
				'bad' => $profile['karma_bad'],
1177 1
				'allow' => !$user_info['is_guest'] && !empty($modSettings['karmaMode']) && $user_info['id'] != $user && allowedTo('karma_edit') &&
1178 1
				($user_info['posts'] >= $modSettings['karmaMinPosts'] || $user_info['is_admin']),
1179 1
			),
1180
			'likes' => array(
1181 1
				'given' => $profile['likes_given'],
1182 1
				'received' => $profile['likes_received']
1183 1
			),
1184 1
			'ip' => htmlspecialchars($profile['member_ip'], ENT_COMPAT, 'UTF-8'),
1185 1
			'ip2' => htmlspecialchars($profile['member_ip2'], ENT_COMPAT, 'UTF-8'),
1186
			'online' => array(
1187 1
				'is_online' => $profile['is_online'],
1188 1
				'text' => Util::htmlspecialchars($txt[$profile['is_online'] ? 'online' : 'offline']),
1189 1
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], Util::htmlspecialchars($profile['real_name'])),
1190 1
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1191 1
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1192 1
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1193 1
			),
1194 1
			'language' => Util::ucwords(strtr($profile['lngfile'], array('_' => ' '))),
1195 1
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1196 1
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1197 1
			'options' => $profile['options'],
1198 1
			'is_guest' => false,
1199 1
			'group' => $profile['member_group'],
1200 1
			'group_color' => $profile['member_group_color'],
1201 1
			'group_id' => $profile['id_group'],
1202 1
			'post_group' => $profile['post_group'],
1203 1
			'post_group_color' => $profile['post_group_color'],
1204 1
			'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]),
1205 1
			'warning' => $profile['warning'],
1206 1
			'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' : (''))),
1207 1
			'local_time' => standardTime(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1208 1
			'custom_fields' => array(),
1209
		);
1210 1
	}
1211
1212
	// Are we also loading the members custom fields into context?
1213 1
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1214 1
	{
1215
		if (!isset($context['display_fields']))
1216
			$context['display_fields'] = Util::unserialize($modSettings['displayFields']);
1217
1218
		foreach ($context['display_fields'] as $custom)
1219
		{
1220
			if (!isset($custom['title']) || trim($custom['title']) == '' || empty($profile['options'][$custom['colname']]))
1221
				continue;
1222
1223
			$value = $profile['options'][$custom['colname']];
1224
1225
			// BBC?
1226
			if ($custom['bbc'])
1227
				$value = $parsers->parseCustomFields($value);
1228
			// ... or checkbox?
1229
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1230
				$value = $value ? $txt['yes'] : $txt['no'];
1231
1232
			// Enclosing the user input within some other text?
1233
			if (!empty($custom['enclose']))
1234
			{
1235
				$replacements = array(
1236
					'{SCRIPTURL}' => $scripturl,
1237
					'{IMAGES_URL}' => $settings['images_url'],
1238
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1239
					'{INPUT}' => $value,
1240
				);
1241
1242
				if (in_array($custom['type'], array('radio', 'select')))
1243
				{
1244
					$replacements['{KEY}'] = $profile['options'][$custom['colname'] . '_key'];
1245
				}
1246
				$value = strtr($custom['enclose'], $replacements);
1247
			}
1248
1249
			$memberContext[$user]['custom_fields'][] = array(
1250
				'title' => $custom['title'],
1251
				'colname' => $custom['colname'],
1252
				'value' => $value,
1253
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1254
			);
1255
		}
1256
	}
1257
1258 1
	call_integration_hook('integrate_member_context', array($user, $display_custom_fields));
1259 1
	return true;
1260
}
1261
1262
/**
1263
 * Loads information about what browser the user is viewing with and places it in $context
1264
 *
1265
 * @uses Browser_Detector class from BrowserDetect.class.php
1266
 */
1267
function detectBrowser()
1268
{
1269
	// Load the current user's browser of choice
1270 7
	$detector = new Browser_Detector;
1271 7
	$detector->detectBrowser();
1272 7
}
1273
1274
/**
1275
 * Get the id of a theme
1276
 *
1277
 * @param int $id_theme
1278
 * @return int
1279
 */
1280
function getThemeId($id_theme = 0)
1281
{
1282 7
	global $modSettings, $user_info, $board_info, $ssi_theme;
1283
1284
	// The theme was specified by parameter.
1285 7
	if (!empty($id_theme))
1286 7
		$id_theme = (int) $id_theme;
1287
	// The theme was specified by REQUEST.
1288 7 View Code Duplication
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1289
	{
1290
		$id_theme = (int) $_REQUEST['theme'];
1291
		$_SESSION['id_theme'] = $id_theme;
1292
	}
1293
	// The theme was specified by REQUEST... previously.
1294 7 View Code Duplication
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1295
		$id_theme = (int) $_SESSION['id_theme'];
1296
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1297 7 View Code Duplication
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1298
		$id_theme = $user_info['theme'];
1299
	// The theme was specified by the board.
1300 7
	elseif (!empty($board_info['theme']))
1301
		$id_theme = $board_info['theme'];
1302
	// The theme is the forum's default.
1303
	else
1304 7
		$id_theme = $modSettings['theme_guests'];
1305
1306 7
	call_integration_hook('integrate_customize_theme_id', array(&$id_theme));
1307
1308
	// Verify the id_theme... no foul play.
1309
	// Always allow the board specific theme, if they are overriding.
1310 7
	if (!empty($board_info['theme']) && $board_info['override_theme'])
1311 7
		$id_theme = $board_info['theme'];
1312
	// If they have specified a particular theme to use with SSI allow it to be used.
1313 7
	elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1314
		$id_theme = (int) $id_theme;
1315 7 View Code Duplication
	elseif (!empty($modSettings['knownThemes']) && !allowedTo('admin_forum'))
1316
	{
1317 4
		$themes = explode(',', $modSettings['knownThemes']);
1318 4
		if (!in_array($id_theme, $themes))
1319 4
			$id_theme = $modSettings['theme_guests'];
1320
		else
1321 4
			$id_theme = (int) $id_theme;
1322 4
	}
1323
	else
1324 3
		$id_theme = (int) $id_theme;
1325
1326 7
	return $id_theme;
1327
}
1328
1329
/**
1330
 * Load in the theme variables for a given theme / member combination
1331
 *
1332
 * @param int $id_theme
1333
 * @param int $member
1334
 *
1335
 * @return array
1336
 */
1337
function getThemeData($id_theme, $member)
1338
{
1339 7
	global $modSettings, $boardurl;
1340
1341 7
	$cache = Cache::instance();
1342
1343
	// Do we already have this members theme data and specific options loaded (for aggressive cache settings)
1344 7
	$temp = array();
1345 7
	if ($cache->levelHigherThan(1) && $cache->getVar($temp, 'theme_settings-' . $id_theme . ':' . $member, 60) && time() - 60 > $modSettings['settings_updated'])
1346 7
	{
1347
		$themeData = $temp;
1348
		$flag = true;
1349
	}
1350
	// Or do we just have the system wide theme settings cached
1351 7
	elseif ($cache->getVar($temp, 'theme_settings-' . $id_theme, 90) && time() - 60 > $modSettings['settings_updated'])
1352
		$themeData = $temp + array($member => array());
1353
	// Nothing at all then
1354
	else
1355 7
		$themeData = array(-1 => array(), 0 => array(), $member => array());
1356
1357 7
	if (empty($flag))
1358 7
	{
1359 7
		$db = database();
1360
1361
		// Load variables from the current or default theme, global or this user's.
1362 7
		$result = $db->query('', '
1363
			SELECT variable, value, id_member, id_theme
1364
			FROM {db_prefix}themes
1365 7
			WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1366 7
				AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1367
			array(
1368 7
				'id_theme' => $id_theme,
1369 7
				'id_member' => $member,
1370
			)
1371 7
		);
1372
1373 7
		$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');
1374
1375
		// Pick between $settings and $options depending on whose data it is.
1376 7
		while ($row = $db->fetch_assoc($result))
1377
		{
1378
			// There are just things we shouldn't be able to change as members.
1379 7
			if ($row['id_member'] != 0 && in_array($row['variable'], $immutable_theme_data))
1380 7
				continue;
1381
1382
			// If this is the theme_dir of the default theme, store it.
1383 7
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1384 7
				$themeData[0]['default_' . $row['variable']] = $row['value'];
1385
1386
			// If this isn't set yet, is a theme option, or is not the default theme..
1387 7
			if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1388 7
				$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1389 7
		}
1390 7
		$db->free_result($result);
1391
1392 7
		if (file_exists($themeData[0]['default_theme_dir'] . '/cache') && is_writable($themeData[0]['default_theme_dir'] . '/cache'))
1393 7
		{
1394 7
			$themeData[0]['default_theme_cache_dir'] = $themeData[0]['default_theme_dir'] . '/cache';
1395 7
			$themeData[0]['default_theme_cache_url'] = $themeData[0]['default_theme_url'] . '/cache';
1396 7
		}
1397
		else
1398
		{
1399
			$themeData[0]['default_theme_cache_dir'] = CACHEDIR;
1400
			$themeData[0]['default_theme_cache_url'] = $boardurl . '/cache';
1401
		}
1402
1403
		// Set the defaults if the user has not chosen on their own
1404 7
		if (!empty($themeData[-1]))
1405 7
		{
1406 7
			foreach ($themeData[-1] as $k => $v)
1407
			{
1408 7
				if (!isset($themeData[$member][$k]))
1409 7
					$themeData[$member][$k] = $v;
1410 7
			}
1411 7
		}
1412
1413
		// If being aggressive we save the site wide and member theme settings
1414 7
		if ($cache->levelHigherThan(1))
1415 7
			$cache->put('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1416
		// Only if we didn't already load that part of the cache...
1417 7
		elseif (!isset($temp))
1418 7
			$cache->put('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1419 7
	}
1420
1421 7
	return $themeData;
1422
}
1423
1424
/**
1425
 * Initialize a theme for use
1426
 *
1427
 * @param int $id_theme
1428
 */
1429
function initTheme($id_theme = 0)
1430
{
1431 7
	global $user_info, $settings, $options, $context;
1432
1433
	// Validate / fetch the themes id
1434 7
	$id_theme = getThemeId($id_theme);
1435
1436
	// Need to know who we are loading the theme for
1437 7
	$member = empty($user_info['id']) ? -1 : $user_info['id'];
1438
1439
	// Load in the theme variables for them
1440 7
	$themeData = getThemeData($id_theme, $member);
1441 7
	$settings = $themeData[0];
1442 7
	$options = $themeData[$member];
1443
1444 7
	$settings['theme_id'] = $id_theme;
1445 7
	$settings['actual_theme_url'] = $settings['theme_url'];
1446 7
	$settings['actual_images_url'] = $settings['images_url'];
1447 7
	$settings['actual_theme_dir'] = $settings['theme_dir'];
1448
1449
	// Reload the templates
1450 7
	Templates::instance()->reloadDirectories($settings);
1451
1452
	// Setup the default theme file. In the future, this won't exist and themes will just have to extend it if they want
1453 7
	require_once($settings['default_theme_dir'] . '/Theme.php');
1454 7
	$default_theme_instance = new \Themes\DefaultTheme\Theme(1);
1455
1456
	// Check if there is a Theme file
1457 7
	if ($id_theme != 1 && !empty($settings['theme_dir']) && file_exists($settings['theme_dir'] . '/Theme.php'))
1458 7
	{
1459
		require_once($settings['theme_dir'] . '/Theme.php');
1460
1461
		$class = '\\Themes\\' . basename(ucfirst($settings['theme_dir'])) . 'Theme\\Theme';
1462
1463
		$theme = new $class($id_theme);
1464
1465
		$context['theme_instance'] = $theme;
1466
	}
1467
	else
1468
	{
1469 7
		$context['theme_instance'] = $default_theme_instance;
1470
	}
1471 7
}
1472
1473
/**
1474
 * Load a theme, by ID.
1475
 *
1476
 * What it does:
1477
 *
1478
 * - identify the theme to be loaded.
1479
 * - validate that the theme is valid and that the user has permission to use it
1480
 * - load the users theme settings and site settings into $options.
1481
 * - prepares the list of folders to search for template loading.
1482
 * - identify what smiley set to use.
1483
 * - sets up $context['user']
1484
 * - detects the users browser and sets a mobile friendly environment if needed
1485
 * - loads default JS variables for use in every theme
1486
 * - loads default JS scripts for use in every theme
1487
 *
1488
 * @event integrate_init_theme used to call initialization theme integration functions and
1489
 * change / update $settings
1490
 * @event integrate_theme_include allows to include files at this point
1491
 * @event integrate_load_theme calls functions after theme is loaded
1492
 * @param int $id_theme = 0
1493
 * @param bool $initialize = true
1494
 */
1495
function loadTheme($id_theme = 0, $initialize = true)
1496
{
1497 7
	global $user_info, $user_settings;
1498 7
	global $txt, $scripturl, $mbname, $modSettings;
1499 7
	global $context, $settings, $options;
1500
1501 7
	initTheme($id_theme);
1502
1503 7
	if (!$initialize)
1504 7
		return;
1505
1506 7
	loadThemeUrls();
1507
1508 7
	loadUserContext();
1509
1510
	// Set up some additional interface preference context
1511 7 View Code Duplication
	if (!empty($options['admin_preferences']))
1512 7
	{
1513
		$context['admin_preferences'] = serializeToJson($options['admin_preferences'], function ($array_form) {
1514
			global $context;
1515
1516
			$context['admin_preferences'] = $array_form;
1517
			require_once(SUBSDIR . '/Admin.subs.php');
1518
			updateAdminPreferences();
1519
		});
1520
	}
1521
	else
1522
	{
1523 7
		$context['admin_preferences'] = array();
1524
	}
1525
1526 7
	if (!$user_info['is_guest'])
1527 7
	{
1528 3 View Code Duplication
		if (!empty($options['minmax_preferences']))
1529 3
		{
1530
			$context['minmax_preferences'] = serializeToJson($options['minmax_preferences'], function ($array_form) {
1531
				global $settings, $user_info;
1532
1533
				// Update the option.
1534
				require_once(SUBSDIR . '/Themes.subs.php');
1535
				updateThemeOptions(array($settings['theme_id'], $user_info['id'], 'minmax_preferences', json_encode($array_form)));
1536
			});
1537
		}
1538
		else
1539
		{
1540 3
			$context['minmax_preferences'] = array();
1541
		}
1542 3
	}
1543
	// Guest may have collapsed the header, check the cookie to prevent collapse jumping
1544 4
	elseif ($user_info['is_guest'] && isset($_COOKIE['upshrink']))
1545
		$context['minmax_preferences'] = array('upshrink' => $_COOKIE['upshrink']);
1546
1547
	// Load the basic layers
1548 7
	theme()->loadDefaultLayers();
1549
1550
	// @todo when are these set before loadTheme(0, true)?
1551 7
	loadThemeContext();
1552
1553
	// @todo These really don't belong in loadTheme() since they are more general than the theme.
1554 7
	$context['session_var'] = $_SESSION['session_var'];
1555 7
	$context['session_id'] = $_SESSION['session_value'];
1556 7
	$context['forum_name'] = $mbname;
1557 7
	$context['forum_name_html_safe'] = $context['forum_name'];
1558 7
	$context['current_action'] = isset($_REQUEST['action']) ? $_REQUEST['action'] : null;
1559 7
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
1560
1561
	// Set some permission related settings.
1562 7
	if ($user_info['is_guest'] && !empty($modSettings['enableVBStyleLogin']))
1563 7
	{
1564 4
		$context['show_login_bar'] = true;
1565 4
		$context['theme_header_callbacks'][] = 'login_bar';
1566 4
		loadJavascriptFile('sha256.js', array('defer' => true));
1567 4
	}
1568
1569
	// This determines the server... not used in many places, except for login fixing.
1570 7
	detectServer();
1571
1572
	// Detect the browser. This is separated out because it's also used in attachment downloads
1573 7
	detectBrowser();
1574
1575
	// Set the top level linktree up.
1576 7
	array_unshift($context['linktree'], array(
1577 7
		'url' => $scripturl,
1578 7
		'name' => $context['forum_name']
1579 7
	));
1580
1581
	// Just some mobile-friendly settings
1582 7
	if ($context['browser_body_id'] == 'mobile')
1583 7
	{
1584
		// Disable the preview text.
1585
		$modSettings['message_index_preview'] = 0;
1586
		// Force the usage of click menu instead of a hover menu.
1587
		$options['use_click_menu'] = 1;
1588
		// No space left for a sidebar
1589
		$options['use_sidebar_menu'] = false;
1590
		// Disable the search dropdown.
1591
		$modSettings['search_dropdown'] = false;
1592
	}
1593
1594 7
	if (!isset($txt))
1595 7
		$txt = array();
1596
1597
	// Defaults in case of odd things
1598 7
	$settings['avatars_on_indexes'] = 0;
1599
1600
	// Initialize the theme.
1601 7
	if (function_exists('template_init'))
1602 7
		$settings = array_merge($settings, template_init());
1603
1604
	// Call initialization theme integration functions.
1605 7
	call_integration_hook('integrate_init_theme', array($id_theme, &$settings));
1606
1607
	// Guests may still need a name.
1608 7 View Code Duplication
	if ($context['user']['is_guest'] && empty($context['user']['name']))
1609 7
		$context['user']['name'] = $txt['guest_title'];
1610
1611
	// Any theme-related strings that need to be loaded?
1612 7
	if (!empty($settings['require_theme_strings']))
1613 7
		loadLanguage('ThemeStrings', '', false);
1614
1615 7
	theme()->loadSupportCSS();
1616
1617
	// We allow theme variants, because we're cool.
1618 7
	if (!empty($settings['theme_variants']))
1619 7
	{
1620 7
		theme()->loadThemeVariant();
1621 7
	}
1622
1623
	// A bit lonely maybe, though I think it should be set up *after* the theme variants detection
1624 7
	$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']);
1625 7
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1626
1627
	// Allow overriding the board wide time/number formats.
1628 7
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
1629 7
		$user_info['time_format'] = $txt['time_format'];
1630
1631 7 View Code Duplication
	if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'always')
1632 7
	{
1633
		$settings['theme_url'] = $settings['default_theme_url'];
1634
		$settings['images_url'] = $settings['default_images_url'];
1635
		$settings['theme_dir'] = $settings['default_theme_dir'];
1636
	}
1637
1638
	// Make a special URL for the language.
1639 7
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
1640
1641
	// RTL languages require an additional stylesheet.
1642 7
	if ($context['right_to_left'])
1643 7
		loadCSSFile('rtl.css');
1644
1645 7
	if (!empty($context['theme_variant']) && $context['right_to_left'])
1646 7
		loadCSSFile($context['theme_variant'] . '/rtl' . $context['theme_variant'] . '.css');
1647
1648
	// This allows us to change the way things look for the admin.
1649 7
	$context['admin_features'] = explode(',', isset($modSettings['admin_features']) ? $modSettings['admin_features'] : 'cd,cp,k,w,rg,ml,pm');
1650
1651 7
	if (!empty($modSettings['xmlnews_enable']) && (!empty($modSettings['allow_guestAccess']) || $context['user']['is_logged']))
1652 7
		$context['newsfeed_urls'] = array(
1653 7
			'rss' => $scripturl . '?action=.xml;type=rss2;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5),
1654 7
			'atom' => $scripturl . '?action=.xml;type=atom;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5)
1655 7
		);
1656
1657 7
	if (!empty($_SESSION['agreement_accepted']))
1658 7
	{
1659
		$_SESSION['agreement_accepted'] = null;
1660
		$context['accepted_agreement'] = array(
1661
			'errors' => array(
1662
				'accepted_agreement' => $txt['agreement_accepted']
1663
			)
1664
		);
1665
	}
1666
1667 7
	if (!empty($_SESSION['privacypolicy_accepted']))
1668 7
	{
1669
		$_SESSION['privacypolicy_accepted'] = null;
1670
		$context['accepted_agreement'] = array(
1671
			'errors' => array(
1672
				'accepted_privacy_policy' => $txt['privacypolicy_accepted']
1673
			)
1674
		);
1675
	}
1676
1677 7
	theme()->loadThemeJavascript();
1678
1679 7
	Hooks::instance()->newPath(array('$themedir' => $settings['theme_dir']));
1680
1681
	// Any files to include at this point?
1682 7
	call_integration_include_hook('integrate_theme_include');
1683
1684
	// Call load theme integration functions.
1685 7
	call_integration_hook('integrate_load_theme');
1686
1687
	// We are ready to go.
1688 7
	$context['theme_loaded'] = true;
1689 7
}
1690
1691
/**
1692
 * Detects url and checks against expected boardurl
1693
 *
1694
 * Attempts to correct improper URL's
1695
 */
1696
function loadThemeUrls()
1697
{
1698 7
	global $scripturl, $boardurl, $modSettings;
1699
1700
	// Check to see if they're accessing it from the wrong place.
1701 7
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1702 7
	{
1703
		$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://';
1704
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1705
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1706
		if ($temp != '/')
1707
			$detected_url .= $temp;
1708
	}
1709
1710 7
	if (isset($detected_url) && $detected_url != $boardurl)
1711 7
	{
1712
		// Try #1 - check if it's in a list of alias addresses.
1713
		if (!empty($modSettings['forum_alias_urls']))
1714
		{
1715
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1716
			foreach ($aliases as $alias)
1717
			{
1718
				// Rip off all the boring parts, spaces, etc.
1719
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1720
					$do_fix = true;
1721
			}
1722
		}
1723
1724
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1725
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && ELK != 'SSI')
1726
		{
1727
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1728
			if (empty($_GET))
1729
				redirectexit('wwwRedirect');
1730
			else
1731
			{
1732
				if (key($_GET) !== 'wwwRedirect')
1733
					redirectexit('wwwRedirect;' . key($_GET) . '=' . current($_GET));
1734
			}
1735
		}
1736
1737
		// #3 is just a check for SSL...
1738
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1739
			$do_fix = true;
1740
1741
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1742
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
1743
		{
1744
			fixThemeUrls($detected_url);
1745
		}
1746
	}
1747 7
}
1748
1749
/**
1750
 * Loads various theme related settings into context and sets system wide theme defaults
1751
 */
1752
function loadThemeContext()
1753
{
1754 7
	global $context, $settings, $modSettings, $txt;
1755
1756
	// Some basic information...
1757
	$init = array(
1758 7
		'html_headers' => '',
1759 7
		'links' => array(),
1760 7
		'css_files' => array(),
1761 7
		'javascript_files' => array(),
1762 7
		'css_rules' => array(),
1763 7
		'javascript_inline' => array('standard' => array(), 'defer' => array()),
1764 7
		'javascript_vars' => array(),
1765 7
	);
1766 7
	foreach ($init as $area => $value)
1767
	{
1768 7
		$context[$area] = isset($context[$area]) ? $context[$area] : $value;
1769 7
	}
1770
1771
	// Set a couple of bits for the template.
1772 7
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1773 7
	$context['tabindex'] = 1;
1774
1775 7
	$context['theme_variant'] = '';
1776 7
	$context['theme_variant_url'] = '';
1777
1778 7
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
1779 7
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
1780
1781 7
	foreach (array('theme_header', 'upper_content') as $call)
1782
	{
1783 7
		if (!isset($context[$call . '_callbacks']))
1784 7
		{
1785
			$context[$call . '_callbacks'] = array();
1786
		}
1787 7
	}
1788
1789
	// This allows sticking some HTML on the page output - useful for controls.
1790 7
	$context['insert_after_template'] = '';
1791 7
}
1792
1793
/**
1794
 * Loads basic user information in to $context['user']
1795
 */
1796
function loadUserContext()
1797
{
1798 7
	global $context, $user_info, $txt, $modSettings;
1799
1800
	// Set up the contextual user array.
1801 7
	$context['user'] = array(
1802 7
		'id' => $user_info['id'],
1803 7
		'is_logged' => !$user_info['is_guest'],
1804 7
		'is_guest' => &$user_info['is_guest'],
1805 7
		'is_admin' => &$user_info['is_admin'],
1806 7
		'is_mod' => &$user_info['is_mod'],
1807 7
		'is_moderator' => &$user_info['is_moderator'],
1808
		// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1809 7
		'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'])))),
1810 7
		'username' => $user_info['username'],
1811 7
		'language' => $user_info['language'],
1812 7
		'email' => $user_info['email'],
1813 7
		'ignoreusers' => $user_info['ignoreusers'],
1814
	);
1815
1816
	// Something for the guests
1817 7
	if (!$context['user']['is_guest'])
1818 7
	{
1819 3
		$context['user']['name'] = $user_info['name'];
1820 3
	}
1821 4 View Code Duplication
	elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1822
	{
1823 4
		$context['user']['name'] = $txt['guest_title'];
1824 4
	}
1825
1826 7
	$context['user']['smiley_set'] = determineSmileySet($user_info['smiley_set'], $modSettings['smiley_sets_known']);
1827 7
	$context['smiley_enabled'] = $user_info['smiley_set'] !== 'none';
1828 7
	$context['user']['smiley_path'] = $modSettings['smileys_url'] . '/' . $context['user']['smiley_set'] . '/';
1829 7
}
1830
1831
/**
1832
 * Called if the detected URL is not the same as boardurl but is a common
1833
 * variation in which case it updates key system variables so it works.
1834
 *
1835
 * @param string $detected_url
1836
 */
1837
function fixThemeUrls($detected_url)
1838
{
1839
	global $boardurl, $scripturl, $settings, $modSettings, $context, $board_info;
1840
1841
	// Caching is good ;).
1842
	$oldurl = $boardurl;
1843
1844
	// Fix $boardurl and $scripturl.
1845
	$boardurl = $detected_url;
1846
	$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1847
	$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1848
1849
	// Fix the theme urls...
1850
	$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1851
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1852
	$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1853
	$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1854
	$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1855
	$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1856
1857
	// And just a few mod settings :).
1858
	$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1859
	$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1860
1861
	// Clean up after loadBoard().
1862
	if (isset($board_info['moderators']))
1863
	{
1864
		foreach ($board_info['moderators'] as $k => $dummy)
1865
		{
1866
			$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1867
			$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1868
		}
1869
	}
1870
1871
	foreach ($context['linktree'] as $k => $dummy)
1872
		$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1873
}
1874
1875
/**
1876
 * Determine the current user's smiley set
1877
 *
1878
 * @param mixed[] $user_smiley_set
1879
 * @param mixed[] $known_smiley_sets
1880
 *
1881
 * @return mixed
1882
 */
1883
function determineSmileySet($user_smiley_set, $known_smiley_sets)
1884
{
1885 7
	global $modSettings, $settings;
1886
1887 7
	if ((!in_array($user_smiley_set, explode(',', $known_smiley_sets)) && $user_smiley_set !== 'none') || empty($modSettings['smiley_sets_enable']))
1888 7
	{
1889 7
		$set = !empty($settings['smiley_sets_default']) ? $settings['smiley_sets_default'] : $modSettings['smiley_sets_default'];
1890 7
	}
1891
	else
1892
	{
1893
		$set = $user_smiley_set;
1894
	}
1895
1896 7
	return $set;
1897
}
1898
1899
/**
1900
 * This loads the bare minimum data.
1901
 *
1902
 * - Needed by scheduled tasks,
1903
 * - Needed by any other code that needs language files before the forum (the theme) is loaded.
1904
 */
1905
function loadEssentialThemeData()
1906
{
1907 1
	global $settings, $modSettings, $mbname, $context;
1908
1909 1
	if (function_exists('database') === false)
1910 1
	{
1911
		throw new \Exception('');
1912
	}
1913
1914 1
	$db = database();
1915
1916
	// Get all the default theme variables.
1917 1
	$db->fetchQueryCallback('
1918
		SELECT id_theme, variable, value
1919
		FROM {db_prefix}themes
1920
		WHERE id_member = {int:no_member}
1921 1
			AND id_theme IN (1, {int:theme_guests})',
1922
		array(
1923 1
			'no_member' => 0,
1924 1
			'theme_guests' => $modSettings['theme_guests'],
1925 1
		),
1926
		function ($row)
1927
		{
1928 1
			global $settings;
1929
1930 1
			$settings[$row['variable']] = $row['value'];
1931
1932
			// Is this the default theme?
1933 1
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
1934 1
				$settings['default_' . $row['variable']] = $row['value'];
1935 1
		}
1936 1
	);
1937
1938
	// Check we have some directories setup.
1939 1
	if (!Templates::instance()->hasDirectories())
1940 1
	{
1941
		Templates::instance()->reloadDirectories($settings);
1942
	}
1943
1944
	// Assume we want this.
1945 1
	$context['forum_name'] = $mbname;
1946 1
	$context['forum_name_html_safe'] = $context['forum_name'];
1947
1948 1
	loadLanguage('index+Addons');
1949 1
}
1950
1951
/**
1952
 * Load a template - if the theme doesn't include it, use the default.
1953
 *
1954
 * What it does:
1955
 *
1956
 * - loads a template file with the name template_name from the current, default, or base theme.
1957
 * - detects a wrong default theme directory and tries to work around it.
1958
 * - can be used to only load style sheets by using false as the template name
1959
 *   loading of style sheets with this function is deprecated, use loadCSSFile instead
1960
 * - if $settings['template_dirs'] is empty, it delays the loading of the template
1961
 *
1962
 * @uses the requireTemplate() function to actually load the file.
1963
 * @param string|false $template_name
1964
 * @param string[]|string $style_sheets any style sheets to load with the template
1965
 * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
1966
 *
1967
 * @return boolean|null
1968
 * @throws Elk_Exception
1969
 */
1970
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
1971
{
1972 13
	return Templates::instance()->load($template_name, $style_sheets, $fatal);
1973
}
1974
1975
/**
1976
 * Load a sub-template.
1977
 *
1978
 * What it does:
1979
 *
1980
 * - loads the sub template specified by sub_template_name, which must be in an already-loaded template.
1981
 * - if ?debug is in the query string, shows administrators a marker after every sub template
1982
 * for debugging purposes.
1983
 *
1984
 * @param string $sub_template_name
1985
 * @param bool|string $fatal = false
1986
 * - $fatal = true is for templates that shouldn't get a 'pretty' error screen
1987
 * - $fatal = 'ignore' to skip
1988
 * @throws Elk_Exception
1989
 */
1990
function loadSubTemplate($sub_template_name, $fatal = false)
1991
{
1992
	Templates::instance()->loadSubTemplate($sub_template_name, $fatal);
1993
1994
	return true;
1995
}
1996
1997
/**
1998
 * Add a CSS file for output later
1999
 *
2000
 * @param string[]|string $filenames string or array of filenames to work on
2001
 * @param mixed[] $params = array()
2002
 * Keys are the following:
2003
 * - ['local'] (true/false): define if the file is local
2004
 * - ['fallback'] (true/false): if false  will attempt to load the file
2005
 *   from the default theme if not found in the current theme
2006
 * - ['stale'] (true/false/string): if true or null, use cache stale,
2007
 *   false do not, or used a supplied string
2008
 * @param string $id optional id to use in html id=""
2009
 */
2010
function loadCSSFile($filenames, $params = array(), $id = '')
2011
{
2012 7
	global $context;
2013
2014 7
	if (empty($filenames))
2015 7
		return;
2016
2017 7
	if (!is_array($filenames))
2018 7
		$filenames = array($filenames);
2019
2020 7
	if (in_array('admin.css', $filenames))
2021 7
		$filenames[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css';
2022
2023 7
	$params['subdir'] = 'css';
2024 7
	$params['extension'] = 'css';
2025 7
	$params['index_name'] = 'css_files';
2026 7
	$params['debug_index'] = 'sheets';
2027
2028 7
	loadAssetFile($filenames, $params, $id);
2029 7
}
2030
2031
/**
2032
 * Add a Javascript file for output later
2033
 *
2034
 * What it does:
2035
 *
2036
 * - Can be passed an array of filenames, all which will have the same
2037
 *   parameters applied,
2038
 * - if you need specific parameters on a per file basis, call it multiple times
2039
 *
2040
 * @param string[]|string $filenames string or array of filenames to work on
2041
 * @param mixed[] $params = array()
2042
 * Keys are the following:
2043
 * - ['local'] (true/false): define if the file is local, if file does not
2044
 *     start with http its assumed local
2045
 * - ['defer'] (true/false): define if the file should load in <head> or before
2046
 *     the closing <html> tag
2047
 * - ['fallback'] (true/false): if true will attempt to load the file from the
2048
 *     default theme if not found in the current this is the default behavior
2049
 *     if this is not supplied
2050
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2051
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
2052
 *     not, or used a supplied string
2053
 * @param string $id = '' optional id to use in html id=""
2054
 */
2055
function loadJavascriptFile($filenames, $params = array(), $id = '')
2056
{
2057 8
	if (empty($filenames))
2058 8
		return;
2059
2060 8
	$params['subdir'] = 'scripts';
2061 8
	$params['extension'] = 'js';
2062 8
	$params['index_name'] = 'js_files';
2063 8
	$params['debug_index'] = 'javascript';
2064
2065 8
	loadAssetFile($filenames, $params, $id);
2066 8
}
2067
2068
/**
2069
 * Add an asset (css, js or other) file for output later
2070
 *
2071
 * What it does:
2072
 *
2073
 * - Can be passed an array of filenames, all which will have the same
2074
 *   parameters applied,
2075
 * - If you need specific parameters on a per file basis, call it multiple times
2076
 *
2077
 * @param string[]|string $filenames string or array of filenames to work on
2078
 * @param mixed[] $params = array()
2079
 * Keys are the following:
2080
 * - ['subdir'] (string): the subdirectory of the theme dir the file is in
2081
 * - ['extension'] (string): the extension of the file (e.g. css)
2082
 * - ['index_name'] (string): the $context index that holds the array of loaded
2083
 *     files
2084
 * - ['debug_index'] (string): the index that holds the array of loaded
2085
 *     files for debugging debug
2086
 * - ['local'] (true/false): define if the file is local, if file does not
2087
 *     start with http or // (schema-less URLs) its assumed local.
2088
 *     The parameter is in fact useful only for files whose name starts with
2089
 *     http, in any other case (e.g. passing a local URL) the parameter would
2090
 *     fail in properly adding the file to the list.
2091
 * - ['defer'] (true/false): define if the file should load in <head> or before
2092
 *     the closing <html> tag
2093
 * - ['fallback'] (true/false): if true will attempt to load the file from the
2094
 *     default theme if not found in the current this is the default behavior
2095
 *     if this is not supplied
2096
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2097
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
2098
 *     not, or used a supplied string
2099
 * @param string $id = '' optional id to use in html id=""
2100
 */
2101
function loadAssetFile($filenames, $params = array(), $id = '')
2102
{
2103 8
	global $settings, $context, $db_show_debug;
2104
2105 8
	if (empty($filenames))
2106 8
		return;
2107
2108 8
	$cache = Cache::instance();
2109
2110 8
	if (!is_array($filenames))
2111 8
		$filenames = array($filenames);
2112
2113
	// Static values for all these settings
2114 8
	if (!isset($params['stale']) || $params['stale'] === true)
2115 8
		$staler_string = CACHE_STALE;
2116
	elseif (is_string($params['stale']))
2117
		$staler_string = ($params['stale'][0] === '?' ? $params['stale'] : '?' . $params['stale']);
2118
	else
2119
		$staler_string = '';
2120
2121 8
	$fallback = (!empty($params['fallback']) && ($params['fallback'] === false)) ? false : true;
2122 8
	$dir = '/' . $params['subdir'] . '/';
2123
2124
	// Whoa ... we've done this before yes?
2125 8
	$cache_name = 'load_' . $params['extension'] . '_' . hash('md5', $settings['theme_dir'] . implode('_', $filenames));
2126 8
	$temp = array();
2127 8
	if ($cache->getVar($temp, $cache_name, 600))
2128 8
	{
2129
		if (empty($context[$params['index_name']]))
2130
			$context[$params['index_name']] = array();
2131
2132
		$context[$params['index_name']] += $temp;
2133
2134
		if ($db_show_debug === true)
2135
		{
2136
			foreach ($temp as $temp_params)
2137
			{
2138
				Debug::instance()->add($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'])) : '') . ')');
2139
			}
2140
		}
2141
	}
2142
	else
2143
	{
2144 8
		$this_build = array();
2145
2146
		// All the files in this group use the above parameters
2147 8
		foreach ($filenames as $filename)
2148
		{
2149
			// Account for shorthand like admin.ext?xyz11 filenames
2150 8
			$has_cache_staler = strpos($filename, '.' . $params['extension'] . '?');
2151
			if ($has_cache_staler)
2152 8
			{
2153
				$cache_staler = $staler_string;
2154
				$params['basename'] = substr($filename, 0, $has_cache_staler + strlen($params['extension']) + 1);
2155
			}
2156
			else
2157
			{
2158 8
				$cache_staler = $staler_string;
2159 8
				$params['basename'] = $filename;
2160
			}
2161 8
			$this_id = empty($id) ? strtr(basename($filename), '?', '_') : $id;
2162
2163
			// Is this a local file?
2164 8
			if (!empty($params['local']) || (substr($filename, 0, 4) !== 'http' && substr($filename, 0, 2) !== '//'))
2165 8
			{
2166 8
				$params['local'] = true;
2167 8
				$params['dir'] = $settings['theme_dir'] . $dir;
2168 8
				$params['url'] = $settings['theme_url'];
2169
2170
				// Fallback if we are not already in the default theme
2171 8
				if ($fallback && ($settings['theme_dir'] !== $settings['default_theme_dir']) && !file_exists($settings['theme_dir'] . $dir . $params['basename']))
2172 8
				{
2173
					// Can't find it in this theme, how about the default?
2174
					if (file_exists($settings['default_theme_dir'] . $dir . $params['basename']))
2175
					{
2176
						$filename = $settings['default_theme_url'] . $dir . $params['basename'] . $cache_staler;
2177
						$params['dir'] = $settings['default_theme_dir'] . $dir;
2178
						$params['url'] = $settings['default_theme_url'];
2179
					}
2180
					else
2181
						$filename = false;
2182
				}
2183
				else
2184 8
					$filename = $settings['theme_url'] . $dir . $params['basename'] . $cache_staler;
2185 8
			}
2186
2187
			// Add it to the array for use in the template
2188 8
			if (!empty($filename))
2189 8
			{
2190 8
				$this_build[$this_id] = $context[$params['index_name']][$this_id] = array('filename' => $filename, 'options' => $params);
2191
2192 8
				if ($db_show_debug === true)
2193 8
				{
2194
					Debug::instance()->add($params['debug_index'], $params['basename'] . '(' . (!empty($params['local']) ? (!empty($params['url']) ? basename($params['url']) : basename($params['dir'])) : '') . ')');
2195
				}
2196 8
			}
2197
2198
			// Save it so we don't have to build this so often
2199 8
			$cache->put($cache_name, $this_build, 600);
2200 8
		}
2201
	}
2202 8
}
2203
2204
/**
2205
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2206
 *
2207
 * @param mixed[] $vars array of vars to include in the output done as 'varname' => 'var value'
2208
 * @param bool $escape = false, whether or not to escape the value
2209
 */
2210
function addJavascriptVar($vars, $escape = false)
2211
{
2212 2
	theme()->addJavascriptVar($vars, $escape);
2213 2
}
2214
2215
/**
2216
 * Add a block of inline Javascript code to be executed later
2217
 *
2218
 * What it does:
2219
 *
2220
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2221
 *   or for scripts that require help from PHP/whatever, this can be useful.
2222
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2223
 *
2224
 * @param string $javascript
2225
 * @param bool $defer = false, define if the script should load in <head> or before the closing <html> tag
2226
 */
2227
function addInlineJavascript($javascript, $defer = false)
2228
{
2229 4
	theme()->addInlineJavascript($javascript, $defer);
2230 4
}
2231
2232
/**
2233
 * Load a language file.
2234
 *
2235
 * - Tries the current and default themes as well as the user and global languages.
2236
 *
2237
 * @param string $template_name
2238
 * @param string $lang = ''
2239
 * @param bool $fatal = true
2240
 * @param bool $force_reload = false
2241
 * @return string The language actually loaded.
2242
 */
2243
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2244
{
2245 37
	global $user_info, $language, $settings, $modSettings;
2246 37
	global $db_show_debug, $txt;
2247 37
	static $already_loaded = array();
2248
2249
	// Default to the user's language.
2250 37
	if ($lang == '')
2251 37
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2252
2253 37
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2254 37
		return $lang;
2255
2256
	// Do we want the English version of language file as fallback?
2257 17
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2258 17
		loadLanguage($template_name, 'english', false);
2259
2260
	// Make sure we have $settings - if not we're in trouble and need to find it!
2261 17
	if (empty($settings['default_theme_dir']))
2262 17
		loadEssentialThemeData();
2263
2264
	// What theme are we in?
2265 17
	$theme_name = basename($settings['theme_url']);
2266 17
	if (empty($theme_name))
2267 17
		$theme_name = 'unknown';
2268
2269 17
	$fix_arrays = false;
2270
	// For each file open it up and write it out!
2271 17
	foreach (explode('+', $template_name) as $template)
2272
	{
2273 17
		if ($template === 'index')
2274 17
			$fix_arrays = true;
2275
2276
		// Obviously, the current theme is most important to check.
2277
		$attempts = array(
2278 17
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2279 17
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2280 17
		);
2281
2282
		// Do we have a base theme to worry about?
2283 17
		if (isset($settings['base_theme_dir']))
2284 17
		{
2285
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2286
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2287
		}
2288
2289
		// Fall back on the default theme if necessary.
2290 17
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2291 17
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2292
2293
		// Fall back on the English language if none of the preferred languages can be found.
2294 17
		if (!in_array('english', array($lang, $language)))
2295 17
		{
2296
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2297
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2298
		}
2299
2300 17
		$templates = Templates::instance();
2301
2302
		// Try to find the language file.
2303 17
		$found = false;
2304 17
		foreach ($attempts as $k => $file)
2305
		{
2306 17
			if (file_exists($file[0] . '/languages/' . $file[2] . '/' . $file[1] . '.' . $file[2] . '.php'))
2307 17
			{
2308
				// Include it!
2309 17
				$templates->templateInclude($file[0] . '/languages/' . $file[2] . '/' . $file[1] . '.' . $file[2] . '.php');
2310
2311
				// Note that we found it.
2312 17
				$found = true;
2313
2314 17
				break;
2315
			}
2316
			// @deprecated since 1.0 - old way of archiving language files, all in one directory
2317
			elseif (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2318
			{
2319
				// Include it!
2320
				$templates->templateInclude($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2321
2322
				// Note that we found it.
2323
				$found = true;
2324
2325
				break;
2326
			}
2327 17
		}
2328
2329
		// That couldn't be found!  Log the error, but *try* to continue normally.
2330 17
		if (!$found && $fatal)
2331 17
		{
2332
			Errors::instance()->log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2333
			break;
2334
		}
2335 17
	}
2336
2337
	if ($fix_arrays)
2338 17
		fix_calendar_text();
2339
2340
	// Keep track of what we're up to soldier.
2341 17
	if ($db_show_debug === true)
2342 17
	{
2343
		Debug::instance()->add('language_files', $template_name . '.' . $lang . ' (' . $theme_name . ')');
2344
	}
2345
2346
	// Remember what we have loaded, and in which language.
2347 17
	$already_loaded[$template_name] = $lang;
2348
2349
	// Return the language actually loaded.
2350 17
	return $lang;
2351
}
2352
2353
/**
2354
 * Loads / Sets arrays for use in date display
2355
 */
2356
function fix_calendar_text()
2357
{
2358
	global $txt;
2359
2360
	$txt['days'] = array(
2361
		$txt['sunday'],
2362
		$txt['monday'],
2363
		$txt['tuesday'],
2364
		$txt['wednesday'],
2365
		$txt['thursday'],
2366
		$txt['friday'],
2367
		$txt['saturday'],
2368
	);
2369
	$txt['days_short'] = array(
2370
		$txt['sunday_short'],
2371
		$txt['monday_short'],
2372
		$txt['tuesday_short'],
2373
		$txt['wednesday_short'],
2374
		$txt['thursday_short'],
2375
		$txt['friday_short'],
2376
		$txt['saturday_short'],
2377
	);
2378
	$txt['months'] = array(
2379
		1 => $txt['january'],
2380
		$txt['february'],
2381
		$txt['march'],
2382
		$txt['april'],
2383
		$txt['may'],
2384
		$txt['june'],
2385
		$txt['july'],
2386
		$txt['august'],
2387
		$txt['september'],
2388
		$txt['october'],
2389
		$txt['november'],
2390
		$txt['december'],
2391
	);
2392
	$txt['months_titles'] = array(
2393
		1 => $txt['january_titles'],
2394
		$txt['february_titles'],
2395
		$txt['march_titles'],
2396
		$txt['april_titles'],
2397
		$txt['may_titles'],
2398
		$txt['june_titles'],
2399
		$txt['july_titles'],
2400
		$txt['august_titles'],
2401
		$txt['september_titles'],
2402
		$txt['october_titles'],
2403
		$txt['november_titles'],
2404
		$txt['december_titles'],
2405
	);
2406
	$txt['months_short'] = array(
2407
		1 => $txt['january_short'],
2408
		$txt['february_short'],
2409
		$txt['march_short'],
2410
		$txt['april_short'],
2411
		$txt['may_short'],
2412
		$txt['june_short'],
2413
		$txt['july_short'],
2414
		$txt['august_short'],
2415
		$txt['september_short'],
2416
		$txt['october_short'],
2417
		$txt['november_short'],
2418
		$txt['december_short'],
2419
	);
2420
}
2421
2422
/**
2423
 * Get all parent boards (requires first parent as parameter)
2424
 *
2425
 * What it does:
2426
 *
2427
 * - It finds all the parents of id_parent, and that board itself.
2428
 * - Additionally, it detects the moderators of said boards.
2429
 * - Returns an array of information about the boards found.
2430
 *
2431
 * @param int $id_parent
2432
 *
2433
 * @return array
2434
 * @throws Elk_Exception parent_not_found
2435
 */
2436
function getBoardParents($id_parent)
2437
{
2438 11
	global $scripturl;
2439
2440 11
	$db = database();
2441 11
	$cache = Cache::instance();
2442 11
	$boards = array();
2443
2444
	// First check if we have this cached already.
2445 11
	if (!$cache->getVar($boards, 'board_parents-' . $id_parent, 480))
2446 11
	{
2447 11
		$boards = array();
2448 11
		$original_parent = $id_parent;
2449
2450
		// Loop while the parent is non-zero.
2451 11
		while ($id_parent != 0)
2452
		{
2453 10
			$result = $db->query('', '
2454
				SELECT
2455
					b.id_parent, b.name, {int:board_parent} AS id_board, COALESCE(mem.id_member, 0) AS id_moderator,
2456
					mem.real_name, b.child_level
2457
				FROM {db_prefix}boards AS b
2458
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2459
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2460 10
				WHERE b.id_board = {int:board_parent}',
2461
				array(
2462 10
					'board_parent' => $id_parent,
2463
				)
2464 10
			);
2465
			// In the EXTREMELY unlikely event this happens, give an error message.
2466 10
			if ($db->num_rows($result) == 0)
2467 10
			{
2468
				throw new Elk_Exception('parent_not_found', 'critical');
2469
			}
2470 10
			while ($row = $db->fetch_assoc($result))
2471
			{
2472 10
				if (!isset($boards[$row['id_board']]))
2473 10
				{
2474 10
					$id_parent = $row['id_parent'];
2475 10
					$boards[$row['id_board']] = array(
2476 10
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2477 10
						'name' => $row['name'],
2478 10
						'level' => $row['child_level'],
2479 10
						'moderators' => array()
2480 10
					);
2481 10
				}
2482
2483
				// If a moderator exists for this board, add that moderator for all children too.
2484 10
				if (!empty($row['id_moderator']))
2485 10
					foreach ($boards as $id => $dummy)
2486
					{
2487
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2488
							'id' => $row['id_moderator'],
2489
							'name' => $row['real_name'],
2490
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2491
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2492
						);
2493
					}
2494 10
			}
2495 10
			$db->free_result($result);
2496 10
		}
2497
2498 11
		$cache->put('board_parents-' . $original_parent, $boards, 480);
2499 11
	}
2500
2501 11
	return $boards;
2502
}
2503
2504
/**
2505
 * Attempt to reload our known languages.
2506
 *
2507
 * @param bool $use_cache = true
2508
 */
2509
function getLanguages($use_cache = true)
2510
{
2511 1
	global $settings;
2512
2513 1
	$cache = Cache::instance();
2514
2515
	// Either we don't use the cache, or its expired.
2516 1
	$languages = array();
2517
2518 1
	if (!$use_cache || !$cache->getVar($languages, 'known_languages', $cache->levelLowerThan(2) ? 86400 : 3600))
2519 1
	{
2520
		// If we don't have our theme information yet, lets get it.
2521 1
		if (empty($settings['default_theme_dir']))
2522 1
			loadTheme(0, false);
2523
2524
		// Default language directories to try.
2525
		$language_directories = array(
2526 1
			$settings['default_theme_dir'] . '/languages',
2527 1
			$settings['actual_theme_dir'] . '/languages',
2528 1
		);
2529
2530
		// We possibly have a base theme directory.
2531 1
		if (!empty($settings['base_theme_dir']))
2532 1
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2533
2534
		// Remove any duplicates.
2535 1
		$language_directories = array_unique($language_directories);
2536
2537 1
		foreach ($language_directories as $language_dir)
2538
		{
2539
			// Can't look in here... doesn't exist!
2540 1
			if (!file_exists($language_dir))
2541 1
				continue;
2542
2543 1
			$dir = dir($language_dir);
2544 1
			while ($entry = $dir->read())
2545
			{
2546
				// Only directories are interesting
2547 1
				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...
2548 1
					continue;
2549
2550
				// @todo at some point we may want to simplify that stuff (I mean scanning all the files just for index)
2551 1
				$file_dir = dir($dir->path . '/' . $entry);
2552 1
				while ($file_entry = $file_dir->read())
2553
				{
2554
					// Look for the index language file....
2555 1
					if (!preg_match('~^index\.(.+)\.php$~', $file_entry, $matches))
2556 1
						continue;
2557
2558 1
					$languages[$matches[1]] = array(
2559 1
						'name' => Util::ucwords(strtr($matches[1], array('_' => ' '))),
2560 1
						'selected' => false,
2561 1
						'filename' => $matches[1],
2562 1
						'location' => $language_dir . '/' . $entry . '/index.' . $matches[1] . '.php',
2563
					);
2564 1
				}
2565 1
				$file_dir->close();
2566 1
			}
2567 1
			$dir->close();
2568 1
		}
2569
2570
		// Lets cash in on this deal.
2571 1
		$cache->put('known_languages', $languages, $cache->isEnabled() && $cache->levelLowerThan(1) ? 86400 : 3600);
2572 1
	}
2573
2574 1
	return $languages;
2575
}
2576
2577
/**
2578
 * Initialize a database connection.
2579
 */
2580
function loadDatabase()
2581
{
2582
	global $db_persist, $db_server, $db_user, $db_passwd, $db_port;
2583
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix;
2584
2585
	// Database stuffs
2586
	require_once(SOURCEDIR . '/database/Database.subs.php');
2587
2588
	// Figure out what type of database we are using.
2589
	if (empty($db_type) || !file_exists(SOURCEDIR . '/database/Db-' . $db_type . '.class.php'))
2590
		$db_type = 'mysql';
2591
2592
	// 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.
2593
	if (ELK === 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
2594
		$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);
2595
2596
	// Either we aren't in SSI mode, or it failed.
2597
	if (empty($connection))
2598
		$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);
2599
2600
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
2601
	if (!$connection)
2602
		Errors::instance()->display_db_error();
2603
2604
	// If in SSI mode fix up the prefix.
2605
	$db = database();
2606
	if (ELK === 'SSI')
2607
		$db_prefix = $db->fix_prefix($db_prefix, $db_name);
2608
2609
	// Case sensitive database? Let's define a constant.
2610
	if ($db->db_case_sensitive() && !defined('DB_CASE_SENSITIVE'))
2611
		DEFINE('DB_CASE_SENSITIVE', '1');
2612
}
2613
2614
/**
2615
 * Determine the user's avatar type and return the information as an array
2616
 *
2617
 * @todo this function seems more useful than expected, it should be improved. :P
2618
 *
2619
 * @event integrate_avatar allows access to $avatar array before it is returned
2620
 * @param mixed[] $profile array containing the users profile data
2621
 *
2622
 * @return mixed[] $avatar
2623
 */
2624
function determineAvatar($profile)
2625
{
2626 2
	global $modSettings, $scripturl, $settings;
2627
2628 2
	if (empty($profile))
2629 2
		return array();
2630
2631 2
	$avatar_protocol = substr(strtolower($profile['avatar']), 0, 7);
2632
2633
	// uploaded avatar?
2634 2
	if ($profile['id_attach'] > 0 && empty($profile['avatar']))
2635 2
	{
2636
		// where are those pesky avatars?
2637
		$avatar_url = empty($profile['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $profile['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
2638
2639
		$avatar = array(
2640
			'name' => $profile['avatar'],
2641
			'image' => '<img class="avatar avatarresize" src="' . $avatar_url . '" alt="" />',
2642
			'href' => $avatar_url,
2643
			'url' => '',
2644
		);
2645
	}
2646
	// remote avatar?
2647 2
	elseif ($avatar_protocol === 'http://' || $avatar_protocol === 'https:/')
2648
	{
2649
		$avatar = array(
2650
			'name' => $profile['avatar'],
2651
			'image' => '<img class="avatar avatarresize" src="' . $profile['avatar'] . '" alt="" />',
2652
			'href' => $profile['avatar'],
2653
			'url' => $profile['avatar'],
2654
		);
2655
	}
2656
	// Gravatar instead?
2657 2
	elseif (!empty($profile['avatar']) && $profile['avatar'] === 'gravatar')
2658
	{
2659
		// Gravatars URL.
2660
		$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']) : '');
2661
2662
		$avatar = array(
2663
			'name' => $profile['avatar'],
2664
			'image' => '<img class="avatar avatarresize" src="' . $gravatar_url . '" alt="" />',
2665
			'href' => $gravatar_url,
2666
			'url' => $gravatar_url,
2667
		);
2668
	}
2669
	// an avatar from the gallery?
2670 2
	elseif (!empty($profile['avatar']) && !($avatar_protocol === 'http://' || $avatar_protocol === 'https:/'))
2671
	{
2672
		$avatar = array(
2673
			'name' => $profile['avatar'],
2674
			'image' => '<img class="avatar avatarresize" src="' . $modSettings['avatar_url'] . '/' . $profile['avatar'] . '" alt="" />',
2675
			'href' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
2676
			'url' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
2677
		);
2678
	}
2679
	// no custom avatar found yet, maybe a default avatar?
2680 2
	elseif (!empty($modSettings['avatar_default']) && empty($profile['avatar']) && empty($profile['filename']))
2681
	{
2682
		// $settings not initialized? We can't do anything further..
2683
		if (!empty($settings))
2684
		{
2685
			// Let's proceed with the default avatar.
2686
			// TODO: This should be incorporated into the theme.
2687
			$avatar = array(
2688
				'name' => '',
2689
				'image' => '<img class="avatar avatarresize" src="' . $settings['images_url'] . '/default_avatar.png" alt="" />',
2690
				'href' => $settings['images_url'] . '/default_avatar.png',
2691
				'url' => 'http://',
2692
			);
2693
		}
2694
		else
2695
		{
2696
			$avatar = array();
2697
		}
2698
	}
2699
	// finally ...
2700
	else
2701
		$avatar = array(
2702 2
			'name' => '',
2703 2
			'image' => '',
2704 2
			'href' => '',
2705
			'url' => ''
2706 2
		);
2707
2708
	// Make sure there's a preview for gravatars available.
2709 2
	$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']) : '');
2710
2711 2
	call_integration_hook('integrate_avatar', array(&$avatar, $profile));
2712
2713 2
	return $avatar;
2714
}
2715
2716
/**
2717
 * Get information about the server
2718
 */
2719
function detectServer()
2720
{
2721 15
	global $context;
2722 15
	static $server = null;
2723
2724 15
	if ($server === null)
2725 15
	{
2726
		$server = new Server($_SERVER);
2727
		$servers = array('iis', 'apache', 'litespeed', 'lighttpd', 'nginx', 'cgi', 'windows');
2728
		$context['server'] = array();
2729
		foreach ($servers as $name)
2730
		{
2731
			$context['server']['is_' . $name] = $server->is($name);
2732
		}
2733
2734
		$context['server']['iso_case_folding'] = $server->is('iso_case_folding');
2735
		// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
2736
		$context['server']['needs_login_fix'] = $server->is('needs_login_fix');
2737
	}
2738
2739 15
	return $server;
2740
}
2741
2742
/**
2743
 * Returns if a webserver is of type server (apache, nginx, etc)
2744
 *
2745
 * @param $server
2746
 *
2747
 * @return bool
2748
 */
2749
function serverIs($server)
2750
{
2751
	return detectServer()->is($server);
2752
}
2753
2754
/**
2755
 * Do some important security checks:
2756
 *
2757
 * What it does:
2758
 *
2759
 * - Checks the existence of critical files e.g. install.php
2760
 * - Checks for an active admin session.
2761
 * - Checks cache directory is writable.
2762
 * - Calls secureDirectory to protect attachments & cache.
2763
 * - Checks if the forum is in maintenance mode.
2764
 */
2765
function doSecurityChecks()
2766
{
2767
	global $modSettings, $context, $maintenance, $user_info, $txt, $scripturl, $user_settings, $options;
2768
2769
	$show_warnings = false;
2770
2771
	$cache = Cache::instance();
2772
2773
	if (allowedTo('admin_forum') && !$user_info['is_guest'])
2774
	{
2775
		// If agreement is enabled, at least the english version shall exists
2776 View Code Duplication
		if ($modSettings['requireAgreement'] && !file_exists(BOARDDIR . '/agreement.txt'))
2777
		{
2778
			$context['security_controls_files']['title'] = $txt['generic_warning'];
2779
			$context['security_controls_files']['errors']['agreement'] = $txt['agreement_missing'];
2780
			$show_warnings = true;
2781
		}
2782
2783
		// Cache directory writable?
2784 View Code Duplication
		if ($cache->isEnabled() && !is_writable(CACHEDIR))
2785
		{
2786
			$context['security_controls_files']['title'] = $txt['generic_warning'];
2787
			$context['security_controls_files']['errors']['cache'] = $txt['cache_writable'];
2788
			$show_warnings = true;
2789
		}
2790
2791
		if (checkSecurityFiles())
2792
			$show_warnings = true;
2793
2794
		// We are already checking so many files...just few more doesn't make any difference! :P
2795
		require_once(SUBSDIR . '/Attachments.subs.php');
2796
		$path = getAttachmentPath();
2797
		secureDirectory($path, true);
2798
		secureDirectory(CACHEDIR, false, '"\.(js|css)$"');
2799
2800
		// Active admin session?
2801
		if (isAdminSessionActive())
2802
			$context['warning_controls']['admin_session'] = sprintf($txt['admin_session_active'], ($scripturl . '?action=admin;area=adminlogoff;redir;' . $context['session_var'] . '=' . $context['session_id']));
2803
2804
		// Maintenance mode enabled?
2805
		if (!empty($maintenance))
2806
			$context['warning_controls']['maintenance'] = sprintf($txt['admin_maintenance_active'], ($scripturl . '?action=admin;area=serversettings;' . $context['session_var'] . '=' . $context['session_id']));
2807
2808
		// New updates
2809
		if (defined('FORUM_VERSION'))
2810
		{
2811
			$index = 'new_in_' . str_replace(array('ElkArte ', '.'), array('', '_'), FORUM_VERSION);
2812
			if (!empty($modSettings[$index]) && empty($options['dismissed_' . $index]))
2813
			{
2814
				$show_warnings = true;
2815
				$context['new_version_updates'] = array(
2816
					'title' => $txt['new_version_updates'],
2817
					'errors' => array(replaceBasicActionUrl($txt['new_version_updates_text'])),
2818
				);
2819
			}
2820
		}
2821
	}
2822
2823
	// Check for database errors.
2824
	if (!empty($_SESSION['query_command_denied']))
2825
	{
2826
		if ($user_info['is_admin'])
2827
		{
2828
			$context['security_controls_query']['title'] = $txt['query_command_denied'];
2829
			$show_warnings = true;
2830 View Code Duplication
			foreach ($_SESSION['query_command_denied'] as $command => $error)
2831
				$context['security_controls_query']['errors'][$command] = '<pre>' . Util::htmlspecialchars($error) . '</pre>';
2832
		}
2833
		else
2834
		{
2835
			$context['security_controls_query']['title'] = $txt['query_command_denied_guests'];
2836 View Code Duplication
			foreach ($_SESSION['query_command_denied'] as $command => $error)
2837
				$context['security_controls_query']['errors'][$command] = '<pre>' . sprintf($txt['query_command_denied_guests_msg'], Util::htmlspecialchars($command)) . '</pre>';
2838
		}
2839
	}
2840
2841
	// Are there any members waiting for approval?
2842
	if (allowedTo('moderate_forum') && ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) && !empty($modSettings['unapprovedMembers']))
2843
		$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']);
2844
2845
	if (!empty($context['open_mod_reports']) && (empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1))
2846
	{
2847
		$context['warning_controls']['open_mod_reports'] = '<a href="' . $scripturl . '?action=moderate;area=reports">' . sprintf($txt['mod_reports_waiting'], $context['open_mod_reports']) . '</a>';
2848
	}
2849
2850
	if (!empty($context['open_pm_reports']) && allowedTo('admin_forum'))
2851
	{
2852
		$context['warning_controls']['open_pm_reports'] = '<a href="' . $scripturl . '?action=moderate;area=pm_reports">' . sprintf($txt['pm_reports_waiting'], $context['open_pm_reports']) . '</a>';
2853
	}
2854
2855
	if (isset($_SESSION['ban']['cannot_post']))
2856
	{
2857
		// An admin cannot be banned (technically he could), and if it is better he knows.
2858
		$context['security_controls_ban']['title'] = sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
2859
		$show_warnings = true;
2860
2861
		$context['security_controls_ban']['errors']['reason'] = '';
2862
2863
		if (!empty($_SESSION['ban']['cannot_post']['reason']))
2864
			$context['security_controls_ban']['errors']['reason'] = $_SESSION['ban']['cannot_post']['reason'];
2865
2866
		if (!empty($_SESSION['ban']['expire_time']))
2867
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . sprintf($txt['your_ban_expires'], standardTime($_SESSION['ban']['expire_time'], false)) . '</span>';
2868
		else
2869
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . $txt['your_ban_expires_never'] . '</span>';
2870
	}
2871
2872
	// Finally, let's show the layer.
2873
	if ($show_warnings || !empty($context['warning_controls']))
2874
		\Template_Layers::instance()->addAfter('admin_warning', 'body');
2875
}
2876
2877
/**
2878
 * Load everything necessary for the BBC parsers
2879
 */
2880
function loadBBCParsers()
2881
{
2882
	global $modSettings;
2883
2884
	// Set the default disabled BBC
2885
	if (!empty($modSettings['disabledBBC']))
2886
	{
2887
		if (!is_array($modSettings['disabledBBC']))
2888
			$disabledBBC = explode(',', $modSettings['disabledBBC']);
2889
		else
2890
			$disabledBBC = $modSettings['disabledBBC'];
2891
		\BBC\ParserWrapper::instance()->setDisabled(empty($disabledBBC) ? array() : (array) $disabledBBC);
2892
	}
2893
2894
	return 1;
2895
}
2896
2897
/**
2898
 * This is necessary to support data stored in the pre-1.0.8 way (i.e. serialized)
2899
 *
2900
 * @param string $variable The string to convert
2901
 * @param null|callable $save_callback The function that will save the data to the db
2902
 * @return mixed[] the array
2903
 */
2904
function serializeToJson($variable, $save_callback = null)
2905
{
2906
	$array_form = json_decode($variable, true);
2907
2908
	// decoding failed, let's try with unserialize
2909
	if (!is_array($array_form))
2910
	{
2911
		try
2912
		{
2913
			$array_form = Util::unserialize($variable);
2914
		}
2915
		catch (\Exception $e)
2916
		{
2917
			$array_form = false;
2918
		}
2919
2920
		// If unserialize fails as well, let's just store an empty array
2921
		if ($array_form === false)
2922
		{
2923
			$array_form = array(0, '', 0);
2924
		}
2925
2926
		// Time to update the value if necessary
2927
		if ($save_callback !== null)
2928
		{
2929
			$save_callback($array_form);
2930
		}
2931
	}
2932
2933
	return $array_form;
2934
}
2935