Completed
Pull Request — development (#2960)
by Elk
09:10
created

Load.php ➔ fix_calendar_text()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 65
Code Lines 57

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 57
CRAP Score 1

Importance

Changes 0
Metric Value
cc 1
eloc 57
nc 1
nop 0
dl 0
loc 65
ccs 57
cts 57
cp 1
crap 1
rs 9.3571
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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