Passed
Pull Request — master (#3533)
by Martyn
06:03
created

elk_strftime()   A

Complexity

Conditions 4
Paths 3

Size

Total Lines 69
Code Lines 53

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 20

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 4
eloc 53
c 1
b 0
f 0
nc 3
nop 2
dl 0
loc 69
ccs 0
cts 0
cp 0
crap 20
rs 9.0254

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.7
15
 *
16
 */
17
18
/**
19
 * Load the $modSettings array and many necessary forum settings.
20
 *
21
 * What it does:
22
 *
23
 * - load the settings from cache if available, otherwise from the database.
24
 * - sets the timezone
25
 * - checks the load average settings if available.
26
 * - check whether post moderation is enabled.
27
 * - calls add_integration_function
28
 * - calls integrate_pre_include, integrate_pre_load,
29
 *
30
 * @event integrate_load_average is called if load average is enabled
31
 * @event integrate_pre_include to allow including files at startup
32
 * @event integrate_pre_load to call any pre load integration functions.
33
 *
34
 * @global array $modSettings is a giant array of all of the forum-wide settings and statistics.
35
 */
36
function reloadSettings()
37
{
38
	global $modSettings;
39
40
	$db = database();
41
	$cache = Cache::instance();
42
	$hooks = Hooks::instance();
43
44
	// Try to load it from the cache first; it'll never get cached if the setting is off.
45
	if (!$cache->getVar($modSettings, 'modSettings', 90))
46
	{
47
		$request = $db->query('', '
48
			SELECT variable, value
49
			FROM {db_prefix}settings',
50
			array(
51
			)
52
		);
53
		$modSettings = array();
54
		if (!$request)
55
			Errors::instance()->display_db_error();
56
		while ($row = $db->fetch_row($request))
57
			$modSettings[$row[0]] = $row[1];
58
		$db->free_result($request);
59
60
		// Do a few things to protect against missing settings or settings with invalid values...
61
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
62
			$modSettings['defaultMaxTopics'] = 20;
63
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
64
			$modSettings['defaultMaxMessages'] = 15;
65
		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);
0 ignored issues
show
Bug introduced by
The constant ELK_INTEGRATION_SETTINGS was not found. Maybe you did not declare it correctly or list all dependencies?
Loading history...
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
		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
Comprehensibility Best Practice introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.
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);
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)))
0 ignored issues
show
introduced by
The condition ELK != 'SSI' is always false.
Loading history...
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 === false || $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
		// Lets upgrade the salt if needed.
331
		require_once(SUBSDIR . '/Members.subs.php');
332
		if (updateMemberSalt($id_member))
333
		{
334
			require_once(SUBSDIR . '/Auth.subs.php');
335
			setLoginCookie(60 * $modSettings['cookieTime'], $user_settings['id_member'], hash('sha256', ($user_settings['passwd'] . $user_settings['password_salt'])));
336
		}
337
	}
338
	// If the user is a guest, initialize all the critical user settings.
339
	else
340
	{
341
		// This is what a guest's variables should be.
342
		$username = '';
343
		$user_info = array('groups' => array(-1));
344
		$user_settings = array();
345
346
		if (isset($_COOKIE[$cookiename]))
347
			$_COOKIE[$cookiename] = '';
348
349
		// Create a login token if it doesn't exist yet.
350
		if (!isset($_SESSION['token']['post-login']))
351
			createToken('login');
352
		else
353
			list ($context['login_token_var'],,, $context['login_token']) = $_SESSION['token']['post-login'];
354
355
		// Do we perhaps think this is a search robot? Check every five minutes just in case...
356
		if ((!empty($modSettings['spider_mode']) || !empty($modSettings['spider_group'])) && (!isset($_SESSION['robot_check']) || $_SESSION['robot_check'] < time() - 300))
357
		{
358
			require_once(SUBSDIR . '/SearchEngines.subs.php');
359
			$user_info['possibly_robot'] = spiderCheck();
360
		}
361
		elseif (!empty($modSettings['spider_mode']))
362
			$user_info['possibly_robot'] = isset($_SESSION['id_robot']) ? $_SESSION['id_robot'] : 0;
363
		// If we haven't turned on proper spider hunts then have a guess!
364
		else
365
		{
366
			$ci_user_agent = strtolower($req->user_agent());
367
			$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;
368
		}
369
	}
370
371
	// Set up the $user_info array.
372
	$user_info += array(
373
		'id' => $id_member,
374
		'username' => $username,
375
		'name' => isset($user_settings['real_name']) ? $user_settings['real_name'] : '',
376
		'email' => isset($user_settings['email_address']) ? $user_settings['email_address'] : '',
377
		'passwd' => isset($user_settings['passwd']) ? $user_settings['passwd'] : '',
378
		'language' => empty($user_settings['lngfile']) || empty($modSettings['userLanguage']) ? $language : $user_settings['lngfile'],
379
		'is_guest' => $id_member == 0,
380
		'is_admin' => in_array(1, $user_info['groups']),
381
		'is_mod' => false,
382
		'theme' => empty($user_settings['id_theme']) ? 0 : $user_settings['id_theme'],
383
		'last_login' => empty($user_settings['last_login']) ? 0 : $user_settings['last_login'],
384
		'ip' => $req->client_ip(),
385
		'ip2' => $req->ban_ip(),
386
		'posts' => empty($user_settings['posts']) ? 0 : $user_settings['posts'],
387
		'time_format' => empty($user_settings['time_format']) ? $modSettings['time_format'] : $user_settings['time_format'],
388
		'time_offset' => empty($user_settings['time_offset']) ? 0 : $user_settings['time_offset'],
389
		'avatar' => array_merge(array(
390
			'url' => isset($user_settings['avatar']) ? $user_settings['avatar'] : '',
391
			'filename' => empty($user_settings['filename']) ? '' : $user_settings['filename'],
392
			'custom_dir' => !empty($user_settings['attachment_type']) && $user_settings['attachment_type'] == 1,
393
			'id_attach' => isset($user_settings['id_attach']) ? $user_settings['id_attach'] : 0
394
		), determineAvatar($user_settings)),
395
		'smiley_set' => isset($user_settings['smiley_set']) ? $user_settings['smiley_set'] : '',
396
		'messages' => empty($user_settings['personal_messages']) ? 0 : $user_settings['personal_messages'],
397
		'mentions' => empty($user_settings['mentions']) ? 0 : max(0, $user_settings['mentions']),
398
		'unread_messages' => empty($user_settings['unread_messages']) ? 0 : $user_settings['unread_messages'],
399
		'total_time_logged_in' => empty($user_settings['total_time_logged_in']) ? 0 : $user_settings['total_time_logged_in'],
400
		'buddies' => !empty($modSettings['enable_buddylist']) && !empty($user_settings['buddy_list']) ? explode(',', $user_settings['buddy_list']) : array(),
401
		'ignoreboards' => !empty($user_settings['ignore_boards']) && !empty($modSettings['allow_ignore_boards']) ? explode(',', $user_settings['ignore_boards']) : array(),
402
		'ignoreusers' => !empty($user_settings['pm_ignore_list']) ? explode(',', $user_settings['pm_ignore_list']) : array(),
403
		'warning' => isset($user_settings['warning']) ? $user_settings['warning'] : 0,
404
		'permissions' => array(),
405
	);
406
	$user_info['groups'] = array_unique($user_info['groups']);
407
408
	// 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.
409
	if (!empty($user_info['ignoreboards']) && empty($user_info['ignoreboards'][$tmp = count($user_info['ignoreboards']) - 1]))
410
		unset($user_info['ignoreboards'][$tmp]);
411
412
	// Do we have any languages to validate this?
413
	if (!empty($modSettings['userLanguage']) && (!empty($_GET['language']) || !empty($_SESSION['language'])))
414
		$languages = getLanguages();
415
416
	// Allow the user to change their language if its valid.
417
	if (!empty($modSettings['userLanguage']) && !empty($_GET['language']) && isset($languages[strtr($_GET['language'], './\\:', '____')]))
418
	{
419
		$user_info['language'] = strtr($_GET['language'], './\\:', '____');
420
		$_SESSION['language'] = $user_info['language'];
421
	}
422
	elseif (!empty($modSettings['userLanguage']) && !empty($_SESSION['language']) && isset($languages[strtr($_SESSION['language'], './\\:', '____')]))
423
		$user_info['language'] = strtr($_SESSION['language'], './\\:', '____');
424
425
	// Just build this here, it makes it easier to change/use - administrators can see all boards.
426
	if ($user_info['is_admin'])
427
		$user_info['query_see_board'] = '1=1';
428
	// Otherwise just the groups in $user_info['groups'].
429
	else
430
		$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'] : '') . ')';
431
	// Build the list of boards they WANT to see.
432
	// This will take the place of query_see_boards in certain spots, so it better include the boards they can see also
433
434
	// If they aren't ignoring any boards then they want to see all the boards they can see
435
	if (empty($user_info['ignoreboards']))
436
		$user_info['query_wanna_see_board'] = $user_info['query_see_board'];
437
	// Ok I guess they don't want to see all the boards
438
	else
439
		$user_info['query_wanna_see_board'] = '(' . $user_info['query_see_board'] . ' AND b.id_board NOT IN (' . implode(',', $user_info['ignoreboards']) . '))';
440
441
	if ($user_info['is_guest'] === false)
442
	{
443
		$http_request = HttpReq::instance();
444
		if (!empty($modSettings['force_accept_agreement']))
445
		{
446
			if (!empty($modSettings['agreementRevision']) && !empty($modSettings['requireAgreement']) && in_array($http_request->action, array('reminder', 'register')) === false)
0 ignored issues
show
Bug Best Practice introduced by
The property action does not exist on HttpReq. Since you implemented __get, consider adding a @property annotation.
Loading history...
447
			{
448
				if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount')
0 ignored issues
show
Bug Best Practice introduced by
The property area does not exist on HttpReq. Since you implemented __get, consider adding a @property annotation.
Loading history...
449
				{
450
					$agreement = new \Agreement($user_info['language']);
451
					if (false === $agreement->checkAccepted($id_member, $modSettings['agreementRevision']))
452
					{
453
						setOldUrl('agreement_url_redirect');
454
						redirectexit('action=register;sa=agreement');
455
					}
456
				}
457
			}
458
		}
459
		if (!empty($modSettings['force_accept_privacy_policy']))
460
		{
461
			if (!empty($modSettings['privacypolicyRevision']) && !empty($modSettings['requirePrivacypolicy']) && in_array($http_request->action, array('reminder', 'register')) === false)
462
			{
463
				if ($http_request->action !== 'profile' || $http_request->area !== 'deleteaccount')
464
				{
465
					$privacypol = new \PrivacyPolicy($user_info['language']);
466
					if (false === $privacypol->checkAccepted($id_member, $modSettings['privacypolicyRevision']))
467
					{
468
						setOldUrl('agreement_url_redirect');
469
						redirectexit('action=register;sa=privacypol');
470
					}
471
				}
472
			}
473
		}
474
	}
475
	call_integration_hook('integrate_user_info');
476
}
477
478
/**
479
 * Check for moderators and see if they have access to the board.
480
 *
481
 * What it does:
482
 *
483
 * - sets up the $board_info array for current board information.
484
 * - if cache is enabled, the $board_info array is stored in cache.
485
 * - redirects to appropriate post if only message id is requested.
486
 * - is only used when inside a topic or board.
487
 * - determines the local moderators for the board.
488
 * - adds group id 3 if the user is a local moderator for the board they are in.
489
 * - prevents access if user is not in proper group nor a local moderator of the board.
490
 *
491 1
 * @event integrate_load_board_query allows to add tables and columns to the query, used
492 1
 * to add to the $board_info array
493
 * @event integrate_loaded_board called after board_info is populated, allows to add
494 1
 * directly to $board_info
495 1
 *
496
 */
497
function loadBoard()
498 1
{
499
	global $txt, $scripturl, $context, $modSettings;
500
	global $board_info, $board, $topic, $user_info;
501 1
502
	$db = database();
503
	$cache = Cache::instance();
504 1
505
	// Assume they are not a moderator.
506
	$user_info['is_mod'] = false;
507 1
	// @since 1.0.5 - is_mod takes into account only local (board) moderators,
508 1
	// and not global moderators, is_moderator is meant to take into account both.
509
	$user_info['is_moderator'] = false;
510
511
	// Start the linktree off empty..
512
	$context['linktree'] = array();
513
514
	// Have they by chance specified a message id but nothing else?
515
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
516
	{
517
		// Make sure the message id is really an int.
518
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
519
520
		// Looking through the message table can be slow, so try using the cache first.
521
		if (!$cache->getVar($topic, 'msg_topic-' . $_REQUEST['msg'], 120))
522
		{
523
			require_once(SUBSDIR . '/Messages.subs.php');
524
			$topic = associatedTopic($_REQUEST['msg']);
525
526
			// So did it find anything?
527
			if ($topic !== false)
528
			{
529
				// Save save save.
530
				$cache->put('msg_topic-' . $_REQUEST['msg'], $topic, 120);
531
			}
532
		}
533
534
		// Remember redirection is the key to avoiding fallout from your bosses.
535
		if (!empty($topic))
536
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
537
		else
538 1
		{
539 1
			loadPermissions();
540
			loadTheme();
541
			throw new Elk_Exception('topic_gone', false);
542
		}
543
	}
544 1
545 1
	// Load this board only if it is specified.
546
	if (empty($board) && empty($topic))
547
	{
548
		$board_info = array('moderators' => array());
549
		return;
550
	}
551
552
	if ($cache->isEnabled() && (empty($topic) || $cache->levelHigherThan(2)))
553
	{
554
		// @todo SLOW?
555
		if (!empty($topic))
556
			$temp = $cache->get('topic_board-' . $topic, 120);
557
		else
558
			$temp = $cache->get('board-' . $board, 120);
559 1
560 1
		if (!empty($temp))
561 1
		{
562 1
			$board_info = $temp;
563
			$board = (int) $board_info['id'];
564 1
		}
565
	}
566 1
567
	if (empty($temp))
568
	{
569
		$select_columns = array();
570 1
		$select_tables = array();
571
		// Wanna grab something more from the boards table or another table at all?
572 1
		call_integration_hook('integrate_load_board_query', array(&$select_columns, &$select_tables));
573 1
574 1
		$request = $db->query('', '
575 1
			SELECT
576
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
577
				b.id_parent, c.name AS cname, COALESCE(mem.id_member, 0) AS id_moderator,
578
				mem.real_name' . (!empty($topic) ? ', b.id_board' : '') . ', b.child_level,
579 1
				b.id_theme, b.override_theme, b.count_posts, b.id_profile, b.redirect,
580
				b.unapproved_topics, b.unapproved_posts' . (!empty($topic) ? ', t.approved, t.id_member_started' : '') . (!empty($select_columns) ? ', ' . implode(', ', $select_columns) : '') . '
581 1
			FROM {db_prefix}boards AS b' . (!empty($topic) ? '
582 1
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})' : '') . (!empty($select_tables) ? '
583
				' . implode("\n\t\t\t\t", $select_tables) : '') . '
584 1
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
585
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
586 1
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
587 1
			WHERE b.id_board = {raw:board_link}',
588 1
			array(
589
				'current_topic' => $topic,
590
				'board_link' => empty($topic) ? $db->quote('{int:current_board}', array('current_board' => $board)) : 't.id_board',
591 1
			)
592 1
		);
593
		// If there aren't any, skip.
594
		if ($db->num_rows($request) > 0)
595
		{
596 1
			$row = $db->fetch_assoc($request);
597 1
598
			// Set the current board.
599 1
			if (!empty($row['id_board']))
600 1
				$board = (int) $row['id_board'];
601 1
602 1
			// Basic operating information. (globals... :/)
603 1
			$board_info = array(
604 1
				'id' => $board,
605 1
				'moderators' => array(),
606 1
				'cat' => array(
607 1
					'id' => $row['id_cat'],
608 1
					'name' => $row['cname']
609 1
				),
610 1
				'name' => $row['bname'],
611 1
				'raw_description' => $row['description'],
612 1
				'description' => $row['description'],
613 1
				'num_topics' => $row['num_topics'],
614 1
				'unapproved_topics' => $row['unapproved_topics'],
615 1
				'unapproved_posts' => $row['unapproved_posts'],
616 1
				'unapproved_user_topics' => 0,
617 1
				'parent_boards' => getBoardParents($row['id_parent']),
618 1
				'parent' => $row['id_parent'],
619 1
				'child_level' => $row['child_level'],
620
				'theme' => $row['id_theme'],
621
				'override_theme' => !empty($row['override_theme']),
622 1
				'profile' => $row['id_profile'],
623 1
				'redirect' => $row['redirect'],
624
				'posts_count' => empty($row['count_posts']),
625 1
				'cur_topic_approved' => empty($topic) || $row['approved'],
626
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
627
			);
628
629 1
			// Load the membergroups allowed, and check permissions.
630 1
			$board_info['groups'] = $row['member_groups'] == '' ? array() : explode(',', $row['member_groups']);
631
			$board_info['deny_groups'] = $row['deny_member_groups'] == '' ? array() : explode(',', $row['deny_member_groups']);
632
633
			call_integration_hook('integrate_loaded_board', array(&$board_info, &$row));
634
635
			do
636
			{
637 1
				if (!empty($row['id_moderator']))
638
					$board_info['moderators'][$row['id_moderator']] = array(
639
						'id' => $row['id_moderator'],
640
						'name' => $row['real_name'],
641 1
						'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
642 1
						'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
643
					);
644
			}
645
			while ($row = $db->fetch_assoc($request));
646
647
			// If the board only contains unapproved posts and the user can't approve then they can't see any topics.
648
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
649
			if ($board_info['num_topics'] == 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
650
			{
651
				// Free the previous result
652
				$db->free_result($request);
653
654
				// @todo why is this using id_topic?
655
				// @todo Can this get cached?
656
				$request = $db->query('', '
657
					SELECT COUNT(id_topic)
658
					FROM {db_prefix}topics
659
					WHERE id_member_started={int:id_member}
660
						AND approved = {int:unapproved}
661
						AND id_board = {int:board}',
662
					array(
663
						'id_member' => $user_info['id'],
664 1
						'unapproved' => 0,
665 1
						'board' => $board,
666
					)
667
				);
668
669
				list ($board_info['unapproved_user_topics']) = $db->fetch_row($request);
670
			}
671 1
672
			if ($cache->isEnabled() && (empty($topic) || $cache->levelHigherThan(2)))
673
			{
674
				// @todo SLOW?
675
				if (!empty($topic))
676
					$cache->put('topic_board-' . $topic, $board_info, 120);
677
				$cache->put('board-' . $board, $board_info, 120);
678
			}
679
		}
680
		else
681
		{
682 1
			// Otherwise the topic is invalid, there are no moderators, etc.
683 1
			$board_info = array(
684
				'moderators' => array(),
685 1
				'error' => 'exist'
686 1
			);
687
			$topic = null;
688 1
			$board = 0;
689 1
		}
690
		$db->free_result($request);
691 1
	}
692
693 1
	if (!empty($topic))
694 1
		$_GET['board'] = (int) $board;
695 1
696 1
	if (!empty($board))
697
	{
698
		// Now check if the user is a moderator.
699 1
		$user_info['is_mod'] = isset($board_info['moderators'][$user_info['id']]);
700 1
701
		if (count(array_intersect($user_info['groups'], $board_info['groups'])) == 0 && !$user_info['is_admin'])
702 1
			$board_info['error'] = 'access';
703 1
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect($user_info['groups'], $board_info['deny_groups'])) != 0 && !$user_info['is_admin'])
704 1
			$board_info['error'] = 'access';
705 1
706
		// Build up the linktree.
707 1
		$context['linktree'] = array_merge(
708 1
			$context['linktree'],
709 1
			array(array(
710 1
				'url' => $scripturl . $modSettings['default_forum_action'] . '#c' . $board_info['cat']['id'],
711 1
				'name' => $board_info['cat']['name']
712
			)),
713
			array_reverse($board_info['parent_boards']),
714 1
			array(array(
715 1
				'url' => $scripturl . '?board=' . $board . '.0',
716 1
				'name' => $board_info['name']
717 1
			))
718
		);
719
	}
720 1
721 1
	// Set the template contextual information.
722
	$context['user']['is_mod'] = &$user_info['is_mod'];
723
	$context['user']['is_moderator'] = &$user_info['is_moderator'];
724
	$context['current_topic'] = $topic;
725
	$context['current_board'] = $board;
726
727
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
728
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] != 'access' || !$user_info['is_moderator']))
729
	{
730
		// The permissions and theme need loading, just to make sure everything goes smoothly.
731
		loadPermissions();
732
		loadTheme();
733
734
		$_GET['board'] = '';
735
		$_GET['topic'] = '';
736
737
		// The linktree should not give the game away mate!
738
		$context['linktree'] = array(
739
			array(
740
				'url' => $scripturl,
741
				'name' => $context['forum_name_html_safe']
742
			)
743
		);
744
745
		// If it's a prefetching agent, stop it
746
		stop_prefetching();
747
748
		// If we're requesting an attachment.
749
		if (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach')
750
		{
751
			ob_end_clean();
752
			header('HTTP/1.1 403 Forbidden');
753
			exit;
754
		}
755
		elseif ($user_info['is_guest'])
756 1
		{
757 1
			loadLanguage('Errors');
758 1
			is_not_guest($txt['topic_gone']);
759
		}
760
		else
761
			throw new Elk_Exception('topic_gone', false);
762
	}
763
764
	if ($user_info['is_mod'])
765
		$user_info['groups'][] = 3;
766
}
767
768
/**
769
 * Load this user's permissions.
770
 *
771
 * What it does:
772
 *
773
 * - If the user is an admin, validate that they have not been banned.
774
 * - Attempt to load permissions from cache for cache level > 2
775
 * - See if the user is possibly a robot and apply added permissions as needed
776
 * - Load permissions from the general permissions table.
777
 * - If inside a board load the necessary board permissions.
778
 * - If the user is not a guest, identify what other boards they have access to.
779
 */
780
function loadPermissions()
781
{
782
	global $user_info, $board, $board_info, $modSettings;
783
784
	$db = database();
785
786
	if ($user_info['is_admin'])
787
	{
788
		banPermissions();
789
		return;
790
	}
791
792
	$removals = array();
793
794
	$cache = Cache::instance();
795
796
	if ($cache->isEnabled())
797
	{
798
		$cache_groups = $user_info['groups'];
799
		asort($cache_groups);
800
		$cache_groups = implode(',', $cache_groups);
801
802
		// If it's a spider then cache it different.
803
		if ($user_info['possibly_robot'])
804
			$cache_groups .= '-spider';
805
806
		$temp = array();
807
		if ($cache->levelHigherThan(1) && !empty($board) && $cache->getVar($temp, 'permissions:' . $cache_groups . ':' . $board, 240) && time() - 240 > $modSettings['settings_updated'])
808
		{
809
			list ($user_info['permissions']) = $temp;
810
			banPermissions();
811
812
			return;
813
		}
814
		elseif ($cache->getVar($temp, 'permissions:' . $cache_groups, 240) && time() - 240 > $modSettings['settings_updated'])
815
		{
816
			if (is_array($temp))
817
			{
818
				list ($user_info['permissions'], $removals) = $temp;
819
			}
820
		}
821
	}
822
823
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
824
	$spider_restrict = $user_info['possibly_robot'] && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
825
826
	if (empty($user_info['permissions']))
827
	{
828
		// Get the general permissions.
829
		$request = $db->query('', '
830
			SELECT 
831
				permission, add_deny
832
			FROM {db_prefix}permissions
833
			WHERE id_group IN ({array_int:member_groups})
834
				' . $spider_restrict,
835
			array(
836
				'member_groups' => $user_info['groups'],
837
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
838
			)
839
		);
840
		while ($row = $db->fetch_assoc($request))
841
		{
842
			if (empty($row['add_deny']))
843
				$removals[] = $row['permission'];
844
			else
845
				$user_info['permissions'][] = $row['permission'];
846
		}
847
		$db->free_result($request);
848
849
		if (isset($cache_groups))
850
			$cache->put('permissions:' . $cache_groups, array($user_info['permissions'], !empty($removals) ? $removals : array()), 2);
851
	}
852
853
	// Get the board permissions.
854
	if (!empty($board))
855
	{
856
		// Make sure the board (if any) has been loaded by loadBoard().
857
		if (!isset($board_info['profile']))
858
			throw new Elk_Exception('no_board');
859
860
		$request = $db->query('', '
861
			SELECT 
862
				permission, add_deny
863
			FROM {db_prefix}board_permissions
864
			WHERE (id_group IN ({array_int:member_groups})
865
				' . $spider_restrict . ')
866
				AND id_profile = {int:id_profile}',
867
			array(
868
				'member_groups' => $user_info['groups'],
869
				'id_profile' => $board_info['profile'],
870
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
871
			)
872
		);
873
		while ($row = $db->fetch_assoc($request))
874
		{
875
			if (empty($row['add_deny']))
876
				$removals[] = $row['permission'];
877
			else
878
				$user_info['permissions'][] = $row['permission'];
879
		}
880
		$db->free_result($request);
881
	}
882
883
	// Remove all the permissions they shouldn't have ;).
884
	if (!empty($modSettings['permission_enable_deny']))
885
		$user_info['permissions'] = array_diff($user_info['permissions'], $removals);
886
887
	if (isset($cache_groups) && !empty($board) && $cache->levelHigherThan(1))
888
		$cache->put('permissions:' . $cache_groups . ':' . $board, array($user_info['permissions'], null), 240);
889
890
	// Banned?  Watch, don't touch..
891
	banPermissions();
892
893
	// Load the mod cache so we can know what additional boards they should see, but no sense in doing it for guests
894
	if (!$user_info['is_guest'])
895
	{
896
		$user_info['is_moderator'] = $user_info['is_mod'] || allowedTo('moderate_board');
897
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
898
		{
899
			require_once(SUBSDIR . '/Auth.subs.php');
900
			rebuildModCache();
901
		}
902
		else
903
			$user_info['mod_cache'] = $_SESSION['mc'];
904
	}
905
}
906
907
/**
908
 * Loads an array of users' data by ID or member_name.
909
 *
910
 * @event integrate_load_member_data allows to add to the columns & tables for $user_profile
911
 * array population
912
 * @event integrate_add_member_data called after data is loaded, allows integration
913
 * to directly add to the user_profile array
914
 *
915 1
 * @param int[]|int|string[]|string $users An array of users by id or name
916
 * @param bool $is_name = false $users is by name or by id
917 1
 * @param string $set = 'normal' What kind of data to load (normal, profile, minimal)
918 1
 *
919
 * @return array|bool The ids of the members loaded or false
920
 */
921 1
function loadMemberData($users, $is_name = false, $set = 'normal')
922 1
{
923
	global $user_profile, $modSettings, $board_info, $context, $user_info;
924
925 1
	$db = database();
926
	$cache = Cache::instance();
927
928 1
	// Can't just look for no users :P.
929 1
	if (empty($users))
930
		return false;
931 1
932 1
	// Pass the set value
933
	$context['loadMemberContext_set'] = $set;
934
935
	// Make sure it's an array.
936
	$users = !is_array($users) ? array($users) : array_unique($users);
937
	$loaded_ids = array();
938
939
	if (!$is_name && $cache->isEnabled() && $cache->levelHigherThan(2))
940
	{
941
		$users = array_values($users);
942
		for ($i = 0, $n = count($users); $i < $n; $i++)
943
		{
944
			$data = $cache->get('member_data-' . $set . '-' . $users[$i], 240);
945
			if ($cache->isMiss())
946
				continue;
947
948
			$loaded_ids[] = $data['id_member'];
949
			$user_profile[$data['id_member']] = $data;
950
			unset($users[$i]);
951
		}
952
	}
953
954
	// Used by default
955 1
	$select_columns = '
956 1
			COALESCE(lo.log_time, 0) AS is_online, COALESCE(a.id_attach, 0) AS id_attach, a.filename, a.attachment_type,
957
			mem.signature, mem.avatar, mem.id_member, mem.member_name,
958
			mem.real_name, mem.email_address, mem.hide_email, mem.date_registered, mem.website_title, mem.website_url,
959
			mem.birthdate, mem.member_ip, mem.member_ip2, mem.posts, mem.last_login, mem.likes_given, mem.likes_received,
960
			mem.karma_good, mem.id_post_group, mem.karma_bad, mem.lngfile, mem.id_group, mem.time_offset, mem.show_online,
961 1
			mg.online_color AS member_group_color, COALESCE(mg.group_name, {string:blank_string}) AS member_group,
962
			pg.online_color AS post_group_color, COALESCE(pg.group_name, {string:blank_string}) AS post_group,
963
			mem.is_activated, mem.warning, ' . (!empty($modSettings['titlesEnable']) ? 'mem.usertitle, ' : '') . '
964
			CASE WHEN mem.id_group = 0 OR mg.icons = {string:blank_string} THEN pg.icons ELSE mg.icons END AS icons';
965
	$select_tables = '
966 1
			LEFT JOIN {db_prefix}log_online AS lo ON (lo.id_member = mem.id_member)
967
			LEFT JOIN {db_prefix}attachments AS a ON (a.id_member = mem.id_member)
968
			LEFT JOIN {db_prefix}membergroups AS pg ON (pg.id_group = mem.id_post_group)
969 1
			LEFT JOIN {db_prefix}membergroups AS mg ON (mg.id_group = mem.id_group)';
970
971
	// We add or replace according to the set
972
	switch ($set)
973 1
	{
974 1
		case 'normal':
975
			$select_columns .= ', mem.buddy_list';
976
			break;
977
		case 'profile':
978
			$select_columns .= ', mem.openid_uri, mem.id_theme, mem.pm_ignore_list, mem.pm_email_notify, mem.receive_from,
979
			mem.time_format, mem.secret_question, mem.additional_groups, mem.smiley_set,
980
			mem.total_time_logged_in, mem.notify_announcements, mem.notify_regularity, mem.notify_send_body,
981
			mem.notify_types, lo.url, mem.ignore_boards, mem.password_salt, mem.pm_prefs, mem.buddy_list, mem.otp_secret, mem.enable_otp';
982
			break;
983
		case 'minimal':
984
			$select_columns = '
985
			mem.id_member, mem.member_name, mem.real_name, mem.email_address, mem.hide_email, mem.date_registered,
986 1
			mem.posts, mem.last_login, mem.member_ip, mem.member_ip2, mem.lngfile, mem.id_group';
987
			$select_tables = '';
988 1
			break;
989 1
		default:
990
			trigger_error('loadMemberData(): Invalid member data set \'' . $set . '\'', E_USER_WARNING);
991 1
	}
992 1
993 1
	// Allow addons to easily add to the selected member data
994 1
	call_integration_hook('integrate_load_member_data', array(&$select_columns, &$select_tables, $set));
995
996 1
	if (!empty($users))
997 1
	{
998
		// Load the member's data.
999 1
		$request = $db->query('', '
1000 1
			SELECT' . $select_columns . '
1001 1
			FROM {db_prefix}members AS mem' . $select_tables . '
1002
			WHERE mem.' . ($is_name ? 'member_name' : 'id_member') . (count($users) == 1 ? ' = {' . ($is_name ? 'string' : 'int') . ':users}' : ' IN ({' . ($is_name ? 'array_string' : 'array_int') . ':users})'),
1003 1
			array(
1004 1
				'blank_string' => '',
1005 1
				'users' => count($users) == 1 ? current($users) : $users,
1006 1
			)
1007 1
		);
1008 1
		$new_loaded_ids = array();
1009 1
		while ($row = $db->fetch_assoc($request))
1010
		{
1011
			$new_loaded_ids[] = $row['id_member'];
1012 1
			$loaded_ids[] = $row['id_member'];
1013 1
			$row['options'] = array();
1014 1
			$user_profile[$row['id_member']] = $row;
1015
		}
1016
		$db->free_result($request);
1017
	}
1018 1
1019
	// Custom profile fields as well
1020 1
	if (!empty($new_loaded_ids) && !empty($user_info['id']) && $set !== 'minimal' && (in_array('cp', $context['admin_features'])))
1021
	{
1022 1
		$request = $db->query('', '
1023 1
			SELECT cfd.id_member, cfd.variable, cfd.value, cf.field_options, cf.field_type
1024
			FROM {db_prefix}custom_fields_data AS cfd
1025
			JOIN {db_prefix}custom_fields AS cf ON (cf.col_name = cfd.variable)
1026
			WHERE id_member' . (count($new_loaded_ids) == 1 ? ' = {int:loaded_ids}' : ' IN ({array_int:loaded_ids})'),
1027
			array(
1028
				'loaded_ids' => count($new_loaded_ids) == 1 ? $new_loaded_ids[0] : $new_loaded_ids,
1029
			)
1030
		);
1031
		while ($row = $db->fetch_assoc($request))
1032
		{
1033
			if (!empty($row['field_options']))
1034
			{
1035
				$field_options = explode(',', $row['field_options']);
1036
				$key = (int) array_search($row['value'], $field_options);
1037
			}
1038 1
			else
1039 1
			{
1040
				$key = 0;
1041
			}
1042 1
1043 1
			$user_profile[$row['id_member']]['options'][$row['variable'] . '_key'] = $row['variable'] . '_' . $key;
1044
			$user_profile[$row['id_member']]['options'][$row['variable']] = $row['value'];
1045 1
		}
1046 1
		$db->free_result($request);
1047
	}
1048
1049
	// Anything else integration may want to add to the user_profile array
1050
	if (!empty($new_loaded_ids))
1051
		call_integration_hook('integrate_add_member_data', array($new_loaded_ids, $set));
1052 1
1053 1
	if (!empty($new_loaded_ids) && $cache->levelHigherThan(2))
1054
	{
1055
		for ($i = 0, $n = count($new_loaded_ids); $i < $n; $i++)
1056
			$cache->put('member_data-' . $set . '-' . $new_loaded_ids[$i], $user_profile[$new_loaded_ids[$i]], 240);
1057
	}
1058
1059
	// Are we loading any moderators?  If so, fix their group data...
1060
	if (!empty($loaded_ids) && !empty($board_info['moderators']) && $set === 'normal' && count($temp_mods = array_intersect($loaded_ids, array_keys($board_info['moderators']))) !== 0)
1061
	{
1062
		$group_info = array();
1063
		if (!$cache->getVar($group_info, 'moderator_group_info', 480))
1064
		{
1065
			require_once(SUBSDIR . '/Membergroups.subs.php');
1066
			$group_info = membergroupById(3, true);
1067
1068
			$cache->put('moderator_group_info', $group_info, 480);
1069
		}
1070
1071
		foreach ($temp_mods as $id)
1072
		{
1073
			// By popular demand, don't show admins or global moderators as moderators.
1074
			if ($user_profile[$id]['id_group'] != 1 && $user_profile[$id]['id_group'] != 2)
1075
				$user_profile[$id]['member_group'] = $group_info['group_name'];
1076
1077 1
			// If the Moderator group has no color or icons, but their group does... don't overwrite.
1078
			if (!empty($group_info['icons']))
1079
				$user_profile[$id]['icons'] = $group_info['icons'];
1080
			if (!empty($group_info['online_color']))
1081
				$user_profile[$id]['member_group_color'] = $group_info['online_color'];
1082
		}
1083
	}
1084
1085
	return empty($loaded_ids) ? false : $loaded_ids;
1086
}
1087
1088
/**
1089
 * Loads the user's basic values... meant for template/theme usage.
1090
 *
1091
 * What it does:
1092
 *
1093
 * - Always loads the minimal values of username, name, id, href, link, email, show_email, registered, registered_timestamp
1094
 * - if $context['loadMemberContext_set'] is not minimal it will load in full a full set of user information
1095
 * - prepares signature for display (censoring if enabled)
1096
 * - loads in the members custom fields if any
1097
 * - prepares the users buddy list, including reverse buddy flags
1098
 *
1099 1
 * @event integrate_member_context allows to manipulate $memberContext[user]
1100 1
 * @param int $user
1101 1
 * @param bool $display_custom_fields = false
1102
 *
1103
 * @return boolean
1104 1
 */
1105 1
function loadMemberContext($user, $display_custom_fields = false)
1106
{
1107
	global $memberContext, $user_profile, $txt, $scripturl, $user_info;
1108 1
	global $context, $modSettings, $settings;
1109 1
	static $dataLoaded = array();
1110
1111 1
	// If this person's data is already loaded, skip it.
1112 1
	if (isset($dataLoaded[$user]))
1113
		return true;
1114
1115
	// We can't load guests or members not loaded by loadMemberData()!
1116
	if ($user == 0)
1117 1
		return false;
1118
1119
	if (!isset($user_profile[$user]))
1120 1
	{
1121 1
		trigger_error('loadMemberContext(): member id ' . $user . ' not previously loaded by loadMemberData()', E_USER_WARNING);
1122
		return false;
1123
	}
1124 1
1125
	$parsers = \BBC\ParserWrapper::instance();
1126
1127
	// Well, it's loaded now anyhow.
1128
	$dataLoaded[$user] = true;
1129 1
	$profile = $user_profile[$user];
1130 1
1131 1
	// Censor everything.
1132 1
	$profile['signature'] = censor($profile['signature']);
1133
1134
	// TODO: We should look into a censoring toggle for custom fields
1135 1
1136 1
	// Set things up to be used before hand.
1137
	$profile['signature'] = str_replace(array("\n", "\r"), array('<br />', ''), $profile['signature']);
1138
	$profile['signature'] = $parsers->parseSignature($profile['signature'], true);
1139 1
	$profile['is_online'] = (!empty($profile['show_online']) || allowedTo('moderate_forum')) && $profile['is_online'] > 0;
1140 1
	$profile['icons'] = empty($profile['icons']) ? array('', '') : explode('#', $profile['icons']);
1141 1
1142 1
	// Setup the buddy status here (One whole in_array call saved :P)
1143 1
	$profile['buddy'] = in_array($profile['id_member'], $user_info['buddies']);
1144 1
	$buddy_list = !empty($profile['buddy_list']) ? explode(',', $profile['buddy_list']) : array();
1145 1
1146 1
	// These minimal values are always loaded
1147 1
	$memberContext[$user] = array(
1148 1
		'username' => $profile['member_name'],
1149 1
		'name' => $profile['real_name'],
1150
		'id' => $profile['id_member'],
1151
		'href' => $scripturl . '?action=profile;u=' . $profile['id_member'],
1152
		'link' => '<a href="' . $scripturl . '?action=profile;u=' . $profile['id_member'] . '" title="' . $txt['profile_of'] . ' ' . trim($profile['real_name']) . '">' . $profile['real_name'] . '</a>',
1153 1
		'email' => $profile['email_address'],
1154 1
		'show_email' => showEmailAddress(!empty($profile['hide_email']), $profile['id_member']),
1155
		'registered_raw' => empty($profile['date_registered']) ? 0 : $profile['date_registered'],
1156 1
		'registered' => empty($profile['date_registered']) ? $txt['not_applicable'] : standardTime($profile['date_registered']),
1157 1
		'registered_timestamp' => empty($profile['date_registered']) ? 0 : forum_time(true, $profile['date_registered']),
1158 1
	);
1159 1
1160 1
	// If the set isn't minimal then load the monstrous array.
1161 1
	if ($context['loadMemberContext_set'] !== 'minimal')
1162 1
	{
1163
		$memberContext[$user] += array(
1164 1
			'username_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['member_name'] . '</span>',
1165 1
			'name_color' => '<span ' . (!empty($profile['member_group_color']) ? 'style="color:' . $profile['member_group_color'] . ';"' : '') . '>' . $profile['real_name'] . '</span>',
1166 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>',
1167 1
			'is_buddy' => $profile['buddy'],
1168 1
			'is_reverse_buddy' => in_array($user_info['id'], $buddy_list),
1169 1
			'buddies' => $buddy_list,
1170 1
			'title' => !empty($modSettings['titlesEnable']) ? $profile['usertitle'] : '',
1171 1
			'website' => array(
1172 1
				'title' => $profile['website_title'],
1173 1
				'url' => $profile['website_url'],
1174
			),
1175 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']),
1176 1
			'signature' => $profile['signature'],
1177 1
			'real_posts' => $profile['posts'],
1178 1
			'posts' => comma_format($profile['posts']),
1179 1
			'avatar' => determineAvatar($profile),
1180
			'last_login' => empty($profile['last_login']) ? $txt['never'] : standardTime($profile['last_login']),
1181 1
			'last_login_timestamp' => empty($profile['last_login']) ? 0 : forum_time(false, $profile['last_login']),
1182 1
			'karma' => array(
1183 1
				'good' => $profile['karma_good'],
1184 1
				'bad' => $profile['karma_bad'],
1185 1
				'allow' => !$user_info['is_guest'] && !empty($modSettings['karmaMode']) && $user_info['id'] != $user && allowedTo('karma_edit') &&
1186
				($user_info['posts'] >= $modSettings['karmaMinPosts'] || $user_info['is_admin']),
1187 1
			),
1188 1
			'likes' => array(
1189 1
				'given' => $profile['likes_given'],
1190 1
				'received' => $profile['likes_received']
1191 1
			),
1192 1
			'ip' => htmlspecialchars($profile['member_ip'], ENT_COMPAT, 'UTF-8'),
1193 1
			'ip2' => htmlspecialchars($profile['member_ip2'], ENT_COMPAT, 'UTF-8'),
1194 1
			'online' => array(
1195 1
				'is_online' => $profile['is_online'],
1196 1
				'text' => Util::htmlspecialchars($txt[$profile['is_online'] ? 'online' : 'offline']),
1197 1
				'member_online_text' => sprintf($txt[$profile['is_online'] ? 'member_is_online' : 'member_is_offline'], Util::htmlspecialchars($profile['real_name'])),
1198 1
				'href' => $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'],
1199 1
				'link' => '<a href="' . $scripturl . '?action=pm;sa=send;u=' . $profile['id_member'] . '">' . $txt[$profile['is_online'] ? 'online' : 'offline'] . '</a>',
1200 1
				'label' => $txt[$profile['is_online'] ? 'online' : 'offline']
1201 1
			),
1202 1
			'language' => Util::ucwords(strtr($profile['lngfile'], array('_' => ' '))),
1203 1
			'is_activated' => isset($profile['is_activated']) ? $profile['is_activated'] : 1,
1204 1
			'is_banned' => isset($profile['is_activated']) ? $profile['is_activated'] >= 10 : 0,
1205 1
			'options' => $profile['options'],
1206 1
			'is_guest' => false,
1207 1
			'group' => $profile['member_group'],
1208 1
			'group_color' => $profile['member_group_color'],
1209
			'group_id' => $profile['id_group'],
1210 1
			'post_group' => $profile['post_group'],
1211
			'post_group_color' => $profile['post_group_color'],
1212
			'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]),
1213 1
			'warning' => $profile['warning'],
1214 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' : (''))),
1215
			'local_time' => standardTime(time() + ($profile['time_offset'] - $user_info['time_offset']) * 3600, false),
1216
			'custom_fields' => array(),
1217
		);
1218
	}
1219
1220
	// Are we also loading the members custom fields into context?
1221
	if ($display_custom_fields && !empty($modSettings['displayFields']))
1222
	{
1223
		if (!isset($context['display_fields']))
1224
			$context['display_fields'] = Util::unserialize($modSettings['displayFields']);
1225
1226
		foreach ($context['display_fields'] as $custom)
1227
		{
1228
			if (!isset($custom['title']) || trim($custom['title']) == '' || empty($profile['options'][$custom['colname']]))
1229
				continue;
1230
1231
			$value = $profile['options'][$custom['colname']];
1232
1233
			// BBC?
1234
			if ($custom['bbc'])
1235
				$value = $parsers->parseCustomFields($value);
1236
			// ... or checkbox?
1237
			elseif (isset($custom['type']) && $custom['type'] == 'check')
1238
				$value = $value ? $txt['yes'] : $txt['no'];
1239
1240
			// Enclosing the user input within some other text?
1241
			if (!empty($custom['enclose']))
1242
			{
1243
				$replacements = array(
1244
					'{SCRIPTURL}' => $scripturl,
1245
					'{IMAGES_URL}' => $settings['images_url'],
1246
					'{DEFAULT_IMAGES_URL}' => $settings['default_images_url'],
1247
					'{INPUT}' => $value,
1248
				);
1249
1250
				if (in_array($custom['type'], array('radio', 'select')))
1251
				{
1252
					$replacements['{KEY}'] = $profile['options'][$custom['colname'] . '_key'];
1253
				}
1254
				$value = strtr($custom['enclose'], $replacements);
1255
			}
1256
1257
			$memberContext[$user]['custom_fields'][] = array(
1258 1
				'title' => $custom['title'],
1259 1
				'colname' => $custom['colname'],
1260
				'value' => $value,
1261
				'placement' => !empty($custom['placement']) ? $custom['placement'] : 0,
1262
			);
1263
		}
1264
	}
1265
1266
	call_integration_hook('integrate_member_context', array($user, $display_custom_fields));
1267
	return true;
1268
}
1269
1270 7
/**
1271 7
 * Loads information about what browser the user is viewing with and places it in $context
1272 7
 *
1273
 * @uses Browser_Detector class from BrowserDetect.class.php
1274
 */
1275
function detectBrowser()
1276
{
1277
	// Load the current user's browser of choice
1278
	$detector = new Browser_Detector;
1279
	$detector->detectBrowser();
1280
}
1281
1282 7
/**
1283
 * Get the id of a theme
1284
 *
1285 7
 * @param int $id_theme
1286 7
 * @return int
1287
 */
1288 7
function getThemeId($id_theme = 0)
1289
{
1290
	global $modSettings, $user_info, $board_info, $ssi_theme;
1291
1292
	// The theme was specified by parameter.
1293
	if (!empty($id_theme))
1294 7
		$id_theme = (int) $id_theme;
1295
	// The theme was specified by REQUEST.
1296
	elseif (!empty($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1297 7
	{
1298
		$id_theme = (int) $_REQUEST['theme'];
1299
		$_SESSION['id_theme'] = $id_theme;
1300 7
	}
1301
	// The theme was specified by REQUEST... previously.
1302
	elseif (!empty($_SESSION['id_theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1303
		$id_theme = (int) $_SESSION['id_theme'];
1304 7
	// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
1305
	elseif (!empty($user_info['theme']) && !isset($_REQUEST['theme']) && (!empty($modSettings['theme_allow']) || allowedTo('admin_forum')))
1306 7
		$id_theme = $user_info['theme'];
1307
	// The theme was specified by the board.
1308
	elseif (!empty($board_info['theme']))
1309
		$id_theme = $board_info['theme'];
1310 7
	// The theme is the forum's default.
1311 7
	else
1312
		$id_theme = $modSettings['theme_guests'];
1313 7
1314
	call_integration_hook('integrate_customize_theme_id', array(&$id_theme));
1315 7
1316
	// Verify the id_theme... no foul play.
1317 4
	// Always allow the board specific theme, if they are overriding.
1318 4
	if (!empty($board_info['theme']) && $board_info['override_theme'])
1319 4
		$id_theme = $board_info['theme'];
1320
	// If they have specified a particular theme to use with SSI allow it to be used.
1321 4
	elseif (!empty($ssi_theme) && $id_theme == $ssi_theme)
1322 4
		$id_theme = (int) $id_theme;
1323
	elseif (!empty($modSettings['knownThemes']) && !allowedTo('admin_forum'))
1324 3
	{
1325
		$themes = explode(',', $modSettings['knownThemes']);
1326 7
		if (!in_array($id_theme, $themes))
1327
			$id_theme = $modSettings['theme_guests'];
1328
		else
1329
			$id_theme = (int) $id_theme;
1330
	}
1331
	else
1332
		$id_theme = (int) $id_theme;
1333
1334
	return $id_theme;
1335
}
1336
1337
/**
1338
 * Load in the theme variables for a given theme / member combination
1339 7
 *
1340
 * @param int $id_theme
1341 7
 * @param int $member
1342
 *
1343
 * @return array
1344 7
 */
1345 7
function getThemeData($id_theme, $member)
1346 7
{
1347
	global $modSettings, $boardurl;
1348
1349
	$cache = Cache::instance();
1350
1351 7
	// Do we already have this members theme data and specific options loaded (for aggressive cache settings)
1352
	$temp = array();
1353
	if ($cache->levelHigherThan(1) && $cache->getVar($temp, 'theme_settings-' . $id_theme . ':' . $member, 60) && time() - 60 > $modSettings['settings_updated'])
1354
	{
1355 7
		$themeData = $temp;
1356
		$flag = true;
1357 7
	}
1358 7
	// Or do we just have the system wide theme settings cached
1359 7
	elseif ($cache->getVar($temp, 'theme_settings-' . $id_theme, 90) && time() - 60 > $modSettings['settings_updated'])
1360
		$themeData = $temp + array($member => array());
1361
	// Nothing at all then
1362 7
	else
1363
		$themeData = array(-1 => array(), 0 => array(), $member => array());
1364
1365 7
	if (empty($flag))
1366 7
	{
1367
		$db = database();
1368 7
1369 7
		// Load variables from the current or default theme, global or this user's.
1370
		$result = $db->query('', '
1371 7
			SELECT variable, value, id_member, id_theme
1372
			FROM {db_prefix}themes
1373 7
			WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
1374
				AND id_theme' . ($id_theme == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
1375
			array(
1376 7
				'id_theme' => $id_theme,
1377
				'id_member' => $member,
1378
			)
1379 7
		);
1380 7
1381
		$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');
1382
1383 7
		// Pick between $settings and $options depending on whose data it is.
1384 7
		while ($row = $db->fetch_assoc($result))
1385
		{
1386
			// There are just things we shouldn't be able to change as members.
1387 7
			if ($row['id_member'] != 0 && in_array($row['variable'], $immutable_theme_data))
1388 7
				continue;
1389 7
1390 7
			// If this is the theme_dir of the default theme, store it.
1391
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1' && empty($row['id_member']))
1392 7
				$themeData[0]['default_' . $row['variable']] = $row['value'];
1393 7
1394 7
			// If this isn't set yet, is a theme option, or is not the default theme..
1395 7
			if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != '1')
1396 7
				$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) == 'show_' ? $row['value'] == '1' : $row['value'];
1397
		}
1398
		$db->free_result($result);
1399
1400
		if (file_exists($themeData[0]['default_theme_dir'] . '/cache') && is_writable($themeData[0]['default_theme_dir'] . '/cache'))
1401
		{
1402
			$themeData[0]['default_theme_cache_dir'] = $themeData[0]['default_theme_dir'] . '/cache';
1403
			$themeData[0]['default_theme_cache_url'] = $themeData[0]['default_theme_url'] . '/cache';
1404 7
		}
1405 7
		else
1406 7
		{
1407
			$themeData[0]['default_theme_cache_dir'] = CACHEDIR;
1408 7
			$themeData[0]['default_theme_cache_url'] = $boardurl . '/cache';
1409 7
		}
1410 7
1411 7
		// Set the defaults if the user has not chosen on their own
1412
		if (!empty($themeData[-1]))
1413
		{
1414 7
			foreach ($themeData[-1] as $k => $v)
1415 7
			{
1416
				if (!isset($themeData[$member][$k]))
1417 7
					$themeData[$member][$k] = $v;
1418 7
			}
1419 7
		}
1420
1421 7
		// If being aggressive we save the site wide and member theme settings
1422
		if ($cache->levelHigherThan(1))
1423
			$cache->put('theme_settings-' . $id_theme . ':' . $member, $themeData, 60);
1424
		// Only if we didn't already load that part of the cache...
1425
		elseif (!isset($temp))
1426
			$cache->put('theme_settings-' . $id_theme, array(-1 => $themeData[-1], 0 => $themeData[0]), 90);
1427
	}
1428
1429
	return $themeData;
1430
}
1431 7
1432
/**
1433
 * Initialize a theme for use
1434 7
 *
1435
 * @param int $id_theme
1436
 */
1437 7
function initTheme($id_theme = 0)
1438
{
1439
	global $user_info, $settings, $options, $context;
1440 7
1441 7
	// Validate / fetch the themes id
1442 7
	$id_theme = getThemeId($id_theme);
1443
1444 7
	// Need to know who we are loading the theme for
1445 7
	$member = empty($user_info['id']) ? -1 : $user_info['id'];
1446 7
1447 7
	// Load in the theme variables for them
1448
	$themeData = getThemeData($id_theme, $member);
1449
	$settings = $themeData[0];
1450 7
	$options = $themeData[$member];
1451
1452
	$settings['theme_id'] = $id_theme;
1453 7
	$settings['actual_theme_url'] = $settings['theme_url'];
1454 7
	$settings['actual_images_url'] = $settings['images_url'];
1455
	$settings['actual_theme_dir'] = $settings['theme_dir'];
1456
1457 7
	// Reload the templates
1458 7
	Templates::instance()->reloadDirectories($settings);
1459
1460
	// Setup the default theme file. In the future, this won't exist and themes will just have to extend it if they want
1461
	require_once($settings['default_theme_dir'] . '/Theme.php');
1462
	$default_theme_instance = new \Themes\DefaultTheme\Theme(1);
1463
1464
	// Check if there is a Theme file
1465
	if ($id_theme != 1 && !empty($settings['theme_dir']) && file_exists($settings['theme_dir'] . '/Theme.php'))
1466
	{
1467
		require_once($settings['theme_dir'] . '/Theme.php');
1468
1469 7
		$class = '\\Themes\\' . basename(ucfirst($settings['theme_dir'])) . 'Theme\\Theme';
1470
1471 7
		$theme = new $class($id_theme);
1472
1473
		$context['theme_instance'] = $theme;
1474
	}
1475
	else
1476
	{
1477
		$context['theme_instance'] = $default_theme_instance;
1478
	}
1479
}
1480
1481
/**
1482
 * Load a theme, by ID.
1483
 *
1484
 * What it does:
1485
 *
1486
 * - identify the theme to be loaded.
1487
 * - validate that the theme is valid and that the user has permission to use it
1488
 * - load the users theme settings and site settings into $options.
1489
 * - prepares the list of folders to search for template loading.
1490
 * - identify what smiley set to use.
1491
 * - sets up $context['user']
1492
 * - detects the users browser and sets a mobile friendly environment if needed
1493
 * - loads default JS variables for use in every theme
1494
 * - loads default JS scripts for use in every theme
1495
 *
1496
 * @event integrate_init_theme used to call initialization theme integration functions and
1497 7
 * change / update $settings
1498 7
 * @event integrate_theme_include allows to include files at this point
1499 7
 * @event integrate_load_theme calls functions after theme is loaded
1500
 * @param int $id_theme = 0
1501 7
 * @param bool $initialize = true
1502
 */
1503 7
function loadTheme($id_theme = 0, $initialize = true)
1504 7
{
1505
	global $user_info, $user_settings;
1506 7
	global $txt, $scripturl, $mbname, $modSettings;
1507
	global $context, $settings, $options;
1508 7
1509
	initTheme($id_theme);
1510
1511 7
	if (!$initialize)
1512 7
		return;
1513
1514
	loadThemeUrls();
1515
1516
	loadUserContext();
1517
1518
	// Set up some additional interface preference context
1519
	if (!empty($options['admin_preferences']))
1520
	{
1521
		$context['admin_preferences'] = serializeToJson($options['admin_preferences'], function ($array_form) {
1522
			global $context;
1523 7
1524
			$context['admin_preferences'] = $array_form;
1525
			require_once(SUBSDIR . '/Admin.subs.php');
1526 7
			updateAdminPreferences();
1527 7
		});
1528 3
	}
1529 3
	else
1530
	{
1531
		$context['admin_preferences'] = array();
1532
	}
1533
1534
	if (!$user_info['is_guest'])
1535
	{
1536
		if (!empty($options['minmax_preferences']))
1537
		{
1538
			$context['minmax_preferences'] = serializeToJson($options['minmax_preferences'], function ($array_form) {
1539
				global $settings, $user_info;
1540 3
1541
				// Update the option.
1542 3
				require_once(SUBSDIR . '/Themes.subs.php');
1543
				updateThemeOptions(array($settings['theme_id'], $user_info['id'], 'minmax_preferences', json_encode($array_form)));
1544 4
			});
1545
		}
1546
		else
1547
		{
1548 7
			$context['minmax_preferences'] = array();
1549
		}
1550
	}
1551 7
	// Guest may have collapsed the header, check the cookie to prevent collapse jumping
1552
	elseif ($user_info['is_guest'] && isset($_COOKIE['upshrink']))
1553
		$context['minmax_preferences'] = array('upshrink' => $_COOKIE['upshrink']);
1554 7
1555 7
	// Load the basic layers
1556 7
	theme()->loadDefaultLayers();
1557 7
1558 7
	// @todo when are these set before loadTheme(0, true)?
1559 7
	loadThemeContext();
1560
1561
	// @todo These really don't belong in loadTheme() since they are more general than the theme.
1562 7
	$context['session_var'] = $_SESSION['session_var'];
1563 7
	$context['session_id'] = $_SESSION['session_value'];
1564 4
	$context['forum_name'] = $mbname;
1565 4
	$context['forum_name_html_safe'] = $context['forum_name'];
1566 4
	$context['current_action'] = isset($_REQUEST['action']) ? $_REQUEST['action'] : null;
1567 4
	$context['current_subaction'] = isset($_REQUEST['sa']) ? $_REQUEST['sa'] : null;
1568
1569
	// Set some permission related settings.
1570 7
	if ($user_info['is_guest'] && !empty($modSettings['enableVBStyleLogin']))
1571
	{
1572
		$context['show_login_bar'] = true;
1573 7
		$context['theme_header_callbacks'][] = 'login_bar';
1574
		loadJavascriptFile('sha256.js', array('defer' => true));
1575
	}
1576 7
1577 7
	// This determines the server... not used in many places, except for login fixing.
1578 7
	detectServer();
1579 7
1580
	// Detect the browser. This is separated out because it's also used in attachment downloads
1581
	detectBrowser();
1582 7
1583 7
	// Set the top level linktree up.
1584
	array_unshift($context['linktree'], array(
1585
		'url' => $scripturl,
1586
		'name' => $context['forum_name']
1587
	));
1588
1589
	// Just some mobile-friendly settings
1590
	if ($context['browser_body_id'] == 'mobile')
1591
	{
1592
		// Disable the preview text.
1593
		$modSettings['message_index_preview'] = 0;
1594 7
		// Force the usage of click menu instead of a hover menu.
1595 7
		$options['use_click_menu'] = 1;
1596
		// No space left for a sidebar
1597
		$options['use_sidebar_menu'] = false;
1598 7
		// Disable the search dropdown.
1599
		$modSettings['search_dropdown'] = false;
1600
	}
1601 7
1602 7
	if (!isset($txt))
1603
		$txt = array();
1604
1605 7
	// Defaults in case of odd things
1606
	$settings['avatars_on_indexes'] = 0;
1607
1608 7
	// Initialize the theme.
1609 7
	if (function_exists('template_init'))
1610
		$settings = array_merge($settings, template_init());
1611
1612 7
	// Call initialization theme integration functions.
1613 7
	call_integration_hook('integrate_init_theme', array($id_theme, &$settings));
1614
1615 7
	// Guests may still need a name.
1616
	if ($context['user']['is_guest'] && empty($context['user']['name']))
1617
		$context['user']['name'] = $txt['guest_title'];
1618 7
1619 7
	// Any theme-related strings that need to be loaded?
1620 7
	if (!empty($settings['require_theme_strings']))
1621 7
		loadLanguage('ThemeStrings', '', false);
1622
1623
	theme()->loadSupportCSS();
1624 7
1625 7
	// We allow theme variants, because we're cool.
1626
	if (!empty($settings['theme_variants']))
1627
	{
1628 7
		theme()->loadThemeVariant();
1629 7
	}
1630
1631 7
	// A bit lonely maybe, though I think it should be set up *after* the theme variants detection
1632 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']);
1633
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1634
1635
	// Allow overriding the board wide time/number formats.
1636
	if (empty($user_settings['time_format']) && !empty($txt['time_format']))
1637
		$user_info['time_format'] = $txt['time_format'];
1638
1639 7
	if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'always')
1640
	{
1641
		$settings['theme_url'] = $settings['default_theme_url'];
1642 7
		$settings['images_url'] = $settings['default_images_url'];
1643 7
		$settings['theme_dir'] = $settings['default_theme_dir'];
1644
	}
1645 7
1646 7
	// Make a special URL for the language.
1647
	$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $user_info['language']);
1648
1649 7
	// RTL languages require an additional stylesheet.
1650
	if ($context['right_to_left'])
1651 7
		loadCSSFile('rtl.css');
1652 7
1653 7
	if (!empty($context['theme_variant']) && $context['right_to_left'])
1654 7
		loadCSSFile($context['theme_variant'] . '/rtl' . $context['theme_variant'] . '.css');
1655 7
1656
	// This allows us to change the way things look for the admin.
1657 7
	$context['admin_features'] = explode(',', isset($modSettings['admin_features']) ? $modSettings['admin_features'] : 'cd,cp,k,w,rg,ml,pm');
1658 7
1659
	if (!empty($modSettings['xmlnews_enable']) && (!empty($modSettings['allow_guestAccess']) || $context['user']['is_logged']))
1660
		$context['newsfeed_urls'] = array(
1661
			'rss' => $scripturl . '?action=.xml;type=rss2;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5),
1662
			'atom' => $scripturl . '?action=.xml;type=atom;limit=' . (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5)
1663
		);
1664
1665
	if (!empty($_SESSION['agreement_accepted']))
1666
	{
1667 7
		$_SESSION['agreement_accepted'] = null;
1668 7
		$context['accepted_agreement'] = array(
1669
			'errors' => array(
1670
				'accepted_agreement' => $txt['agreement_accepted']
1671
			)
1672
		);
1673
	}
1674
1675
	if (!empty($_SESSION['privacypolicy_accepted']))
1676
	{
1677 7
		$_SESSION['privacypolicy_accepted'] = null;
1678
		$context['accepted_agreement'] = array(
1679 7
			'errors' => array(
1680
				'accepted_privacy_policy' => $txt['privacypolicy_accepted']
1681
			)
1682 7
		);
1683
	}
1684
1685 7
	theme()->loadThemeJavascript();
1686
1687
	Hooks::instance()->newPath(array('$themedir' => $settings['theme_dir']));
1688 7
1689 7
	// Any files to include at this point?
1690
	call_integration_include_hook('integrate_theme_include');
1691
1692
	// Call load theme integration functions.
1693
	call_integration_hook('integrate_load_theme');
1694
1695
	// We are ready to go.
1696
	$context['theme_loaded'] = true;
1697
}
1698 7
1699
/**
1700
 * Detects url and checks against expected boardurl
1701 7
 *
1702 7
 * Attempts to correct improper URL's
1703
 */
1704
function loadThemeUrls()
1705
{
1706
	global $scripturl, $boardurl, $modSettings;
1707
1708
	// Check to see if they're accessing it from the wrong place.
1709
	if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
1710 7
	{
1711 7
		$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) == 'on' ? 'https://' : 'http://';
1712
		$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
1713
		$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '', strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
1714
		if ($temp != '/')
1715
			$detected_url .= $temp;
1716
	}
1717
1718
	if (isset($detected_url) && $detected_url != $boardurl)
1719
	{
1720
		// Try #1 - check if it's in a list of alias addresses.
1721
		if (!empty($modSettings['forum_alias_urls']))
1722
		{
1723
			$aliases = explode(',', $modSettings['forum_alias_urls']);
1724
			foreach ($aliases as $alias)
1725
			{
1726
				// Rip off all the boring parts, spaces, etc.
1727
				if ($detected_url == trim($alias) || strtr($detected_url, array('http://' => '', 'https://' => '')) == trim($alias))
1728
					$do_fix = true;
1729
			}
1730
		}
1731
1732
		// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
1733
		if (empty($do_fix) && strtr($detected_url, array('://' => '://www.')) == $boardurl && (empty($_GET) || count($_GET) == 1) && ELK != 'SSI')
0 ignored issues
show
introduced by
The condition ELK != 'SSI' is always false.
Loading history...
1734
		{
1735
			// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
1736
			if (empty($_GET))
1737
				redirectexit('wwwRedirect');
1738
			else
1739
			{
1740
				if (key($_GET) !== 'wwwRedirect')
1741
					redirectexit('wwwRedirect;' . key($_GET) . '=' . current($_GET));
1742
			}
1743
		}
1744
1745
		// #3 is just a check for SSL...
1746
		if (strtr($detected_url, array('https://' => 'http://')) == $boardurl)
1747 7
			$do_fix = true;
1748
1749
		// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
1750
		if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~', $detected_url) == 1)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $detected_url does not seem to be defined for all execution paths leading up to this point.
Loading history...
1751
		{
1752
			fixThemeUrls($detected_url);
1753
		}
1754 7
	}
1755
}
1756
1757
/**
1758 7
 * Loads various theme related settings into context and sets system wide theme defaults
1759 7
 */
1760 7
function loadThemeContext()
1761 7
{
1762 7
	global $context, $settings, $modSettings, $txt;
1763 7
1764 7
	// Some basic information...
1765 7
	$init = array(
1766 7
		'html_headers' => '',
1767
		'links' => array(),
1768 7
		'css_files' => array(),
1769 7
		'javascript_files' => array(),
1770
		'css_rules' => array(),
1771
		'javascript_inline' => array('standard' => array(), 'defer' => array()),
1772 7
		'javascript_vars' => array(),
1773 7
	);
1774
	foreach ($init as $area => $value)
1775 7
	{
1776 7
		$context[$area] = isset($context[$area]) ? $context[$area] : $value;
1777
	}
1778 7
1779 7
	// Set a couple of bits for the template.
1780
	$context['right_to_left'] = !empty($txt['lang_rtl']);
1781 7
	$context['tabindex'] = 1;
1782
1783 7
	$context['theme_variant'] = '';
1784 7
	$context['theme_variant_url'] = '';
1785
1786
	$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
1787 7
	$context['can_register'] = empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
1788
1789
	foreach (array('theme_header', 'upper_content') as $call)
1790 7
	{
1791 7
		if (!isset($context[$call . '_callbacks']))
1792
		{
1793
			$context[$call . '_callbacks'] = array();
1794
		}
1795
	}
1796
1797
	// This allows sticking some HTML on the page output - useful for controls.
1798 7
	$context['insert_after_template'] = '';
1799
}
1800
1801 7
/**
1802 7
 * Loads basic user information in to $context['user']
1803 7
 */
1804 7
function loadUserContext()
1805 7
{
1806 7
	global $context, $user_info, $txt, $modSettings;
1807 7
1808
	// Set up the contextual user array.
1809 7
	$context['user'] = array(
1810 7
		'id' => $user_info['id'],
1811 7
		'is_logged' => !$user_info['is_guest'],
1812 7
		'is_guest' => &$user_info['is_guest'],
1813 7
		'is_admin' => &$user_info['is_admin'],
1814
		'is_mod' => &$user_info['is_mod'],
1815
		'is_moderator' => &$user_info['is_moderator'],
1816
		// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
1817 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'])))),
1818 7
		'username' => $user_info['username'],
1819 3
		'language' => $user_info['language'],
1820 3
		'email' => $user_info['email'],
1821 4
		'ignoreusers' => $user_info['ignoreusers'],
1822
	);
1823 4
1824 4
	// Something for the guests
1825
	if (!$context['user']['is_guest'])
1826 7
	{
1827 7
		$context['user']['name'] = $user_info['name'];
1828 7
	}
1829 7
	elseif ($context['user']['is_guest'] && !empty($txt['guest_title']))
1830
	{
1831
		$context['user']['name'] = $txt['guest_title'];
1832
	}
1833
1834
	$context['user']['smiley_set'] = determineSmileySet($user_info['smiley_set'], $modSettings['smiley_sets_known']);
1835
	$context['smiley_enabled'] = $user_info['smiley_set'] !== 'none';
1836
	$context['user']['smiley_path'] = $modSettings['smileys_url'] . '/' . $context['user']['smiley_set'] . '/';
1837
}
1838
1839
/**
1840
 * Called if the detected URL is not the same as boardurl but is a common
1841
 * variation in which case it updates key system variables so it works.
1842
 *
1843
 * @param string $detected_url
1844
 */
1845
function fixThemeUrls($detected_url)
1846
{
1847
	global $boardurl, $scripturl, $settings, $modSettings, $context, $board_info;
1848
1849
	// Caching is good ;).
1850
	$oldurl = $boardurl;
1851
1852
	// Fix $boardurl and $scripturl.
1853
	$boardurl = $detected_url;
1854
	$scripturl = strtr($scripturl, array($oldurl => $boardurl));
1855
	$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
1856
1857
	// Fix the theme urls...
1858
	$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
1859
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
1860
	$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
1861
	$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
1862
	$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
1863
	$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
1864
1865
	// And just a few mod settings :).
1866
	$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
1867
	$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
1868
1869
	// Clean up after loadBoard().
1870
	if (isset($board_info['moderators']))
1871
	{
1872
		foreach ($board_info['moderators'] as $k => $dummy)
1873
		{
1874
			$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
1875
			$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
1876
		}
1877
	}
1878
1879
	foreach ($context['linktree'] as $k => $dummy)
1880
		$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
1881
}
1882
1883
/**
1884
 * Determine the current user's smiley set
1885 7
 *
1886
 * @param mixed[] $user_smiley_set
1887 7
 * @param mixed[] $known_smiley_sets
1888 7
 *
1889 7
 * @return mixed
1890 7
 */
1891
function determineSmileySet($user_smiley_set, $known_smiley_sets)
1892
{
1893
	global $modSettings, $settings;
1894
1895
	if ((!in_array($user_smiley_set, explode(',', $known_smiley_sets)) && $user_smiley_set !== 'none') || empty($modSettings['smiley_sets_enable']))
0 ignored issues
show
Bug introduced by
$known_smiley_sets of type array<mixed,mixed> is incompatible with the type string expected by parameter $string of explode(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1895
	if ((!in_array($user_smiley_set, explode(',', /** @scrutinizer ignore-type */ $known_smiley_sets)) && $user_smiley_set !== 'none') || empty($modSettings['smiley_sets_enable']))
Loading history...
1896 7
	{
1897
		$set = !empty($settings['smiley_sets_default']) ? $settings['smiley_sets_default'] : $modSettings['smiley_sets_default'];
1898
	}
1899
	else
1900
	{
1901
		$set = $user_smiley_set;
1902
	}
1903
1904
	return $set;
1905
}
1906
1907 2
/**
1908
 * This loads the bare minimum data.
1909 2
 *
1910 2
 * - Needed by scheduled tasks,
1911
 * - Needed by any other code that needs language files before the forum (the theme) is loaded.
1912
 */
1913
function loadEssentialThemeData()
1914 2
{
1915
	global $settings, $modSettings, $mbname, $context;
1916
1917 2
	if (function_exists('database') === false)
1918
	{
1919
		throw new \Exception('');
1920
	}
1921 2
1922
	$db = database();
1923 2
1924 2
	// Get all the default theme variables.
1925 2
	$db->fetchQueryCallback('
1926
		SELECT id_theme, variable, value
1927
		FROM {db_prefix}themes
1928 2
		WHERE id_member = {int:no_member}
1929
			AND id_theme IN (1, {int:theme_guests})',
1930 2
		array(
1931
			'no_member' => 0,
1932
			'theme_guests' => $modSettings['theme_guests'],
1933 2
		),
1934 2
		function ($row)
0 ignored issues
show
Bug introduced by
function(...) { /* ... */ } of type callable is incompatible with the type object|string expected by parameter $callback of Database::fetchQueryCallback(). ( Ignorable by Annotation )

If this is a false-positive, you can also ignore this issue in your code via the ignore-type  annotation

1934
		/** @scrutinizer ignore-type */ function ($row)
Loading history...
1935 2
		{
1936 2
			global $settings;
1937
1938
			$settings[$row['variable']] = $row['value'];
1939 2
1940 2
			// Is this the default theme?
1941
			if (in_array($row['variable'], array('theme_dir', 'theme_url', 'images_url')) && $row['id_theme'] == '1')
1942
				$settings['default_' . $row['variable']] = $row['value'];
1943
		}
1944
	);
1945 2
1946 2
	// Check we have some directories setup.
1947
	if (!Templates::instance()->hasDirectories())
1948 2
	{
1949 2
		Templates::instance()->reloadDirectories($settings);
1950
	}
1951
1952
	// Assume we want this.
1953
	$context['forum_name'] = $mbname;
1954
	$context['forum_name_html_safe'] = $context['forum_name'];
1955
1956
	loadLanguage('index+Addons');
1957
}
1958
1959
/**
1960
 * Load a template - if the theme doesn't include it, use the default.
1961
 *
1962
 * What it does:
1963
 *
1964
 * - loads a template file with the name template_name from the current, default, or base theme.
1965
 * - detects a wrong default theme directory and tries to work around it.
1966
 * - can be used to only load style sheets by using false as the template name
1967
 *   loading of style sheets with this function is deprecated, use loadCSSFile instead
1968
 * - if $settings['template_dirs'] is empty, it delays the loading of the template
1969
 *
1970
 * @uses the requireTemplate() function to actually load the file.
1971
 * @param string|false $template_name
1972 13
 * @param string[]|string $style_sheets any style sheets to load with the template
1973
 * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
1974
 *
1975
 * @return boolean|null
1976
 * @throws Elk_Exception
1977
 */
1978
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
1979
{
1980
	return Templates::instance()->load($template_name, $style_sheets, $fatal);
0 ignored issues
show
Bug introduced by
Are you sure the usage of Templates::instance()->l... $style_sheets, $fatal) targeting Templates::load() seems to always return null.

This check looks for function or method calls that always return null and whose return value is used.

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

}

$a = new A();
if ($a->getObject()) {

The method getObject() can return nothing but null, so it makes no sense to use the return value.

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

Loading history...
1981
}
1982
1983
/**
1984
 * Load a sub-template.
1985
 *
1986
 * What it does:
1987
 *
1988
 * - loads the sub template specified by sub_template_name, which must be in an already-loaded template.
1989
 * - if ?debug is in the query string, shows administrators a marker after every sub template
1990
 * for debugging purposes.
1991
 *
1992
 * @param string $sub_template_name
1993
 * @param bool|string $fatal = false
1994
 * - $fatal = true is for templates that shouldn't get a 'pretty' error screen
1995
 * - $fatal = 'ignore' to skip
1996
 * @throws Elk_Exception
1997
 */
1998
function loadSubTemplate($sub_template_name, $fatal = false)
1999
{
2000
	Templates::instance()->loadSubTemplate($sub_template_name, $fatal);
2001
2002
	return true;
2003
}
2004
2005
/**
2006
 * Add a CSS file for output later
2007
 *
2008
 * @param string[]|string $filenames string or array of filenames to work on
2009
 * @param mixed[] $params = array()
2010
 * Keys are the following:
2011
 * - ['local'] (true/false): define if the file is local
2012 7
 * - ['fallback'] (true/false): if false  will attempt to load the file
2013
 *   from the default theme if not found in the current theme
2014 7
 * - ['stale'] (true/false/string): if true or null, use cache stale,
2015 7
 *   false do not, or used a supplied string
2016
 * @param string $id optional id to use in html id=""
2017 7
 */
2018 7
function loadCSSFile($filenames, $params = array(), $id = '')
2019
{
2020 7
	global $context;
2021 7
2022
	if (empty($filenames))
2023 7
		return;
2024 7
2025 7
	if (!is_array($filenames))
2026 7
		$filenames = array($filenames);
2027
2028 7
	if (in_array('admin.css', $filenames))
2029 7
		$filenames[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css';
2030
2031
	$params['subdir'] = isset($params['subdir']) ? $params['subdir'] : 'css';
2032
	$params['extension'] = 'css';
2033
	$params['index_name'] = 'css_files';
2034
	$params['debug_index'] = 'sheets';
2035
2036
	loadAssetFile($filenames, $params, $id);
2037
}
2038
2039
/**
2040
 * Add a Javascript file for output later
2041
 *
2042
 * What it does:
2043
 *
2044
 * - Can be passed an array of filenames, all which will have the same
2045
 *   parameters applied,
2046
 * - if you need specific parameters on a per file basis, call it multiple times
2047
 *
2048
 * @param string[]|string $filenames string or array of filenames to work on
2049
 * @param mixed[] $params = array()
2050
 * Keys are the following:
2051
 * - ['local'] (true/false): define if the file is local, if file does not
2052
 *     start with http its assumed local
2053
 * - ['defer'] (true/false): define if the file should load in <head> or before
2054
 *     the closing <html> tag
2055
 * - ['fallback'] (true/false): if true will attempt to load the file from the
2056
 *     default theme if not found in the current this is the default behavior
2057 7
 *     if this is not supplied
2058 7
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2059
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
2060 7
 *     not, or used a supplied string
2061 7
 * @param string $id = '' optional id to use in html id=""
2062 7
 */
2063 7
function loadJavascriptFile($filenames, $params = array(), $id = '')
2064
{
2065 7
	if (empty($filenames))
2066 7
		return;
2067
2068
	$params['subdir'] = isset($params['subdir']) ? $params['subdir'] : 'scripts';
2069
	$params['extension'] = 'js';
2070
	$params['index_name'] = 'js_files';
2071
	$params['debug_index'] = 'javascript';
2072
2073
	loadAssetFile($filenames, $params, $id);
2074
}
2075
2076
/**
2077
 * Add an asset (css, js or other) file for output later
2078
 *
2079
 * What it does:
2080
 *
2081
 * - Can be passed an array of filenames, all which will have the same
2082
 *   parameters applied,
2083
 * - If you need specific parameters on a per file basis, call it multiple times
2084
 *
2085
 * @param string[]|string $filenames string or array of filenames to work on
2086
 * @param mixed[] $params = array()
2087
 * Keys are the following:
2088
 * - ['subdir'] (string): the subdirectory of the theme dir the file is in
2089
 * - ['extension'] (string): the extension of the file (e.g. css)
2090
 * - ['index_name'] (string): the $context index that holds the array of loaded
2091
 *     files
2092
 * - ['debug_index'] (string): the index that holds the array of loaded
2093
 *     files for debugging debug
2094
 * - ['local'] (true/false): define if the file is local, if file does not
2095
 *     start with http or // (schema-less URLs) its assumed local.
2096
 *     The parameter is in fact useful only for files whose name starts with
2097
 *     http, in any other case (e.g. passing a local URL) the parameter would
2098
 *     fail in properly adding the file to the list.
2099
 * - ['defer'] (true/false): define if the file should load in <head> or before
2100
 *     the closing <html> tag
2101
 * - ['fallback'] (true/false): if true will attempt to load the file from the
2102
 *     default theme if not found in the current this is the default behavior
2103 7
 *     if this is not supplied
2104
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
2105 7
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
2106 7
 *     not, or used a supplied string
2107
 * @param string $id = '' optional id to use in html id=""
2108 7
 */
2109
function loadAssetFile($filenames, $params = array(), $id = '')
2110 7
{
2111 7
	global $settings, $context, $db_show_debug;
2112
2113
	if (empty($filenames))
2114 7
		return;
2115 7
2116
	$cache = Cache::instance();
2117
2118
	if (!is_array($filenames))
2119
		$filenames = array($filenames);
2120
2121 7
	// Static values for all these settings
2122 7
	if (!isset($params['stale']) || $params['stale'] === true)
2123
		$staler_string = CACHE_STALE;
2124
	elseif (is_string($params['stale']))
2125 7
		$staler_string = ($params['stale'][0] === '?' ? $params['stale'] : '?' . $params['stale']);
2126 7
	else
2127 7
		$staler_string = '';
2128 7
2129
	$fallback = (!empty($params['fallback']) && ($params['fallback'] === false)) ? false : true;
2130
	$dir = '/' . $params['subdir'] . '/';
2131
2132
	// Whoa ... we've done this before yes?
2133
	$cache_name = 'load_' . $params['extension'] . '_' . hash('md5', $settings['theme_dir'] . implode('_', $filenames));
2134
	$temp = array();
2135
	if ($cache->getVar($temp, $cache_name, 600))
2136
	{
2137
		if (empty($context[$params['index_name']]))
2138
			$context[$params['index_name']] = array();
2139
2140
		$context[$params['index_name']] += $temp;
2141
2142
		if ($db_show_debug === true)
2143
		{
2144 7
			foreach ($temp as $temp_params)
2145
			{
2146
				Debug::instance()->add($params['debug_index'], $temp_params['options']['basename'] . '(' . (!empty($temp_params['options']['local']) ? (!empty($temp_params['options']['url']) ? basename($temp_params['options']['url']) : basename($temp_params['options']['dir'])) : '') . ')');
2147 7
			}
2148
		}
2149
	}
2150 7
	else
2151
	{
2152 7
		$this_build = array();
2153
2154
		// All the files in this group use the above parameters
2155
		foreach ($filenames as $filename)
2156
		{
2157
			// Account for shorthand like admin.ext?xyz11 filenames
2158 7
			$has_cache_staler = strpos($filename, '.' . $params['extension'] . '?');
2159 7
			if ($has_cache_staler)
2160
			{
2161 7
				$cache_staler = $staler_string;
2162
				$params['basename'] = substr($filename, 0, $has_cache_staler + strlen($params['extension']) + 1);
2163
			}
2164 7
			else
2165 7
			{
2166 7
				$cache_staler = $staler_string;
2167 7
				$params['basename'] = $filename;
2168 7
			}
2169
			$this_id = empty($id) ? strtr(basename($filename), '?', '_') : $id;
2170
2171 7
			// Is this a local file?
2172 7
			if (!empty($params['local']) || (substr($filename, 0, 4) !== 'http' && substr($filename, 0, 2) !== '//'))
2173
			{
2174
				$params['local'] = true;
2175
				$params['dir'] = $settings['theme_dir'] . $dir;
2176
				$params['url'] = $settings['theme_url'];
2177
2178
				// Fallback if we are not already in the default theme
2179
				if ($fallback && ($settings['theme_dir'] !== $settings['default_theme_dir']) && !file_exists($settings['theme_dir'] . $dir . $params['basename']))
2180
				{
2181
					// Can't find it in this theme, how about the default?
2182
					if (file_exists($settings['default_theme_dir'] . $dir . $params['basename']))
2183
					{
2184 7
						$filename = $settings['default_theme_url'] . $dir . $params['basename'] . $cache_staler;
2185 7
						$params['dir'] = $settings['default_theme_dir'] . $dir;
2186
						$params['url'] = $settings['default_theme_url'];
2187
					}
2188 7
					else
2189 7
						$filename = false;
2190 7
				}
2191
				else
2192 7
					$filename = $settings['theme_url'] . $dir . $params['basename'] . $cache_staler;
2193 7
			}
2194
2195
			// Add it to the array for use in the template
2196 7
			if (!empty($filename))
2197
			{
2198
				$this_build[$this_id] = $context[$params['index_name']][$this_id] = array('filename' => $filename, 'options' => $params);
2199 7
2200 7
				if ($db_show_debug === true)
2201
				{
2202 7
					Debug::instance()->add($params['debug_index'], $params['basename'] . '(' . (!empty($params['local']) ? (!empty($params['url']) ? basename($params['url']) : basename($params['dir'])) : '') . ')');
2203
				}
2204
			}
2205
2206
			// Save it so we don't have to build this so often
2207
			$cache->put($cache_name, $this_build, 600);
2208
		}
2209
	}
2210
}
2211
2212 2
/**
2213 2
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
2214
 *
2215
 * @param mixed[] $vars array of vars to include in the output done as 'varname' => 'var value'
2216
 * @param bool $escape = false, whether or not to escape the value
2217
 */
2218
function addJavascriptVar($vars, $escape = false)
2219
{
2220
	theme()->addJavascriptVar($vars, $escape);
2221
}
2222
2223
/**
2224
 * Add a block of inline Javascript code to be executed later
2225
 *
2226
 * What it does:
2227
 *
2228
 * - only use this if you have to, generally external JS files are better, but for very small scripts
2229 4
 *   or for scripts that require help from PHP/whatever, this can be useful.
2230 4
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
2231
 *
2232
 * @param string $javascript
2233
 * @param bool $defer = false, define if the script should load in <head> or before the closing <html> tag
2234
 */
2235
function addInlineJavascript($javascript, $defer = false)
2236
{
2237
	theme()->addInlineJavascript($javascript, $defer);
2238
}
2239
2240
/**
2241
 * Load a language file.
2242
 *
2243
 * - Tries the current and default themes as well as the user and global languages.
2244
 *
2245 37
 * @param string $template_name
2246 37
 * @param string $lang = ''
2247 37
 * @param bool $fatal = true
2248
 * @param bool $force_reload = false
2249
 * @return string The language actually loaded.
2250 37
 */
2251 37
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
2252
{
2253 37
	global $user_info, $language, $settings, $modSettings;
2254 37
	global $db_show_debug, $txt;
2255
	static $already_loaded = array();
2256
2257 17
	// Default to the user's language.
2258 17
	if ($lang == '')
2259
		$lang = isset($user_info['language']) ? $user_info['language'] : $language;
2260
2261 17
	if (!$force_reload && isset($already_loaded[$template_name]) && $already_loaded[$template_name] == $lang)
2262 17
		return $lang;
2263
2264
	// Do we want the English version of language file as fallback?
2265 17
	if (empty($modSettings['disable_language_fallback']) && $lang != 'english')
2266 17
		loadLanguage($template_name, 'english', false);
2267 17
2268
	// Make sure we have $settings - if not we're in trouble and need to find it!
2269 17
	if (empty($settings['default_theme_dir']))
2270
		loadEssentialThemeData();
2271 17
2272
	// What theme are we in?
2273 17
	$theme_name = basename($settings['theme_url']);
2274 17
	if (empty($theme_name))
2275
		$theme_name = 'unknown';
2276
2277
	$fix_arrays = false;
2278 17
	// For each file open it up and write it out!
2279 17
	foreach (explode('+', $template_name) as $template)
2280 17
	{
2281
		if ($template === 'index')
2282
			$fix_arrays = true;
2283 17
2284 17
		// Obviously, the current theme is most important to check.
2285
		$attempts = array(
2286
			array($settings['theme_dir'], $template, $lang, $settings['theme_url']),
2287
			array($settings['theme_dir'], $template, $language, $settings['theme_url']),
2288
		);
2289
2290 17
		// Do we have a base theme to worry about?
2291 17
		if (isset($settings['base_theme_dir']))
2292
		{
2293
			$attempts[] = array($settings['base_theme_dir'], $template, $lang, $settings['base_theme_url']);
2294 17
			$attempts[] = array($settings['base_theme_dir'], $template, $language, $settings['base_theme_url']);
2295 17
		}
2296
2297
		// Fall back on the default theme if necessary.
2298
		$attempts[] = array($settings['default_theme_dir'], $template, $lang, $settings['default_theme_url']);
2299
		$attempts[] = array($settings['default_theme_dir'], $template, $language, $settings['default_theme_url']);
2300 17
2301
		// Fall back on the English language if none of the preferred languages can be found.
2302
		if (!in_array('english', array($lang, $language)))
2303 17
		{
2304 17
			$attempts[] = array($settings['theme_dir'], $template, 'english', $settings['theme_url']);
2305
			$attempts[] = array($settings['default_theme_dir'], $template, 'english', $settings['default_theme_url']);
2306 17
		}
2307 17
2308
		$templates = Templates::instance();
2309 17
2310
		// Try to find the language file.
2311
		$found = false;
2312 17
		foreach ($attempts as $k => $file)
2313
		{
2314 17
			if (file_exists($file[0] . '/languages/' . $file[2] . '/' . $file[1] . '.' . $file[2] . '.php'))
2315
			{
2316
				// Include it!
2317
				$templates->templateInclude($file[0] . '/languages/' . $file[2] . '/' . $file[1] . '.' . $file[2] . '.php');
2318
2319
				// Note that we found it.
2320
				$found = true;
2321
2322
				break;
2323
			}
2324
			// @deprecated since 1.0 - old way of archiving language files, all in one directory
2325
			elseif (file_exists($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php'))
2326
			{
2327 17
				// Include it!
2328
				$templates->templateInclude($file[0] . '/languages/' . $file[1] . '.' . $file[2] . '.php');
2329
2330 17
				// Note that we found it.
2331 17
				$found = true;
2332
2333
				break;
2334
			}
2335 17
		}
2336
2337
		// That couldn't be found!  Log the error, but *try* to continue normally.
2338 17
		if (!$found && $fatal)
2339
		{
2340
			Errors::instance()->log_error(sprintf($txt['theme_language_error'], $template_name . '.' . $lang, 'template'));
2341 17
			break;
2342 17
		}
2343
	}
2344
2345
	if ($fix_arrays)
2346
		fix_calendar_text();
2347 17
2348
	// Keep track of what we're up to soldier.
2349
	if ($db_show_debug === true)
2350 17
	{
2351
		Debug::instance()->add('language_files', $template_name . '.' . $lang . ' (' . $theme_name . ')');
2352
	}
2353
2354
	// Remember what we have loaded, and in which language.
2355
	$already_loaded[$template_name] = $lang;
2356
2357
	// Return the language actually loaded.
2358
	return $lang;
2359
}
2360
2361
/**
2362
 * Loads / Sets arrays for use in date display
2363
 */
2364
function fix_calendar_text()
2365
{
2366
	global $txt;
2367
2368
	$txt['days'] = array(
2369
		$txt['sunday'],
2370
		$txt['monday'],
2371
		$txt['tuesday'],
2372
		$txt['wednesday'],
2373
		$txt['thursday'],
2374
		$txt['friday'],
2375
		$txt['saturday'],
2376
	);
2377
	$txt['days_short'] = array(
2378
		$txt['sunday_short'],
2379
		$txt['monday_short'],
2380
		$txt['tuesday_short'],
2381
		$txt['wednesday_short'],
2382
		$txt['thursday_short'],
2383
		$txt['friday_short'],
2384
		$txt['saturday_short'],
2385
	);
2386
	$txt['months'] = array(
2387
		1 => $txt['january'],
2388
		$txt['february'],
2389
		$txt['march'],
2390
		$txt['april'],
2391
		$txt['may'],
2392
		$txt['june'],
2393
		$txt['july'],
2394
		$txt['august'],
2395
		$txt['september'],
2396
		$txt['october'],
2397
		$txt['november'],
2398
		$txt['december'],
2399
	);
2400
	$txt['months_titles'] = array(
2401
		1 => $txt['january_titles'],
2402
		$txt['february_titles'],
2403
		$txt['march_titles'],
2404
		$txt['april_titles'],
2405
		$txt['may_titles'],
2406
		$txt['june_titles'],
2407
		$txt['july_titles'],
2408
		$txt['august_titles'],
2409
		$txt['september_titles'],
2410
		$txt['october_titles'],
2411
		$txt['november_titles'],
2412
		$txt['december_titles'],
2413
	);
2414
	$txt['months_short'] = array(
2415
		1 => $txt['january_short'],
2416
		$txt['february_short'],
2417
		$txt['march_short'],
2418
		$txt['april_short'],
2419
		$txt['may_short'],
2420
		$txt['june_short'],
2421
		$txt['july_short'],
2422
		$txt['august_short'],
2423
		$txt['september_short'],
2424
		$txt['october_short'],
2425
		$txt['november_short'],
2426
		$txt['december_short'],
2427
	);
2428
}
2429
2430
/**
2431
 * Get all parent boards (requires first parent as parameter)
2432
 *
2433
 * What it does:
2434
 *
2435
 * - It finds all the parents of id_parent, and that board itself.
2436
 * - Additionally, it detects the moderators of said boards.
2437
 * - Returns an array of information about the boards found.
2438 11
 *
2439
 * @param int $id_parent
2440 11
 *
2441 11
 * @return array
2442 11
 * @throws Elk_Exception parent_not_found
2443
 */
2444
function getBoardParents($id_parent)
2445 11
{
2446 11
	global $scripturl;
2447 11
2448 11
	$db = database();
2449
	$cache = Cache::instance();
2450
	$boards = array();
2451 11
2452
	// First check if we have this cached already.
2453 10
	if (!$cache->getVar($boards, 'board_parents-' . $id_parent, 480))
2454
	{
2455
		$boards = array();
2456
		$original_parent = $id_parent;
2457
2458
		// Loop while the parent is non-zero.
2459
		while ($id_parent != 0)
2460 10
		{
2461
			$result = $db->query('', '
2462 10
				SELECT
2463
					b.id_parent, b.name, {int:board_parent} AS id_board, COALESCE(mem.id_member, 0) AS id_moderator,
2464 10
					mem.real_name, b.child_level
2465
				FROM {db_prefix}boards AS b
2466 10
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
2467 10
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
2468
				WHERE b.id_board = {int:board_parent}',
2469
				array(
2470 10
					'board_parent' => $id_parent,
2471
				)
2472 10
			);
2473 10
			// In the EXTREMELY unlikely event this happens, give an error message.
2474 10
			if ($db->num_rows($result) == 0)
2475 10
			{
2476 10
				throw new Elk_Exception('parent_not_found', 'critical');
2477 10
			}
2478 10
			while ($row = $db->fetch_assoc($result))
2479 10
			{
2480 10
				if (!isset($boards[$row['id_board']]))
2481 10
				{
2482
					$id_parent = $row['id_parent'];
2483
					$boards[$row['id_board']] = array(
2484 10
						'url' => $scripturl . '?board=' . $row['id_board'] . '.0',
2485 10
						'name' => $row['name'],
2486
						'level' => $row['child_level'],
2487
						'moderators' => array()
2488
					);
2489
				}
2490
2491
				// If a moderator exists for this board, add that moderator for all children too.
2492
				if (!empty($row['id_moderator']))
2493
					foreach ($boards as $id => $dummy)
2494 10
					{
2495 10
						$boards[$id]['moderators'][$row['id_moderator']] = array(
2496 10
							'id' => $row['id_moderator'],
2497
							'name' => $row['real_name'],
2498 11
							'href' => $scripturl . '?action=profile;u=' . $row['id_moderator'],
2499 11
							'link' => '<a href="' . $scripturl . '?action=profile;u=' . $row['id_moderator'] . '">' . $row['real_name'] . '</a>'
2500
						);
2501 11
					}
2502
			}
2503
			$db->free_result($result);
2504
		}
2505
2506
		$cache->put('board_parents-' . $original_parent, $boards, 480);
2507
	}
2508
2509
	return $boards;
2510
}
2511 1
2512
/**
2513 1
 * Attempt to reload our known languages.
2514
 *
2515
 * @param bool $use_cache = true
2516 1
 */
2517
function getLanguages($use_cache = true)
2518 1
{
2519 1
	global $settings;
2520
2521 1
	$cache = Cache::instance();
2522 1
2523
	// Either we don't use the cache, or its expired.
2524
	$languages = array();
2525
2526 1
	if (!$use_cache || !$cache->getVar($languages, 'known_languages', $cache->levelLowerThan(2) ? 86400 : 3600))
2527 1
	{
2528 1
		// If we don't have our theme information yet, lets get it.
2529
		if (empty($settings['default_theme_dir']))
2530
			loadTheme(0, false);
2531 1
2532 1
		// Default language directories to try.
2533
		$language_directories = array(
2534
			$settings['default_theme_dir'] . '/languages',
2535 1
			$settings['actual_theme_dir'] . '/languages',
2536
		);
2537 1
2538
		// We possibly have a base theme directory.
2539
		if (!empty($settings['base_theme_dir']))
2540 1
			$language_directories[] = $settings['base_theme_dir'] . '/languages';
2541 1
2542
		// Remove any duplicates.
2543 1
		$language_directories = array_unique($language_directories);
2544 1
2545
		foreach ($language_directories as $language_dir)
2546
		{
2547 1
			// Can't look in here... doesn't exist!
2548 1
			if (!file_exists($language_dir))
2549
				continue;
2550
2551 1
			$dir = dir($language_dir);
2552 1
			while ($entry = $dir->read())
2553
			{
2554
				// Only directories are interesting
2555 1
				if ($entry == '..' || !is_dir($dir->path . '/' . $entry))
2556 1
					continue;
2557
2558 1
				// @todo at some point we may want to simplify that stuff (I mean scanning all the files just for index)
2559 1
				$file_dir = dir($dir->path . '/' . $entry);
2560 1
				while ($file_entry = $file_dir->read())
2561 1
				{
2562 1
					// Look for the index language file....
2563
					if (!preg_match('~^index\.(.+)\.php$~', $file_entry, $matches))
2564 1
						continue;
2565 1
2566 1
					$languages[$matches[1]] = array(
2567 1
						'name' => Util::ucwords(strtr($matches[1], array('_' => ' '))),
2568 1
						'selected' => false,
2569
						'filename' => $matches[1],
2570
						'location' => $language_dir . '/' . $entry . '/index.' . $matches[1] . '.php',
2571 1
					);
2572 1
				}
2573
				$file_dir->close();
2574 1
			}
2575
			$dir->close();
2576
		}
2577
2578
		// Lets cash in on this deal.
2579
		$cache->put('known_languages', $languages, $cache->isEnabled() && $cache->levelLowerThan(1) ? 86400 : 3600);
2580
	}
2581
2582
	return $languages;
2583
}
2584
2585
/**
2586
 * Initialize a database connection.
2587
 */
2588
function loadDatabase()
2589
{
2590
	global $db_persist, $db_server, $db_user, $db_passwd, $db_port;
2591
	global $db_type, $db_name, $ssi_db_user, $ssi_db_passwd, $db_prefix;
2592
2593
	// Database stuffs
2594
	require_once(SOURCEDIR . '/database/Database.subs.php');
2595
2596
	// Figure out what type of database we are using.
2597
	if (empty($db_type) || !file_exists(SOURCEDIR . '/database/Db-' . $db_type . '.class.php'))
2598
		$db_type = 'mysql';
2599
2600
	// 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.
2601
	if (ELK === 'SSI' && !empty($ssi_db_user) && !empty($ssi_db_passwd))
2602
		$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);
2603
2604
	// Either we aren't in SSI mode, or it failed.
2605
	if (empty($connection))
2606
		$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);
2607
2608
	// Safe guard here, if there isn't a valid connection lets put a stop to it.
2609
	if (!$connection)
2610
		Errors::instance()->display_db_error();
2611
2612
	// If in SSI mode fix up the prefix.
2613
	$db = database();
2614
	if (ELK === 'SSI')
0 ignored issues
show
introduced by
The condition ELK === 'SSI' is always true.
Loading history...
2615
		$db_prefix = $db->fix_prefix($db_prefix, $db_name);
2616
2617
	// Case sensitive database? Let's define a constant.
2618
	if ($db->db_case_sensitive() && !defined('DB_CASE_SENSITIVE'))
2619
		DEFINE('DB_CASE_SENSITIVE', '1');
2620
}
2621
2622
/**
2623
 * Determine the user's avatar type and return the information as an array
2624
 *
2625
 * @todo this function seems more useful than expected, it should be improved. :P
2626 2
 *
2627
 * @event integrate_avatar allows access to $avatar array before it is returned
2628 2
 * @param mixed[] $profile array containing the users profile data
2629 2
 *
2630
 * @return mixed[] $avatar
2631 2
 */
2632
function determineAvatar($profile)
2633
{
2634 2
	global $modSettings, $scripturl, $settings;
2635 2
2636
	if (empty($profile))
2637
		return array();
2638
2639
	$avatar_protocol = substr(strtolower($profile['avatar']), 0, 7);
2640
2641
	// uploaded avatar?
2642
	if ($profile['id_attach'] > 0 && empty($profile['avatar']))
2643
	{
2644
		// where are those pesky avatars?
2645
		$avatar_url = empty($profile['attachment_type']) ? $scripturl . '?action=dlattach;attach=' . $profile['id_attach'] . ';type=avatar' : $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
2646
2647 2
		$avatar = array(
2648
			'name' => $profile['avatar'],
2649
			'image' => '<img class="avatar avatarresize" src="' . $avatar_url . '" alt="" />',
2650
			'href' => $avatar_url,
2651
			'url' => '',
2652
		);
2653
	}
2654
	// remote avatar?
2655
	elseif ($avatar_protocol === 'http://' || $avatar_protocol === 'https:/')
2656
	{
2657 2
		$avatar = array(
2658
			'name' => $profile['avatar'],
2659
			'image' => '<img class="avatar avatarresize" src="' . $profile['avatar'] . '" alt="" />',
2660
			'href' => $profile['avatar'],
2661
			'url' => $profile['avatar'],
2662
		);
2663
	}
2664
	// Gravatar instead?
2665
	elseif (!empty($profile['avatar']) && $profile['avatar'] === 'gravatar')
2666
	{
2667
		// Gravatars URL.
2668
		$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']) : '');
2669
2670 2
		$avatar = array(
2671
			'name' => $profile['avatar'],
2672
			'image' => '<img class="avatar avatarresize" src="' . $gravatar_url . '" alt="" />',
2673
			'href' => $gravatar_url,
2674
			'url' => $gravatar_url,
2675
		);
2676
	}
2677
	// an avatar from the gallery?
2678
	elseif (!empty($profile['avatar']) && !($avatar_protocol === 'http://' || $avatar_protocol === 'https:/'))
2679
	{
2680 2
		$avatar = array(
2681
			'name' => $profile['avatar'],
2682
			'image' => '<img class="avatar avatarresize" src="' . $modSettings['avatar_url'] . '/' . $profile['avatar'] . '" alt="" />',
2683
			'href' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
2684
			'url' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
2685
		);
2686
	}
2687
	// no custom avatar found yet, maybe a default avatar?
2688
	elseif (!empty($modSettings['avatar_default']) && empty($profile['avatar']) && empty($profile['filename']))
2689
	{
2690
		// $settings not initialized? We can't do anything further..
2691
		if (!empty($settings))
2692
		{
2693
			// Let's proceed with the default avatar.
2694
			// TODO: This should be incorporated into the theme.
2695
			$avatar = array(
2696
				'name' => '',
2697
				'image' => '<img class="avatar avatarresize" src="' . $settings['images_url'] . '/default_avatar.png" alt="" />',
2698
				'href' => $settings['images_url'] . '/default_avatar.png',
2699
				'url' => 'http://',
2700
			);
2701
		}
2702 2
		else
2703 2
		{
2704 2
			$avatar = array();
2705
		}
2706 2
	}
2707
	// finally ...
2708
	else
2709 2
		$avatar = array(
2710
			'name' => '',
2711 2
			'image' => '',
2712
			'href' => '',
2713 2
			'url' => ''
2714
		);
2715
2716
	// Make sure there's a preview for gravatars available.
2717
	$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']) : '');
2718
2719
	call_integration_hook('integrate_avatar', array(&$avatar, $profile));
2720
2721 15
	return $avatar;
2722 15
}
2723
2724 15
/**
2725 15
 * Get information about the server
2726
 */
2727
function detectServer()
2728
{
2729
	global $context;
2730
	static $server = null;
2731
2732
	if ($server === null)
2733
	{
2734
		$server = new Server($_SERVER);
2735
		$servers = array('iis', 'apache', 'litespeed', 'lighttpd', 'nginx', 'cgi', 'windows');
2736
		$context['server'] = array();
2737
		foreach ($servers as $name)
2738
		{
2739 15
			$context['server']['is_' . $name] = $server->is($name);
2740
		}
2741
2742
		$context['server']['iso_case_folding'] = $server->is('iso_case_folding');
2743
		// A bug in some versions of IIS under CGI (older ones) makes cookie setting not work with Location: headers.
2744
		$context['server']['needs_login_fix'] = $server->is('needs_login_fix');
2745
	}
2746
2747
	return $server;
2748
}
2749
2750
/**
2751
 * Returns if a webserver is of type server (apache, nginx, etc)
2752
 *
2753
 * @param $server
2754
 *
2755
 * @return bool
2756
 */
2757
function serverIs($server)
2758
{
2759
	return detectServer()->is($server);
2760
}
2761
2762
/**
2763
 * Do some important security checks:
2764
 *
2765
 * What it does:
2766
 *
2767
 * - Checks the existence of critical files e.g. install.php
2768
 * - Checks for an active admin session.
2769
 * - Checks cache directory is writable.
2770
 * - Calls secureDirectory to protect attachments & cache.
2771
 * - Checks if the forum is in maintenance mode.
2772
 */
2773
function doSecurityChecks()
2774
{
2775
	global $modSettings, $context, $maintenance, $user_info, $txt, $scripturl, $user_settings, $options;
2776
2777
	$show_warnings = false;
2778
2779
	$cache = Cache::instance();
2780
2781
	if (allowedTo('admin_forum') && !$user_info['is_guest'])
2782
	{
2783
		// If agreement is enabled, at least the english version shall exists
2784
		if ($modSettings['requireAgreement'] && !file_exists(BOARDDIR . '/agreement.txt'))
2785
		{
2786
			$context['security_controls_files']['title'] = $txt['generic_warning'];
2787
			$context['security_controls_files']['errors']['agreement'] = $txt['agreement_missing'];
2788
			$show_warnings = true;
2789
		}
2790
2791
		// Cache directory writable?
2792
		if ($cache->isEnabled() && !is_writable(CACHEDIR))
2793
		{
2794
			$context['security_controls_files']['title'] = $txt['generic_warning'];
2795
			$context['security_controls_files']['errors']['cache'] = $txt['cache_writable'];
2796
			$show_warnings = true;
2797
		}
2798
2799
		if (checkSecurityFiles())
2800
			$show_warnings = true;
2801
2802
		// We are already checking so many files...just few more doesn't make any difference! :P
2803
		require_once(SUBSDIR . '/Attachments.subs.php');
2804
		$path = getAttachmentPath();
2805
		secureDirectory($path, true);
2806
		secureDirectory(CACHEDIR, false, '"\.(js|css)$"');
2807
2808
		// Active admin session?
2809
		if (isAdminSessionActive())
2810
			$context['warning_controls']['admin_session'] = sprintf($txt['admin_session_active'], ($scripturl . '?action=admin;area=adminlogoff;redir;' . $context['session_var'] . '=' . $context['session_id']));
2811
2812
		// Maintenance mode enabled?
2813
		if (!empty($maintenance))
2814
			$context['warning_controls']['maintenance'] = sprintf($txt['admin_maintenance_active'], ($scripturl . '?action=admin;area=serversettings;' . $context['session_var'] . '=' . $context['session_id']));
2815
2816
		// New updates
2817
		if (defined('FORUM_VERSION'))
2818
		{
2819
			$index = 'new_in_' . str_replace(array('ElkArte ', '.'), array('', '_'), FORUM_VERSION);
2820
			if (!empty($modSettings[$index]) && empty($options['dismissed_' . $index]))
2821
			{
2822
				$show_warnings = true;
2823
				$context['new_version_updates'] = array(
2824
					'title' => $txt['new_version_updates'],
2825
					'errors' => array(replaceBasicActionUrl($txt['new_version_updates_text'])),
2826
				);
2827
			}
2828
		}
2829
	}
2830
2831
	// Check for database errors.
2832
	if (!empty($_SESSION['query_command_denied']))
2833
	{
2834
		if ($user_info['is_admin'])
2835
		{
2836
			$context['security_controls_query']['title'] = $txt['query_command_denied'];
2837
			$show_warnings = true;
2838
			foreach ($_SESSION['query_command_denied'] as $command => $error)
2839
				$context['security_controls_query']['errors'][$command] = '<pre>' . Util::htmlspecialchars($error) . '</pre>';
2840
		}
2841
		else
2842
		{
2843
			$context['security_controls_query']['title'] = $txt['query_command_denied_guests'];
2844
			foreach ($_SESSION['query_command_denied'] as $command => $error)
2845
				$context['security_controls_query']['errors'][$command] = '<pre>' . sprintf($txt['query_command_denied_guests_msg'], Util::htmlspecialchars($command)) . '</pre>';
2846
		}
2847
	}
2848
2849
	// Are there any members waiting for approval?
2850
	if (allowedTo('moderate_forum') && ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) && !empty($modSettings['unapprovedMembers']))
2851
		$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']);
2852
2853
	if (!empty($context['open_mod_reports']) && (empty($user_settings['mod_prefs']) || $user_settings['mod_prefs'][0] == 1))
2854
	{
2855
		$context['warning_controls']['open_mod_reports'] = '<a href="' . $scripturl . '?action=moderate;area=reports">' . sprintf($txt['mod_reports_waiting'], $context['open_mod_reports']) . '</a>';
2856
	}
2857
2858
	if (!empty($context['open_pm_reports']) && allowedTo('admin_forum'))
2859
	{
2860
		$context['warning_controls']['open_pm_reports'] = '<a href="' . $scripturl . '?action=moderate;area=pm_reports">' . sprintf($txt['pm_reports_waiting'], $context['open_pm_reports']) . '</a>';
2861
	}
2862
2863
	if (isset($_SESSION['ban']['cannot_post']))
2864
	{
2865
		// An admin cannot be banned (technically he could), and if it is better he knows.
2866
		$context['security_controls_ban']['title'] = sprintf($txt['you_are_post_banned'], $user_info['is_guest'] ? $txt['guest_title'] : $user_info['name']);
2867
		$show_warnings = true;
2868
2869
		$context['security_controls_ban']['errors']['reason'] = '';
2870
2871
		if (!empty($_SESSION['ban']['cannot_post']['reason']))
2872
			$context['security_controls_ban']['errors']['reason'] = $_SESSION['ban']['cannot_post']['reason'];
2873
2874
		if (!empty($_SESSION['ban']['expire_time']))
2875
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . sprintf($txt['your_ban_expires'], standardTime($_SESSION['ban']['expire_time'], false)) . '</span>';
2876
		else
2877
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . $txt['your_ban_expires_never'] . '</span>';
2878
	}
2879
2880
	// Finally, let's show the layer.
2881
	if ($show_warnings || !empty($context['warning_controls']))
2882
		\Template_Layers::instance()->addAfter('admin_warning', 'body');
2883
}
2884
2885
/**
2886
 * Load everything necessary for the BBC parsers
2887
 */
2888
function loadBBCParsers()
2889
{
2890
	global $modSettings;
2891
2892
	// Set the default disabled BBC
2893
	if (!empty($modSettings['disabledBBC']))
2894
	{
2895
		if (!is_array($modSettings['disabledBBC']))
2896
			$disabledBBC = explode(',', $modSettings['disabledBBC']);
2897
		else
2898
			$disabledBBC = $modSettings['disabledBBC'];
2899
		\BBC\ParserWrapper::instance()->setDisabled(empty($disabledBBC) ? array() : (array) $disabledBBC);
2900
	}
2901
2902
	return 1;
2903
}
2904
2905
/**
2906
 * This is necessary to support data stored in the pre-1.0.8 way (i.e. serialized)
2907
 *
2908
 * @param string $variable The string to convert
2909
 * @param null|callable $save_callback The function that will save the data to the db
2910
 * @return mixed[] the array
2911
 */
2912
function serializeToJson($variable, $save_callback = null)
2913
{
2914
	$array_form = json_decode($variable, true);
2915
2916
	// decoding failed, let's try with unserialize
2917
	if (!is_array($array_form))
2918
	{
2919
		try
2920
		{
2921
			$array_form = Util::unserialize($variable);
2922
		}
2923
		catch (\Exception $e)
2924
		{
2925
			$array_form = false;
2926
		}
2927
2928
		// If unserialize fails as well, let's just store an empty array
2929
		if ($array_form === false)
2930
		{
2931
			$array_form = array(0, '', 0);
2932
		}
2933
2934
		// Time to update the value if necessary
2935
		if ($save_callback !== null)
2936
		{
2937
			$save_callback($array_form);
2938
		}
2939
	}
2940
2941
	return $array_form;
2942
}
2943
2944
/*
2945
* Provide a PHP 8.1 version of strftime
2946
*
2947
* @param string $format of the date/time to return
2948
* @param int|null $timestamp to convert
2949
* @return string|false
2950
*/
2951
function elk_strftime(string $format, int $timestamp = null)
2952
{
2953
	if (function_exists('strftime') && (PHP_VERSION_ID < 80100))
2954
		return strftime($format, $timestamp);
2955
2956
	if (is_null($timestamp))
2957
		$timestamp = time();
2958
2959
	$date_equivalents = array (
2960
		'%a' => 'D',
2961
		'%A' => 'l',
2962
		'%d' => 'd',
2963
		'%e' => 'j',
2964
		'%j' => 'z',
2965
		'%u' => 'N',
2966
		'%w' => 'w',
2967
		// Week
2968
		'%U' => '',	// Week Number of the given year
2969
		'%V' => 'W',
2970
		'%W' => '',
2971
		// Month
2972
		'%b' => 'M',
2973
		'%B' => 'F',
2974
		'%h' => 'M',
2975
		'%m' => 'm',
2976
		// Year
2977
		'%C' => '', // Two digit representation of the century
2978
		'%g' => 'y',
2979
		'%G' => 'o',
2980
		'%y' => 'y',
2981
		'%Y' => 'Y',
2982
		// Time
2983
		'%H' => 'H',
2984
		'%k' => 'G',
2985
		'%I' => 'h',
2986
		'%l' => 'g',
2987
		'%M' => 'i',
2988
		'%p' => 'A',
2989
		'%P' => 'a',
2990
		'%r' => 'H:i:s a',
2991
		'%R' => 'H:i',
2992
		'%S' => 's',
2993
		'%T' => 'h:m:s',
2994
		'%X' => '', // Preferred time representation based upon locale
2995
		'%z' => 'O',
2996
		'%Z' => 'T',
2997
		// Time and Date Stamps
2998
		'%c' => 'c',
2999
		'%D' => 'm/d/y',
3000
		'%F' => 'y/m/d',
3001
		'%s' => 'U',
3002
		'%x' => '', // Locale based date representation
3003
		// Misc
3004
		'%n' => "\n",
3005
		'%t' => "\t",
3006
		'%%' => '%',
3007
	);
3008
3009
	$format = preg_replace_callback(
3010
		'/%[A-Za-z]{1}/',
3011
		function($matches) use ($timestamp, $date_equivalents)
3012
		{
3013
			$new_format = str_replace(array_keys($date_equivalents), array_values($date_equivalents), $matches[0]);
3014
			return date($new_format, $timestamp);
3015
		},
3016
		$format
3017
	);
3018
3019
	return $format;
3020
}
3021
3022
/*
3023
* Provide a PHP 8.1 version of gmstrftime
3024
*
3025
* @param string $format of the date/time to return
3026
* @param int|null $timestamp to convert
3027
* @return string|false
3028
*/
3029
function elk_gmstrftime(string $format, int $timestamp = null)
3030
{
3031
	if (function_exists('gmstrftime') && (PHP_VERSION_ID < 80100))
3032
		return gmstrftime($format, $timestamp);
3033
	
3034
	return elk_strftime($format, $timestamp);
3035
}
3036