Issues (1686)

sources/Load.php (1 issue)

1
<?php
2
3
/**
4
 * This file has the hefty job of loading information for the forum.
5
 *
6
 * @package   ElkArte Forum
7
 * @copyright ElkArte Forum contributors
8
 * @license   BSD http://opensource.org/licenses/BSD-3-Clause (see accompanying LICENSE.txt file)
9
 *
10
 * This file contains code covered by:
11
 * copyright: 2011 Simple Machines (http://www.simplemachines.org)
12
 *
13
 * @version 2.0 dev
14
 *
15
 */
16
17
use BBC\ParserWrapper;
18
use ElkArte\Attachments\AttachmentsDirectory;
19
use ElkArte\Cache\Cache;
20
use ElkArte\Debug;
21
use ElkArte\Errors\Errors;
22
use ElkArte\Helper\FileFunctions;
23
use ElkArte\Helper\Util;
24
use ElkArte\Hooks;
25
use ElkArte\Http\Headers;
26
use ElkArte\Languages\Txt;
27
use ElkArte\Server;
28
use ElkArte\Themes\ThemeLoader;
29
use ElkArte\User;
30
31
/**
32
 * Load the $modSettings array and many necessary forum settings.
33
 *
34
 * What it does:
35
 *
36
 * - load the settings from cache if available, otherwise from the database.
37
 * - sets the timezone
38
 * - checks the load average settings if available.
39
 * - check whether post moderation is enabled.
40
 * - calls add_integration_function
41
 * - calls integrate_pre_include, integrate_pre_load,
42
 *
43
 * @event integrate_load_average is called if load average is enabled
44
 * @event integrate_pre_include to allow including files at startup
45
 * @event integrate_pre_load to call any pre load integration functions.
46 1
 *
47
 * @global array $modSettings is a giant array of all the forum-wide settings and statistics.
48 1
 */
49 1
function reloadSettings()
50 1
{
51
	global $modSettings;
52
53 1
	$db = database();
54
	$cache = Cache::instance();
55 1
	$hooks = Hooks::instance();
56
57
	// Try to load it from the cache first; it'll never get cached if the setting is off.
58 1
	if (!$cache->getVar($modSettings, 'modSettings', 90))
59
	{
60 1
		$request = $db->query('', '
61 1
			SELECT 
62
				variable, value
63
			FROM {db_prefix}settings',
64
			array()
65 1
		);
66
		$modSettings = array();
67 1
		if (!$request)
68
		{
69 1
			Errors::instance()->display_db_error();
70
		}
71
72 1
		while (($row = $request->fetch_row()))
73
		{
74
			$modSettings[$row[0]] = $row[1];
75
		}
76
77 1
		$request->free_result();
78
79
		// Do a few things to protect against missing settings or settings with invalid values...
80
		if (empty($modSettings['defaultMaxTopics']) || $modSettings['defaultMaxTopics'] <= 0 || $modSettings['defaultMaxTopics'] > 999)
81
		{
82 1
			$modSettings['defaultMaxTopics'] = 20;
83
		}
84
85
		if (empty($modSettings['defaultMaxMessages']) || $modSettings['defaultMaxMessages'] <= 0 || $modSettings['defaultMaxMessages'] > 999)
86
		{
87 1
			$modSettings['defaultMaxMessages'] = 15;
88
		}
89 1
90
		if (empty($modSettings['defaultMaxMembers']) || $modSettings['defaultMaxMembers'] <= 0 || $modSettings['defaultMaxMembers'] > 999)
91
		{
92 1
			$modSettings['defaultMaxMembers'] = 30;
93
		}
94 1
95
		$modSettings['warning_enable'] = $modSettings['warning_settings'][0];
96
97 1
		$cache->put('modSettings', $modSettings, 90);
98
	}
99
100 1
	$hooks->loadIntegrations();
101
102 1
	// Setting the timezone is a requirement for some functions in PHP >= 5.1.
103
	if (isset($modSettings['default_timezone']))
104
	{
105
		date_default_timezone_set($modSettings['default_timezone']);
106 1
	}
107
108
	// Check the load averages?
109
	if (!empty($modSettings['loadavg_enable']))
110
	{
111
		if (!$cache->getVar($modSettings['load_average'], 'loadavg', 90))
112
		{
113
			require_once(SUBSDIR . '/Server.subs.php');
114
			$modSettings['load_average'] = detectServerLoad();
115
116
			$cache->put('loadavg', $modSettings['load_average'], 90);
117
		}
118
119
		if ($modSettings['load_average'] !== false)
120
		{
121
			call_integration_hook('integrate_load_average', array($modSettings['load_average']));
122
		}
123
124
		// Let's have at least a zero
125
		if (empty($modSettings['loadavg_forum']) || $modSettings['load_average'] === false)
126
		{
127
			$modSettings['current_load'] = 0;
128
		}
129
		else
130
		{
131
			$modSettings['current_load'] = $modSettings['load_average'];
132
		}
133
134
		if (!empty($modSettings['loadavg_forum']) && $modSettings['current_load'] >= $modSettings['loadavg_forum'])
135
		{
136
			Errors::instance()->display_loadavg_error();
137
		}
138 1
	}
139
	else
140
	{
141
		$modSettings['current_load'] = 0;
142 1
	}
143
144 1
	// Is post moderation alive and well?
145
	$modSettings['postmod_active'] = !isset($modSettings['admin_features']) || in_array('pm', explode(',', $modSettings['admin_features']));
146 1
147
	if (!isset($_SERVER['HTTPS']) || strtolower($_SERVER['HTTPS']) == 'off')
148
	{
149
		$modSettings['secureCookies'] = 0;
150 1
	}
151
152
	// Integration is cool.
153
	if (defined('ELK_INTEGRATION_SETTINGS'))
154
	{
155
		$integration_settings = Util::unserialize(ELK_INTEGRATION_SETTINGS);
156
		foreach ($integration_settings as $hook => $function)
157
		{
158
			add_integration_function($hook, $function);
159
		}
160 1
	}
161
162
	// Any files to pre include?
163 1
	call_integration_include_hook('integrate_pre_include');
164 1
165
	// Call pre load integration functions.
166
	call_integration_hook('integrate_pre_load');
167
}
168
169
/**
170
 * Load all the important user information.
171
 *
172
 * What it does:
173
 *
174
 * - sets up the $user_info array
175
 * - assigns $user_info['query_wanna_see_board'] for what boards the user can see.
176
 * - first checks for cookie or integration validation.
177
 * - uses the current session if no integration function or cookie is found.
178
 * - checks password length, if member is activated and the login span isn't over.
179
 * - if validation fails for the user, $id_member is set to 0.
180
 * - updates the last visit time when needed.
181
 *
182
 * @event integrate_verify_user allow for integration to verify a user
183
 * @event integrate_user_info to allow for adding to $user_info array
184
 * @deprecated kept until any trace of $user_info has been completely removed
185 1
 */
186 1
function loadUserSettings()
187
{
188
	User::load(true);
189
}
190
191
/**
192
 * Check for moderators and see if they have access to the board.
193
 *
194
 * What it does:
195
 *
196
 * - sets up the $board_info array for current board information.
197
 * - if cache is enabled, the $board_info array is stored in cache.
198
 * - redirects to appropriate post if only message id is requested.
199
 * - is only used when inside a topic or board.
200
 * - determines the local moderators for the board.
201
 * - adds group id 3 if the user is a local moderator for the board they are in.
202
 * - prevents access if user is not in proper group nor a local moderator of the board.
203
 *
204
 * @event integrate_load_board_query allows to add tables and columns to the query, used
205
 * to add to the $board_info array
206
 * @event integrate_loaded_board called after board_info is populated, allows to add
207
 * directly to $board_info
208
 */
209 13
function loadBoard()
210 13
{
211
	global $txt, $scripturl, $context, $modSettings, $board_info, $board, $topic;
212 13
213 13
	$db = database();
214
	$cache = Cache::instance();
215
216 13
	// Assume they are not a moderator.
217
	User::$info->is_mod = false;
218
	// @since 1.0.5 - is_mod takes into account only local (board) moderators,
219 13
	// and not global moderators, is_moderator is meant to take into account both.
220
	User::$info->is_moderator = false;
221
222 13
	// Start the breadcrumbs off empty..
223
	$context['breadcrumbs'] = [];
224
225 13
	// Have they by chance specified a message id but nothing else?
226
	if (empty($_REQUEST['action']) && empty($topic) && empty($board) && !empty($_REQUEST['msg']))
227
	{
228
		// Make sure the message id is really an int.
229
		$_REQUEST['msg'] = (int) $_REQUEST['msg'];
230
231
		// Looking through the message table can be slow, so try using the cache first.
232
		if (!$cache->getVar($topic, 'msg_topic-' . $_REQUEST['msg'], 120))
233
		{
234
			require_once(SUBSDIR . '/Messages.subs.php');
235
			$topic = associatedTopic($_REQUEST['msg']);
236
237
			// So did it find anything?
238
			if ($topic !== false)
239
			{
240
				// Save save save.
241
				$cache->put('msg_topic-' . $_REQUEST['msg'], $topic, 120);
242
			}
243
		}
244
245
		// Remember redirection is the key to avoiding fallout from your bosses.
246
		if (!empty($topic))
247
		{
248
			redirectexit('topic=' . $topic . '.msg' . $_REQUEST['msg'] . '#msg' . $_REQUEST['msg']);
249
		}
250
		else
251
		{
252
			loadPermissions();
253
			new ElkArte\Themes\ThemeLoader();
254
			if (!empty(User::$info->possibly_robot))
255
			{
256
				Headers::instance()
257
					->removeHeader('all')
258 13
					->headerSpecial('HTTP/1.1 410 Gone')
259
					->sendHeaders();
260 1
			}
261
262 1
			throw new \ElkArte\Exceptions\Exception('topic_gone', false);
263
		}
264
	}
265 12
266
	// Load this board only if it is specified.
267
	if (empty($board) && empty($topic))
268
	{
269
		$board_info = array('moderators' => array());
270
271
		return;
272
	}
273
274
	if ($cache->isEnabled() && (empty($topic) || $cache->levelHigherThan(2)))
275
	{
276
		// @todo SLOW?
277
		$temp = empty($topic) ? $cache->get('board-' . $board, 120) : $cache->get('topic_board-' . $topic, 120);
278
279
		if (!empty($temp))
280
		{
281
			$board_info = $temp;
282
			$board = (int) $board_info['id'];
283
		}
284 12
	}
285
286 12
	if (empty($temp))
287 12
	{
288
		$select_columns = array();
289 12
		$select_tables = array();
290
		// Wanna grab something more from the boards table or another table at all?
291 12
		call_integration_hook('integrate_load_board_query', array(&$select_columns, &$select_tables));
292
293
		$request = $db->query('', '
294
			SELECT
295 12
				c.id_cat, b.name AS bname, b.description, b.num_topics, b.member_groups, b.deny_member_groups,
296
				b.id_parent, c.name AS cname, COALESCE(mem.id_member, 0) AS id_moderator,
297 12
				mem.real_name' . (empty($topic) ? '' : ', b.id_board') . ', b.child_level,
298 12
				b.id_theme, b.override_theme, b.count_posts, b.old_posts, b.id_profile, b.redirect,
299 12
				b.unapproved_topics, b.unapproved_posts' . (empty($topic) ? '' : ', t.approved, t.id_member_started') . (empty($select_columns) ? '' : ', ' . implode(', ', $select_columns)) . '
300 12
			FROM {db_prefix}boards AS b' . (empty($topic) ? '' : '
301
				INNER JOIN {db_prefix}topics AS t ON (t.id_topic = {int:current_topic})') . (empty($select_tables) ? '' : '
302
				' . implode("\n\t\t\t\t", $select_tables)) . '
303
				LEFT JOIN {db_prefix}categories AS c ON (c.id_cat = b.id_cat)
304
				LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = {raw:board_link})
305
				LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
306 12
			WHERE b.id_board = {raw:board_link}',
307 12
			array(
308
				'current_topic' => $topic,
309
				'board_link' => empty($topic) ? $db->quote('{int:current_board}', array('current_board' => $board)) : 't.id_board',
310
			)
311 12
		);
312
		// If there aren't any, skip.
313 12
		if ($request->num_rows() > 0)
314
		{
315
			$row = $request->fetch_assoc();
316 12
317
			// Set the current board.
318 10
			if (!empty($row['id_board']))
319
			{
320
				$board = (int) $row['id_board'];
321
			}
322
323 12
			// Basic operating information. (globals... :/)
324
			$board_info = array(
325
				'id' => $board,
326 12
				'moderators' => array(),
327 12
				'cat' => array(
328
					'id' => (int) $row['id_cat'],
329 12
					'name' => $row['cname']
330 12
				),
331 12
				'name' => $row['bname'],
332 12
				'raw_description' => $row['description'],
333 12
				'description' => $row['description'],
334 12
				'num_topics' => (int) $row['num_topics'],
335 12
				'unapproved_topics' => (int) $row['unapproved_topics'],
336 12
				'unapproved_posts' => (int) $row['unapproved_posts'],
337 12
				'unapproved_user_topics' => 0,
338 12
				'parent_boards' => getBoardParents($row['id_parent']),
339 12
				'parent' => (int) $row['id_parent'],
340 12
				'child_level' => (int) $row['child_level'],
341 12
				'theme' => $row['id_theme'],
342 12
				'override_theme' => !empty($row['override_theme']),
343 12
				'profile' => (int) $row['id_profile'],
344 12
				'redirect' => $row['redirect'],
345 12
				'posts_count' => empty($row['count_posts']),
346
				'old_posts' => empty($row['old_posts']),
347
				'cur_topic_approved' => empty($topic) || $row['approved'],
348
				'cur_topic_starter' => empty($topic) ? 0 : $row['id_member_started'],
349 12
			);
350 12
351
			// Load the membergroups allowed, and check permissions.
352 12
			$board_info['groups'] = $row['member_groups'] === '' ? array() : explode(',', $row['member_groups']);
353
			$board_info['deny_groups'] = $row['deny_member_groups'] === '' ? array() : explode(',', $row['deny_member_groups']);
354
355
			call_integration_hook('integrate_loaded_board', array(&$board_info, &$row));
356 12
357
			do
358
			{
359
				if (!empty($row['id_moderator']))
360
				{
361
					$board_info['moderators'][$row['id_moderator']] = array(
362
						'id' => $row['id_moderator'],
363
						'name' => $row['real_name'],
364
						'href' => getUrl('profile', ['action' => 'profile', 'u' => $row['id_moderator']]),
365 12
						'link' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_moderator']]) . '">' . $row['real_name'] . '</a>'
366
					);
367
				}
368
			} while (($row = $request->fetch_assoc()));
369 12
370
			// If the board only contains unapproved posts and the user can't approve then they can't see any topics.
371
			// If that is the case do an additional check to see if they have any topics waiting to be approved.
372
			if ($board_info['num_topics'] === 0 && $modSettings['postmod_active'] && !allowedTo('approve_posts'))
373
			{
374
				// Free the previous result
375
				$request->free_result();
376
377
				// @todo why is this using id_topic?
378
				// @todo Can this get cached?
379
				$request = $db->query('', '
380
					SELECT COUNT(id_topic)
381
					FROM {db_prefix}topics
382
					WHERE id_member_started={int:id_member}
383
						AND approved = {int:unapproved}
384
						AND id_board = {int:board}',
385
					array(
386
						'id_member' => User::$info->id,
387
						'unapproved' => 0,
388
						'board' => $board,
389
					)
390
				);
391
392 12
				[$board_info['unapproved_user_topics']] = $request->fetch_row();
393
			}
394
395
			if ($cache->isEnabled() && (empty($topic) || $cache->levelHigherThan(2)))
396
			{
397
				// @todo SLOW?
398
				if (!empty($topic))
399
				{
400 12
					$cache->put('topic_board-' . $topic, $board_info, 120);
401
				}
402
403
				$cache->put('board-' . $board, $board_info, 120);
404
			}
405
		}
406
		else
407
		{
408
			// Otherwise the topic is invalid, there are no moderators, etc.
409
			$board_info = [
410
				'moderators' => [],
411
				'error' => 'exist'
412
			];
413 12
			$topic = null;
414
			$board = 0;
415
		}
416 12
417
		$request->free_result();
418 10
	}
419
420
	if (!empty($topic))
421 12
	{
422
		$_GET['board'] = $board;
423
	}
424 12
425
	if (!empty($board))
426 12
	{
427
		// Now check if the user is a moderator.
428
		User::$info->is_mod = isset($board_info['moderators'][User::$info->id]);
429
430 12
		if (User::$info->is_admin === false && count(array_intersect(User::$info->groups, $board_info['groups'])) === 0)
431
		{
432
			$board_info['error'] = 'access';
433
		}
434
435
		if (!empty($modSettings['deny_boards_access']) && count(array_intersect(User::$info->groups, $board_info['deny_groups'])) != 0 && User::$info->is_admin === false)
436 12
		{
437 12
			$board_info['error'] = 'access';
438
		}
439 12
440 12
		// Build up the breadcrumbs.
441
		$context['breadcrumbs'] = array_merge(
442
			$context['breadcrumbs'],
443 12
			[[
444
				'url' => getUrl('action', $modSettings['default_forum_action']) . '#c' . $board_info['cat']['id'],
445 12
				'name' => $board_info['cat']['name']
446 12
			]
447
			],
448
			array_reverse($board_info['parent_boards']),	[
449
				[
450
					'url' => getUrl('board', ['board' => $board, 'start' => '0', 'name' => $board_info['name']]),
451
					'name' => $board_info['name']
452
				]
453 12
			]
454 12
		);
455 12
	}
456 12
457
	// Set the template contextual information.
458
	$context['user']['is_mod'] = (bool) User::$info->is_mod;
459 12
	$context['user']['is_moderator'] = (bool) User::$info->is_moderator;
460
	$context['current_topic'] = $topic;
461
	$context['current_board'] = $board;
462
463
	// Hacker... you can't see this topic, I'll tell you that. (but moderators can!)
464
	if (!empty($board_info['error']) && (!empty($modSettings['deny_boards_access']) || $board_info['error'] !== 'access' || User::$info->is_moderator === false))
465
	{
466
		// The permissions and theme need loading, just to make sure everything goes smoothly.
467
		loadPermissions();
468
		new ElkArte\Themes\ThemeLoader();
469
470
		$_GET['board'] = '';
471
		$_GET['topic'] = '';
472
473
		// The breadcrumbs should not give the game away mate!
474
		$context['breadcrumbs'] = [
475
			[
476
				'url' => $scripturl,
477
				'name' => $context['forum_name_html_safe']
478
			]
479
		];
480
481
		// If it's a prefetching agent, stop it
482
		stop_prefetching();
483
484
		// If we're requesting an attachment.
485
		if (!empty($_REQUEST['action']) && $_REQUEST['action'] === 'dlattach')
486
		{
487
			ob_end_clean();
488
			Headers::instance()
489
				->removeHeader('all')
490
				->headerSpecial('HTTP/1.1 403 Forbidden')
491
				->sendHeaders();
492
			exit;
493
		}
494
495
		if (User::$info->is_guest)
496
		{
497 12
			Txt::load('Errors');
498
			is_not_guest($txt['topic_gone']);
499
		}
500
		else
501 12
		{
502
			if (!empty(User::$info->possibly_robot))
503
			{
504
				Headers::instance()
505
					->removeHeader('all')
506
					->headerSpecial('HTTP/1.1 410 Gone')
507
					->sendHeaders();
508
			}
509
510
			throw new \ElkArte\Exceptions\Exception('topic_gone', false);
511
		}
512
	}
513
514
	if (User::$info->is_mod)
515
	{
516
		User::$info->groups = array_merge(User::$info->groups, [3]);
517 1
	}
518
}
519 1
520
/**
521 1
 * Load this user's permissions.
522
 *
523
 * What it does:
524
 *
525
 * - If the user is an admin, validate that they have not been banned.
526
 * - Attempt to load permissions from cache for cache level > 2
527
 * - See if the user is possibly a robot and apply added permissions as needed
528 1
 * - Load permissions from the general permissions table.
529
 * - If inside a board load the necessary board permissions.
530 1
 * - If the user is not a guest, identify what other boards they have access to.
531
 *
532 1
 * @throws \ElkArte\Exceptions\Exception
533
 */
534
function loadPermissions()
535
{
536
	global $board, $board_info, $modSettings;
537
538
	$db = database();
539
540
	if (User::$info->is_admin)
541
	{
542
		banPermissions();
543
544
		return;
545
	}
546
547
	$permissions = [];
548
	$removals = [];
549
550
	$cache = Cache::instance();
551
	if ($cache->isEnabled())
552
	{
553
		$cache_groups = User::$info->groups;
554
		asort($cache_groups);
0 ignored issues
show
It seems like $cache_groups can also be of type null; however, parameter $array of asort() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

554
		asort(/** @scrutinizer ignore-type */ $cache_groups);
Loading history...
555
		$cache_groups = implode(',', $cache_groups);
556
557
		// If it's a spider then cache it different.
558
		if (User::$info->possibly_robot)
559
		{
560
			$cache_groups .= '-spider';
561
		}
562
563
		$cache_key = 'permissions:' . $cache_groups;
564 1
		$cache_board_key = 'permissions:' . $cache_groups . ':' . $board;
565
566 1
		if ($cache->levelHigherThan(1) && !empty($board) && $cache->getVar($temp, $cache_board_key, 240) && time() - 240 > $modSettings['settings_updated'])
567
		{
568
			[User::$info->permissions] = $temp;
569 1
			banPermissions();
570
571
			return;
572
		}
573
574 1
		if ($cache->getVar($temp, $cache_key, 240) && time() - 240 > $modSettings['settings_updated'] && is_array($temp))
575
		{
576 1
			[$permissions, $removals] = $temp;
577 1
		}
578
	}
579
580 1
	// If it is detected as a robot, and we are restricting permissions as a special group - then implement this.
581 1
	$spider_restrict = User::$info->possibly_robot && !empty($modSettings['spider_group']) ? ' OR (id_group = {int:spider_group} AND add_deny = 0)' : '';
582
583 1
	if (empty($permissions))
584
	{
585
		// Get the general permissions.
586
		$db->fetchQuery('
587
			SELECT
588
				permission, add_deny
589 1
			FROM {db_prefix}permissions
590
			WHERE id_group IN ({array_int:member_groups})
591
				' . $spider_restrict,
592 1
			[
593 1
				'member_groups' => User::$info->groups,
594
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
595 1
			]
596
		)->fetch_callback(
597
			static function ($row) use (&$removals, &$permissions) {
598
				if (empty($row['add_deny']))
599
				{
600
					$removals[] = $row['permission'];
601
				}
602 1
				else
603
				{
604
					$permissions[] = $row['permission'];
605
				}
606
			}
607
		);
608
609
		if (isset($cache_key))
610
		{
611
			$cache->put($cache_key, [$permissions, $removals], 240);
612
		}
613
	}
614
615
	// Get the board permissions.
616
	if (!empty($board))
617
	{
618
		// Make sure the board (if any) has been loaded by loadBoard().
619
		if (!isset($board_info['profile']))
620
		{
621
			throw new \ElkArte\Exceptions\Exception('no_board');
622
		}
623
624
		$db->fetchQuery('
625
			SELECT
626
				permission, add_deny
627
			FROM {db_prefix}board_permissions
628
			WHERE (id_group IN ({array_int:member_groups})
629
				' . $spider_restrict . ')
630
				AND id_profile = {int:id_profile}',
631
			array(
632
				'member_groups' => User::$info->groups,
633
				'id_profile' => $board_info['profile'],
634
				'spider_group' => !empty($modSettings['spider_group']) && $modSettings['spider_group'] != 1 ? $modSettings['spider_group'] : 0,
635
			)
636
		)->fetch_callback(
637
			static function ($row) use (&$removals, &$permissions) {
638
				if (empty($row['add_deny']))
639
				{
640 1
					$removals[] = $row['permission'];
641
				}
642
				else
643
				{
644
					$permissions[] = $row['permission'];
645 1
				}
646
			}
647
		);
648
	}
649
650
	User::$info->permissions = $permissions;
651 1
652
	// Remove all the permissions they shouldn't have ;).
653
	if (!empty($modSettings['permission_enable_deny']))
654 1
	{
655
		User::$info->permissions = array_diff(User::$info->permissions, $removals);
656
	}
657
658
	if (isset($cache_board_key) && !empty($board) && $cache->levelHigherThan(1))
659
	{
660
		$cache->put($cache_board_key, [User::$info->permissions, null], 240);
661
	}
662
663
	// Banned?  Watch, don't touch..
664
	banPermissions();
665
666
	// Load the mod cache, so we can know what additional boards they should see, but no sense in doing it for guests
667 1
	if (User::$info->is_guest === false)
668
	{
669
		User::$info->is_moderator = User::$info->is_mod || allowedTo('moderate_board');
670
		if (!isset($_SESSION['mc']) || $_SESSION['mc']['time'] <= $modSettings['settings_updated'])
671
		{
672
			require_once(SUBSDIR . '/Auth.subs.php');
673
			rebuildModCache();
674
		}
675
		else
676 229
		{
677
			User::$info->mod_cache = $_SESSION['mc'];
678
		}
679 229
	}
680 229
}
681 229
682 229
/**
683
 * Load a theme, by ID.
684
 *
685
 * What it does:
686
 *
687
 * - identify the theme to be loaded.
688
 * - validate that the theme is valid and that the user has permission to use it
689
 * - load the users theme settings and site settings into $options.
690
 * - prepares the list of folders to search for template loading.
691
 * - sets up $context['user']
692
 * - detects the users browser and sets a mobile friendly environment if needed
693
 * - loads default JS variables for use in every theme
694
 * - loads default JS scripts for use in every theme
695
 *
696
 * @param int $id_theme = 0
697
 * @param bool $initialize = true
698
 * @deprecated since 2.0; use the theme object
699
 *
700
 */
701
function loadTheme($id_theme = 0, $initialize = true)
702
{
703
	Errors::instance()->log_deprecated('loadTheme()', \ElkArte\Themes\ThemeLoader::class);
704
	new ThemeLoader($id_theme, $initialize);
705
}
706
707
/**
708
 * Loads basic user information in to $context['user']
709
 */
710
function loadUserContext()
711
{
712
	global $context, $txt, $modSettings;
713
714
	// Set up the contextual user array.
715 229
	$context['user'] = [
716
		'id' => (int) User::$info->id,
717
		'is_logged' => User::$info->is_guest === false,
718 229
		'is_guest' => (bool) User::$info->is_guest,
719 229
		'is_admin' => (bool) User::$info->is_admin,
720 229
		'is_mod' => (bool) User::$info->is_mod,
721 229
		'is_moderator' => (bool) User::$info->is_moderator,
722 229
		// A user can mod if they have permission to see the mod center, or they are a board/group/approval moderator.
723 229
		'can_mod' => (bool) User::$info->canMod($modSettings['postmod_active']),
724 229
		'username' => User::$info->username,
725
		'language' => User::$info->language,
726 229
		'email' => User::$info->email,
727 229
		'ignoreusers' => User::$info->ignoreusers,
728 229
	];
729 229
730 229
	// @todo Base language is being loaded to late, placed here temporarily
731
	Txt::load('index+Addons', true, true);
732
733
	// Something for the guests
734 229
	if (!$context['user']['is_guest'])
735
	{
736 82
		$context['user']['name'] = User::$info->name;
737
	}
738 147
	elseif (!empty($txt['guest_title']))
739
	{
740 146
		$context['user']['name'] = $txt['guest_title'];
741
	}
742
743 229
	loadSmileyEmojiData();
744 229
}
745 229
746 229
/**
747
 * Sets path, set, type and enabled status for smile and emoji
748
 */
749
function loadSmileyEmojiData()
750
{
751
	global $context, $modSettings, $options, $settings;
752
753
	// Using the theme specific or global set
754
	$context['smiley_set'] = empty($settings['smiley_sets_default']) ? $modSettings['smiley_sets_default'] : $settings['smiley_sets_default'];
755
	$context['emoji_set'] = $modSettings['emoji_selection'] ?? 'no-emoji';
756
757
	// Where are current smiley and emoji sets are located
758 231
	$context['smiley_path'] = $modSettings['smileys_url'] . '/' . $context['smiley_set'] . '/';
759
	$context['smiley_dir'] = $modSettings['smileys_dir'] . '/' . $context['smiley_set'] . '/';
760 231
	$context['emoji_path'] = $modSettings['smileys_url'] . '/' . $context['emoji_set'] . '/';
761
762 231
	// Normally set, but after an upgrade can be missing
763
	if (!isset($modSettings['smiley_sets_extensions']))
764
	{
765
		require_once(SUBSDIR . '/Smileys.subs.php');
766
		$modSettings['smiley_sets_extensions'] = setSmileyExtensionArray();
767
	}
768
769 231
	// And what type of smiley library is this, gif, png, etc.
770
	$smiley_sets_extensions = explode(',', $modSettings['smiley_sets_extensions']);
771
	$set_paths = explode(',', $modSettings['smiley_sets_known']);
772
	$context['smiley_extension'] = $smiley_sets_extensions[array_search($context['smiley_set'], $set_paths, true)];
773
	$context['smiley_extension'] = $context['smiley_extension'] ?? 'svg';
774
775
	// Do they even want to see smileys
776
	$context['smiley_enabled'] = empty($options['show_no_smileys']) && $context['smiley_set'] !== 'none';
777
	$context['emoji_enabled'] = empty($options['show_no_smileys']) && $context['emoji_set'] !== 'no-emoji';
778
}
779
780
/**
781
 * This loads the bare minimum data.
782
 *
783
 * @deprecated since 2.0; use the theme object
784
 *
785
 * - Needed by scheduled tasks,
786
 * - Needed by any other code that needs language files before the forum (the theme) is loaded.
787
 */
788
function loadEssentialThemeData()
789
{
790
	Errors::instance()->log_deprecated('loadEssentialThemeData()', '\ElkArte\Themes\ThemeLoader::loadEssentialThemeData()');
791
792
	ThemeLoader::loadEssentialThemeData();
793
}
794
795
/**
796
 * Load a template - if the theme doesn't include it, use the default.
797
 *
798
 * What it does:
799
 *
800
 * - loads a template file with the name template_name from the current, default, or base theme.
801
 * - detects a wrong default theme directory and tries to work around it.
802
 * - can be used to only load style sheets by using false as the template name
803
 *   loading of style sheets with this function is deprecated, use loadCSSFile instead
804
 * - if $settings['template_dirs'] is empty, it delays the loading of the template
805
 *
806
 * @param string|false $template_name
807
 * @param string[]|string $style_sheets any style sheets to load with the template
808
 * @param bool $fatal = true if fatal is true, dies with an error message if the template cannot be found
809
 *
810
 * @return bool|null
811
 * @deprecated since 2.0; use the theme object
812
 *
813
 * @uses the requireTemplate() function to actually load the file.
814
 */
815
function loadTemplate($template_name, $style_sheets = array(), $fatal = true)
816
{
817
	Errors::instance()->log_deprecated('loadTemplate()', 'theme()->getTemplates()->load()');
818
819
	return theme()->getTemplates()->load($template_name, $style_sheets, $fatal);
820
}
821
822
/**
823
 * Load a sub-template.
824
 *
825
 * What it does:
826
 *
827
 * - loads the sub template specified by sub_template_name, which must be in an already-loaded template.
828
 * - if ?debug is in the query string, shows administrators a marker after every sub template
829
 * for debugging purposes.
830
 *
831
 * @param string $sub_template_name
832
 * @param bool|string $fatal = false
833
 * - $fatal = true is for templates that shouldn't get a 'pretty' error screen
834
 * - $fatal = 'ignore' to skip
835
 *
836
 * @return bool
837
 * @deprecated since 2.0; use the theme object
838
 */
839
function loadSubTemplate($sub_template_name, $fatal = false)
840
{
841
	Errors::instance()->log_deprecated('loadSubTemplate()', 'theme()->getTemplates()->loadSubTemplate()');
842
	theme()->getTemplates()->loadSubTemplate($sub_template_name, $fatal);
843
844
	return true;
845
}
846
847
/**
848
 * Add a CSS file for output later
849
 *
850
 * @param string[]|string $filenames string or array of filenames to work on
851
 * @param array $params = array()
852
 * Keys are the following:
853
 * - ['local'] (true/false): define if the file is local
854
 * - ['fallback'] (true/false): if false  will attempt to load the file
855
 *   from the default theme if not found in the current theme
856 229
 * - ['stale'] (true/false/string): if true or null, use cache stale,
857
 *   false do not, or used a supplied string
858 229
 * @param string $id optional id to use in html id=""
859
 */
860
function loadCSSFile($filenames, $params = array(), $id = '')
861
{
862
	global $context;
863 229
864
	if (empty($filenames))
865 229
	{
866
		return;
867
	}
868 229
869
	if (!is_array($filenames))
870 12
	{
871
		$filenames = [$filenames];
872
	}
873 229
874 229
	if (in_array('admin.css', $filenames))
875 229
	{
876 229
		$filenames[] = $context['theme_variant'] . '/admin' . $context['theme_variant'] . '.css';
877
	}
878 229
879 229
	$params['subdir'] = $params['subdir'] ?? 'css';
880
	$params['extension'] = 'css';
881
	$params['index_name'] = 'css_files';
882
	$params['debug_index'] = 'sheets';
883
884
	loadAssetFile($filenames, $params, $id);
885
}
886
887
/**
888
 * Add a Javascript file for output later
889
 *
890
 * What it does:
891
 *
892
 * - Can be passed an array of filenames, all which will have the same
893
 *   parameters applied,
894
 * - if you need specific parameters on a per file basis, call it multiple times
895
 *
896
 * @param string[]|string $filenames string or array of filenames to work on
897
 * @param array $params = array()
898
 * Keys are the following:
899
 * - ['local'] (true/false): define if the file is local, if file does not
900
 *     start with http its assumed local
901
 * - ['defer'] (true/false): define if the file should load in <head> with the
902
 *     defer attribute (script is fetched asynchronously) and run after page is loaded
903
 * - ['fallback'] (true/false): if true will attempt to load the file from the
904
 *     default theme if not found in the current this is the default behavior
905
 *     if this is not supplied
906
 * - ['async'] (true/false): if the script should be loaded asynchronously and
907 231
 *    as soon as its loaded, interrupt parsing to run
908
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
909
 *     not, or used a supplied string
910
 * @param string $id = '' optional id to use in html id=""
911
 */
912 231
function loadJavascriptFile($filenames, $params = array(), $id = '')
913 231
{
914 231
	if (empty($filenames))
915 231
	{
916
		return;
917 231
	}
918 231
919
	$params['subdir'] = $params['subdir'] ?? 'scripts';
920
	$params['extension'] = 'js';
921
	$params['index_name'] = 'js_files';
922
	$params['debug_index'] = 'javascript';
923
924
	loadAssetFile($filenames, $params, $id);
925
}
926
927
/**
928
 * Add an asset (css, js or other) file for output later
929
 *
930
 * What it does:
931
 *
932
 * - Can be passed an array of filenames, all which will have the same
933
 *   parameters applied,
934
 * - If you need specific parameters on a per file basis, call it multiple times
935
 *
936
 * @param string[]|string $filenames string or array of filenames to work on
937
 * @param array $params = array()
938
 * Keys are the following:
939
 * - ['subdir'] (string): the subdirectory of the theme dir the file is in
940
 * - ['extension'] (string): the extension of the file (e.g. css)
941
 * - ['index_name'] (string): the $context index that holds the array of loaded
942
 *     files
943
 * - ['debug_index'] (string): the index that holds the array of loaded
944
 *     files for debugging debug
945
 * - ['local'] (true/false): define if the file is local, if file does not
946
 *     start with http or // (schema-less URLs) its assumed local.
947
 *     The parameter is in fact useful only for files whose name starts with
948
 *     http, in any other case (e.g. passing a local URL) the parameter would
949
 *     fail in properly adding the file to the list.
950
 * - ['defer'] (true/false): define if the file should load in <head> or before
951
 *     the closing <html> tag
952
 * - ['fallback'] (true/false): if true will attempt to load the file from the
953
 *     default theme if not found in the current this is the default behavior
954
 *     if this is not supplied
955 231
 * - ['async'] (true/false): if the script should be loaded asynchronously (HTML5)
956
 * - ['stale'] (true/false/string): if true or null, use cache stale, false do
957 231
 *     not, or used a supplied string
958
 * @param string $id = '' optional id to use in html id=""
959
 */
960
function loadAssetFile($filenames, $params = array(), $id = '')
961
{
962 231
	global $settings, $context, $db_show_debug;
963
964 231
	if (empty($filenames))
965
	{
966 161
		return;
967
	}
968
969
	$cache = Cache::instance();
970 231
	$fileFunc = FileFunctions::instance();
971
972 231
	if (!is_array($filenames))
973
	{
974
		$filenames = array($filenames);
975
	}
976
977
	// Static values for all these settings
978
	$staler_string = '';
979
	if (!isset($params['stale']) || $params['stale'] === true)
980
	{
981
		$staler_string = CACHE_STALE;
982
	}
983 231
	elseif (is_string($params['stale']))
984 231
	{
985
		$staler_string = ($params['stale'][0] === '?' ? $params['stale'] : '?' . $params['stale']);
986
	}
987 231
988 231
	$fallback = !isset($params['fallback']) || $params['fallback'] !== false;
989 231
	$dir = '/' . $params['subdir'] . '/';
990
991
	// Whoa ... we've done this before yes?
992
	$cache_name = 'load_' . $params['extension'] . '_' . hash('md5', $settings['theme_dir'] . implode('_', $filenames));
993
	$temp = [];
994
	if ($cache->getVar($temp, $cache_name, 600))
995
	{
996
		if (empty($context[$params['index_name']]))
997
		{
998
			$context[$params['index_name']] = [];
999
		}
1000
1001
		$context[$params['index_name']] += $temp;
1002
1003
		if ($db_show_debug === true)
1004
		{
1005
			foreach ($temp as $temp_params)
1006
			{
1007
				Debug::instance()->add($params['debug_index'], $temp_params['options']['basename'] . '(' . (empty($temp_params['options']['local']) ? '' : (empty($temp_params['options']['url']) ? basename($temp_params['options']['dir']) : basename($temp_params['options']['url']))) . ')');
1008 231
			}
1009
		}
1010
	}
1011 231
	else
1012
	{
1013
		$this_build = [];
1014 231
1015 231
		// All the files in this group use the above parameters
1016
		foreach ($filenames as $filename)
1017
		{
1018
			// Account for shorthand like admin.ext?xyz11 filenames
1019
			$has_cache_staler = strpos($filename, '.' . $params['extension'] . '?');
1020
			$cache_staler = $staler_string;
1021
			if ($has_cache_staler)
1022 231
			{
1023 231
				$params['basename'] = substr($filename, 0, $has_cache_staler + strlen($params['extension']) + 1);
1024
			}
1025 231
			else
1026
			{
1027
				$params['basename'] = $filename;
1028 231
			}
1029
1030 231
			$this_id = empty($id) ? str_replace('?', '_', basename($filename)) : $id;
1031 231
1032 231
			// Is this a local file?
1033
			if (!empty($params['local']) || (strpos($filename, 'http') !== 0 && strpos($filename, '//') !== 0))
1034
			{
1035 231
				$params['local'] = true;
1036
				$params['dir'] = $settings['theme_dir'] . $dir;
1037
				$params['url'] = $settings['theme_url'];
1038
1039
				// Fallback if we are not already in the default theme
1040
				if ($fallback && ($settings['theme_dir'] !== $settings['default_theme_dir']) && !$fileFunc->fileExists($settings['theme_dir'] . $dir . $params['basename']))
1041
				{
1042
					// Can't find it in this theme, how about the default?
1043
					$filename = false;
1044
					if ($fileFunc->fileExists($settings['default_theme_dir'] . $dir . $params['basename']))
1045
					{
1046
						$filename = $settings['default_theme_url'] . $dir . $params['basename'] . $cache_staler;
1047
						$params['dir'] = $settings['default_theme_dir'] . $dir;
1048
						$params['url'] = $settings['default_theme_url'];
1049
					}
1050
				}
1051 231
				else
1052
				{
1053
					$filename = $settings['theme_url'] . $dir . $params['basename'] . $cache_staler;
1054
				}
1055
			}
1056 231
1057
			// Add it to the array for use in the template
1058 231
			if (!empty($filename))
1059
			{
1060 231
				$this_build[$this_id] = array('filename' => $filename, 'options' => $params);
1061
				$context[$params['index_name']][$this_id] = $this_build[$this_id];
1062
1063
				if ($db_show_debug === true)
1064
				{
1065
					Debug::instance()->add($params['debug_index'], $params['basename'] . '(' . (empty($params['local']) ? '' : (empty($params['url']) ? basename($params['dir']) : basename($params['url']))) . ')');
1066
				}
1067 231
			}
1068
1069
			// Save it, so we don't have to build this so often
1070 231
			$cache->put($cache_name, $this_build, 600);
1071
		}
1072
	}
1073
}
1074
1075
/**
1076
 * Add a Javascript variable for output later (for feeding text strings and similar to JS)
1077
 *
1078
 * @param array $vars array of vars to include in the output done as 'varname' => 'var value'
1079
 * @param bool $escape = false, whether or not to escape the value
1080
 * @deprecated since 2.0; use the theme object
1081
 *
1082
 */
1083
function addJavascriptVar($vars, $escape = false)
1084
{
1085
	Errors::instance()->log_deprecated('addJavascriptVar()', 'theme()->getTemplates()->addJavascriptVar()');
1086
	theme()->addJavascriptVar($vars, $escape);
1087
}
1088
1089
/**
1090
 * Add a block of inline Javascript code to be executed later
1091
 *
1092
 * What it does:
1093
 *
1094
 * - only use this if you have to, generally external JS files are better, but for very small scripts
1095
 *   or for scripts that require help from PHP/whatever, this can be useful.
1096
 * - all code added with this function is added to the same <script> tag so do make sure your JS is clean!
1097
 *
1098
 * @param string $javascript
1099
 * @param bool $defer = false, define if the script should load in <head> or before the closing <html> tag
1100
 * @deprecated since 2.0; use the theme object
1101
 *
1102
 */
1103
function addInlineJavascript($javascript, $defer = false)
1104
{
1105
	Errors::instance()->log_deprecated('addInlineJavascript()', 'theme()->addInlineJavascript()');
1106
	theme()->addInlineJavascript($javascript, $defer);
1107
}
1108
1109
/**
1110
 * Load a language file.
1111
 *
1112
 * - Tries the current and default themes as well as the user and global languages.
1113
 *
1114
 * @param string $template_name
1115
 * @param string $lang = ''
1116
 * @param bool $fatal = true
1117
 * @param bool $force_reload = false
1118
 * @deprecated since 2.0; use the theme object
1119
 *
1120
 */
1121
function loadLanguage($template_name, $lang = '', $fatal = true, $force_reload = false)
1122
{
1123
	Txt::load($template_name, $lang, $fatal);
1124
}
1125
1126
/**
1127
 * Get all parent boards (requires first parent as parameter)
1128
 *
1129
 * What it does:
1130
 *
1131
 * - It finds all the parents of id_parent, and that board itself.
1132
 * - Additionally, it detects the moderators of said boards.
1133
 * - Returns an array of information about the boards found.
1134
 *
1135
 * @param int $id_parent
1136
 *
1137 1
 * @return array
1138
 * @throws \ElkArte\Exceptions\Exception parent_not_found
1139 1
 */
1140 1
function getBoardParents($id_parent)
1141 1
{
1142 1
	$db = database();
1143 1
	$cache = Cache::instance();
1144 1
	$boards = array();
1145 1
1146 1
	// First check if we have this cached already.
1147
	if (!$cache->getVar($boards, 'board_parents-' . $id_parent, 480))
1148 1
	{
1149 1
		$boards = array();
1150 1
		$original_parent = $id_parent;
1151 1
1152 1
		// Loop while the parent is non-zero.
1153 1
		while ($id_parent != 0)
1154 1
		{
1155 1
			$result = $db->query('', '
1156
				SELECT
1157 1
					b.id_parent, b.name, {int:board_parent} AS id_board, COALESCE(mem.id_member, 0) AS id_moderator,
1158 1
					mem.real_name, b.child_level
1159 1
				FROM {db_prefix}boards AS b
1160 1
					LEFT JOIN {db_prefix}moderators AS mods ON (mods.id_board = b.id_board)
1161 1
					LEFT JOIN {db_prefix}members AS mem ON (mem.id_member = mods.id_member)
1162 1
				WHERE b.id_board = {int:board_parent}',
1163 1
				array(
1164 1
					'board_parent' => $id_parent,
1165 1
				)
1166 1
			);
1167 1
			// In the EXTREMELY unlikely event this happens, give an error message.
1168 1
			if ($result->num_rows() === 0)
1169 1
			{
1170
				throw new \ElkArte\Exceptions\Exception('parent_not_found', 'critical');
1171 1
			}
1172 1
1173 1
			while (($row = $result->fetch_assoc()))
1174 1
			{
1175 1
				if (!isset($boards[$row['id_board']]))
1176 1
				{
1177 1
					$id_parent = $row['id_parent'];
1178 1
					$boards[$row['id_board']] = array(
1179 1
						'url' => getUrl('board', ['board' => $row['id_board'], 'name' => $row['name'], 'start' => '0']),
1180 1
						'name' => $row['name'],
1181 1
						'level' => $row['child_level'],
1182 1
						'moderators' => array()
1183 1
					);
1184
				}
1185 1
1186 1
				// If a moderator exists for this board, add that moderator for all children too.
1187 1
				if (!empty($row['id_moderator']))
1188 1
				{
1189 1
					foreach (array_keys($boards) as $id)
1190 1
					{
1191 1
						$boards[$id]['moderators'][$row['id_moderator']] = array(
1192 1
							'id' => $row['id_moderator'],
1193 1
							'name' => $row['real_name'],
1194 1
							'href' => getUrl('profile', ['action' => 'profile', 'u' => $row['id_moderator']]),
1195 1
							'link' => '<a href="' . getUrl('profile', ['action' => 'profile', 'u' => $row['id_moderator']]) . '">' . $row['real_name'] . '</a>'
1196 1
						);
1197 1
					}
1198
				}
1199 1
			}
1200
1201
			$result->free_result();
1202
		}
1203
1204
		$cache->put('board_parents-' . $original_parent, $boards, 480);
1205
	}
1206
1207
	return $boards;
1208
}
1209
1210
/**
1211
 * Attempt to reload our known languages.
1212
 *
1213
 * @param bool $use_cache = true
1214
 *
1215
 * @return array
1216
 */
1217 34
function getLanguages($use_cache = true)
1218
{
1219 34
	$cache = Cache::instance();
1220 34
1221 34
	// Either we don't use the cache, or its expired.
1222
	$languages = [];
1223
	$language_dir = SOURCEDIR . '/ElkArte/Languages/Index';
1224 34
1225
	if (!$use_cache || !$cache->getVar($languages, 'known_languages', $cache->levelLowerThan(2) ? 86400 : 3600))
1226 34
	{
1227 34
		$dir = dir($language_dir . '/');
1228
		while (($entry = $dir->read()))
1229
		{
1230 34
			if ($entry === '.' || $entry === '..')
1231
			{
1232 22
				continue;
1233
			}
1234
1235
			$basename = basename($entry, '.php');
1236
			$languages[$basename] = array(
1237
				'name' => $basename,
1238
				'selected' => false,
1239
				'filename' => $entry,
1240
				'location' => $language_dir . '/' . $entry,
1241 22
			);
1242
		}
1243
1244
		$dir->close();
1245 22
1246
		// Let's cash in on this deal.
1247
		$cache->put('known_languages', $languages, $cache->isEnabled() && $cache->levelLowerThan(1) ? 86400 : 3600);
1248
	}
1249 22
1250
	return $languages;
1251 22
}
1252
1253 22
/**
1254 22
 * Initialize a database connection.
1255 22
 */
1256 22
function loadDatabase()
1257 22
{
1258
	global $db_prefix, $db_name;
1259
1260
	// Database stuffs
1261
	require_once(SOURCEDIR . '/database/Database.subs.php');
1262
1263 22
	// Safeguard here, if there isn't a valid connection lets put a stop to it.
1264
	try
1265
	{
1266
		$db = database(false);
1267
	}
1268
	catch (Exception)
1269
	{
1270
		Errors::instance()->display_db_error();
1271
	}
1272
1273
	// If in SSI mode fix up the prefix.
1274
	if (ELK === 'SSI')
1275
	{
1276 22
		$db_prefix = $db->fix_prefix($db_prefix, $db_name);
1277
	}
1278
	// Case-sensitive database? Let's define a constant.
1279 34
	// @NOTE: I think it is already taken care by the abstraction, it should be possible to remove
1280
	if (!$db->case_sensitive())
1281
	{
1282 34
		return;
1283
	}
1284
1285
	if (defined('DB_CASE_SENSITIVE'))
1286
	{
1287
		return;
1288
	}
1289
1290
	define('DB_CASE_SENSITIVE', '1');
1291
}
1292
1293
/**
1294 4
 * Determine the user's avatar type and return the information as an array
1295
 *
1296 4
 * @param array $profile array containing the users profile data
1297
 *
1298
 * @return array $avatar
1299 4
 *
1300
 * @event integrate_avatar allows access to $avatar array before it is returned
1301 4
 */
1302
function determineAvatar($profile)
1303
{
1304 4
	global $modSettings, $settings, $context;
1305
1306
	if (empty($profile))
1307
	{
1308
		return [];
1309
	}
1310
1311 4
	$avatar_protocol = empty($profile['avatar']) ? '' : strtolower(substr($profile['avatar'], 0, 7));
1312 4
	$alt = $profile['member_name'] ?? '';
1313
1314
	// Build the gravatar request once.
1315
	$gravatar = '//www.gravatar.com/avatar/' .
1316 4
		hash('md5', strtolower($profile['email_address'] ?? '')) .
1317
		'?s=' . $modSettings['avatar_max_height'] .
1318
		(empty($modSettings['gravatar_rating'])
1319
			? ('')
1320
			: '&amp;r=' . $modSettings['gravatar_rating']) .
1321
		((!empty($modSettings['gravatar_default']) && $modSettings['gravatar_default'] !== 'none')
1322 4
			? ('&amp;d=' . $modSettings['gravatar_default'])
1323
			: '');
1324 4
1325
	// uploaded avatar?
1326
	if ($profile['id_attach'] > 0 && empty($profile['avatar']))
1327 4
	{
1328
		// where are those pesky avatars?
1329
		$avatar_url = empty($profile['attachment_type']) ? getUrl('action', ['action' => 'dlattach', 'attach' => $profile['id_attach'], 'type' => 'avatar']) : $modSettings['custom_avatar_url'] . '/' . $profile['filename'];
1330
1331
		$avatar = [
1332 4
			'name' => $profile['avatar'],
1333 4
			'image' => '<img class="avatar avatarresize" src="' . $avatar_url . '" alt="' . $alt . '" loading="lazy" />',
1334
			'href' => $avatar_url,
1335
			'url' => '',
1336 4
		];
1337
	}
1338 4
	// remote avatar?
1339
	elseif ($avatar_protocol === 'http://' || $avatar_protocol === 'https:/')
1340
	{
1341
		$avatar = [
1342 4
			'name' => $profile['avatar'],
1343 4
			'image' => '<img class="avatar avatarresize" src="' . $profile['avatar'] . '" alt="' . $alt . '" loading="lazy" />',
1344
			'href' => $profile['avatar'],
1345
			'url' => $profile['avatar'],
1346 4
		];
1347
	}
1348 4
	// Gravatar instead?
1349
	elseif (!empty($profile['avatar']) && $profile['avatar'] === 'gravatar')
1350
	{
1351 4
		// Gravatars URL.
1352 4
		$gravatar_url = $gravatar;
1353
		$avatar = array(
1354 4
			'name' => $profile['avatar'],
1355 4
			'image' => '<img class="avatar avatarresize" src="' . $gravatar_url . '" alt="' . $alt . '" loading="lazy" />',
1356
			'href' => $gravatar_url,
1357
			'url' => $gravatar_url,
1358 4
		);
1359
	}
1360 4
	// an avatar from the gallery?
1361
	elseif (!empty($profile['avatar']) && ($avatar_protocol !== 'http://' && $avatar_protocol !== 'https:/'))
1362
	{
1363
		$avatar = [
1364 4
			'name' => $profile['avatar'],
1365
			'image' => '<img class="avatar avatarresize" src="' . $modSettings['avatar_url'] . '/' . $profile['avatar'] . '" alt="' . $alt . '" loading="lazy" />',
1366
			'href' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
1367 4
			'url' => $modSettings['avatar_url'] . '/' . $profile['avatar'],
1368
		];
1369
	}
1370
	// no custom avatar found yet, maybe a default avatar?
1371
	elseif (!empty($modSettings['avatar_default']) && empty($profile['avatar']) && empty($profile['filename']))
1372
	{
1373
		// $settings not initialized? We can't do anything further..
1374
		if (!empty($settings))
1375 1
		{
1376
			if (!empty($modSettings['avatar_gravatar_enabled']) && !empty($modSettings['gravatar_as_default'])
1377
				&& $modSettings['gravatar_default'] !== 'none')
1378 1
			{
1379
				$href = $gravatar;
1380
			}
1381
			else
1382
			{
1383 1
				// Use the theme, or its variants, default image
1384
				$href = $settings['images_url'] . '/default_avatar.png';
1385
				$href_var = $settings['actual_theme_dir'] . '/images/' . $context['theme_variant'] . '/default_avatar.png';
1386
1387
				if (!empty($context['theme_variant'])
1388
					&& FileFunctions::instance()->fileExists($href_var))
1389
				{
1390
					$href = $settings['images_url'] . '/' . $context['theme_variant'] . '/default_avatar.png';
1391 1
				}
1392
			}
1393
1394
			// Let's proceed with the default avatar.
1395
			// TODO: This should be incorporated into the theme.
1396
			$avatar = [
1397
				'name' => '',
1398 1
				'image' => '<img class="avatar avatarresize" src="' . $href . '" alt="' . $alt . '" loading="lazy" />',
1399
				'href' => $href,
1400
				'url' => 'https://',
1401
			];
1402 1
		}
1403
		else
1404
		{
1405
			$avatar = [];
1406
		}
1407
	}
1408
	// finally ...
1409
	else
1410
	{
1411
		$avatar = [
1412
			'name' => '',
1413
			'image' => '',
1414
			'href' => '',
1415
			'url' => ''
1416 13
		];
1417
	}
1418 13
1419
	// Make sure there's a preview for gravatars available.
1420
	$avatar['gravatar_preview'] = $gravatar;
1421
1422
	call_integration_hook('integrate_avatar', array(&$avatar, $profile));
1423 13
1424
	return $avatar;
1425
}
1426 13
1427
/**
1428
 * Get information about the server
1429
 *
1430
 * @return Server
1431
 */
1432
function detectServer()
1433
{
1434
	global $context;
1435
	static $server = null;
1436
1437
	if ($server === null)
1438
	{
1439 13
		$server = new Server($_SERVER);
1440
		$servers = ['iis', 'apache', 'litespeed', 'lighttpd', 'nginx', 'cgi', 'windows'];
1441
		$context['server'] = array();
1442
		foreach ($servers as $name)
1443
		{
1444
			$context['server']['is_' . $name] = $server->is($name);
1445
		}
1446
1447
		$context['server']['iso_case_folding'] = $server->is('iso_case_folding');
1448
	}
1449 13
1450
	return $server;
1451
}
1452
1453
/**
1454
 * Returns if a webserver is of type server (apache, nginx, etc)
1455
 *
1456
 * @param $server
1457
 *
1458
 * @return bool
1459
 */
1460
function serverIs($server)
1461
{
1462 13
	return detectServer()->is($server);
1463
}
1464
1465
/**
1466
 * Do some important security checks:
1467
 *
1468
 * What it does:
1469
 *
1470
 * - Checks the existence of critical files e.g. install.php
1471
 * - Checks for an active admin session.
1472 13
 * - Checks cache directory is writable.
1473
 * - Calls secureDirectory to protect attachments & cache.
1474
 * - Checks if the forum is in maintenance mode.
1475
 */
1476
function doSecurityChecks()
1477
{
1478
	global $modSettings, $context, $maintenance, $txt, $options;
1479
1480
	$show_warnings = false;
1481
1482
	$cache = Cache::instance();
1483
1484
	if (User::$info->is_guest === false && allowedTo('admin_forum'))
1485
	{
1486
		// If agreement is enabled, at least the english version shall exists
1487
		if ($modSettings['requireAgreement'] && !file_exists(SOURCEDIR . '/ElkArte/Languages/Agreement/English.txt'))
1488
		{
1489
			$context['security_controls_files']['title'] = $txt['generic_warning'];
1490
			$context['security_controls_files']['errors']['agreement'] = $txt['agreement_missing'];
1491
			$show_warnings = true;
1492
		}
1493
1494
		// Cache directory writable?
1495 13
		if ($cache->isEnabled() && !is_writable(CACHEDIR))
1496
		{
1497
			$context['security_controls_files']['title'] = $txt['generic_warning'];
1498
			$context['security_controls_files']['errors']['cache'] = $txt['cache_writable'];
1499
			$show_warnings = true;
1500
		}
1501
1502
		if (checkSecurityFiles())
1503 13
		{
1504
			$show_warnings = true;
1505 13
		}
1506
1507 13
		// We are already checking so many files...just few more doesn't make any difference! :P
1508
		$attachmentsDir = new AttachmentsDirectory($modSettings, database());
1509
		$path = $attachmentsDir->getCurrent();
1510
		secureDirectory($path, true);
1511
		secureDirectory(CACHEDIR, false, '"\.(js|css)$"');
1512
1513
		// Active admin session?
1514
		if (isAdminSessionActive())
1515 288
		{
1516 288
			$context['warning_controls']['admin_session'] = sprintf($txt['admin_session_active'], (getUrl('admin', ['action' => 'admin', 'area' => 'adminlogoff', 'redir', '{session_data}'])));
1517
		}
1518 288
1519
		// Maintenance mode enabled?
1520 1
		if (!empty($maintenance))
1521 1
		{
1522 1
			$context['warning_controls']['maintenance'] = sprintf($txt['admin_maintenance_active'], (getUrl('admin', ['action' => 'admin', 'area' => 'serversettings', '{session_data}'])));
1523 1
		}
1524
1525 1
		// New updates
1526
		if (defined('FORUM_VERSION'))
1527
		{
1528 1
			$index = 'new_in_' . str_replace(array('ElkArte ', '.'), array('', '_'), FORUM_VERSION);
1529
			if (!empty($modSettings[$index]) && empty($options['dismissed_' . $index]))
1530 1
			{
1531
				$show_warnings = true;
1532
				$context['new_version_updates'] = array(
1533 288
					'title' => $txt['new_version_updates'],
1534
					'errors' => array(replaceBasicActionUrl($txt['new_version_updates_text'])),
1535
				);
1536
			}
1537
		}
1538
	}
1539
1540
	// Check for database errors.
1541
	if (!empty($_SESSION['query_command_denied']))
1542
	{
1543
		if (User::$info->is_admin)
1544
		{
1545
			$context['security_controls_query']['title'] = $txt['query_command_denied'];
1546
			$show_warnings = true;
1547
			foreach ($_SESSION['query_command_denied'] as $command => $error)
1548
			{
1549
				$context['security_controls_query']['errors'][$command] = '<pre>' . Util::htmlspecialchars($error) . '</pre>';
1550
			}
1551
		}
1552
		else
1553
		{
1554
			$context['security_controls_query']['title'] = $txt['query_command_denied_guests'];
1555
			foreach ($_SESSION['query_command_denied'] as $command => $error)
1556
			{
1557
				$context['security_controls_query']['errors'][$command] = '<pre>' . sprintf($txt['query_command_denied_guests_msg'], Util::htmlspecialchars($command)) . '</pre>';
1558
			}
1559
		}
1560
	}
1561
1562
	// Are there any members waiting for approval?
1563
	if (allowedTo('moderate_forum') && ((!empty($modSettings['registration_method']) && $modSettings['registration_method'] == 2) || !empty($modSettings['approveAccountDeletion'])) && !empty($modSettings['unapprovedMembers']))
1564
	{
1565
		$context['warning_controls']['unapproved_members'] = sprintf($txt[$modSettings['unapprovedMembers'] == 1 ? 'approve_one_member_waiting' : 'approve_many_members_waiting'], getUrl('admin', ['action' => 'admin', 'area' => 'viewmembers', 'sa' => 'browse', 'type' => 'approve']), $modSettings['unapprovedMembers']);
1566
	}
1567
1568
	if (!empty($context['open_mod_reports']) && (empty(User::$settings['mod_prefs']) || User::$settings['mod_prefs'][0] == 1))
1569
	{
1570
		$context['warning_controls']['open_mod_reports'] = '<a href="' . getUrl('action', ['action' => 'moderate', 'area' => 'reports']) . '">' . sprintf($txt['mod_reports_waiting'], $context['open_mod_reports']) . '</a>';
1571
	}
1572
1573
	if (!empty($context['open_pm_reports']) && allowedTo('admin_forum'))
1574
	{
1575
		$context['warning_controls']['open_pm_reports'] = '<a href="' . getUrl('action', ['action' => 'moderate', 'area' => 'pm_reports']) . '">' . sprintf($txt['pm_reports_waiting'], $context['open_pm_reports']) . '</a>';
1576
	}
1577
1578
	if (isset($_SESSION['ban']['cannot_post']))
1579
	{
1580
		// An admin cannot be banned (technically he could), and if it is better he knows.
1581
		$context['security_controls_ban']['title'] = sprintf($txt['you_are_post_banned'], User::$info->is_guest ? $txt['guest_title'] : User::$info->name);
1582
		$show_warnings = true;
1583
1584
		$context['security_controls_ban']['errors']['reason'] = '';
1585
1586
		if (!empty($_SESSION['ban']['cannot_post']['reason']))
1587
		{
1588
			$context['security_controls_ban']['errors']['reason'] = $_SESSION['ban']['cannot_post']['reason'];
1589
		}
1590
1591
		if (!empty($_SESSION['ban']['expire_time']))
1592
		{
1593
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . sprintf($txt['your_ban_expires'], standardTime($_SESSION['ban']['expire_time'], false)) . '</span>';
1594
		}
1595
		else
1596
		{
1597
			$context['security_controls_ban']['errors']['reason'] .= '<span class="smalltext">' . $txt['your_ban_expires_never'] . '</span>';
1598
		}
1599
	}
1600
1601
	// Finally, let's show the layer.
1602
	if ($show_warnings || !empty($context['warning_controls']))
1603
	{
1604
		theme()->getLayers()->addAfter('admin_warning', 'body');
1605
	}
1606
}
1607
1608
/**
1609
 * Load everything necessary for the BBC parsers
1610
 */
1611
function loadBBCParsers()
1612
{
1613
	global $modSettings;
1614
1615
	// Set the default disabled BBC
1616
	if (!empty($modSettings['disabledBBC']))
1617
	{
1618
		if (!is_array($modSettings['disabledBBC']))
1619
		{
1620
			$disabledBBC = explode(',', $modSettings['disabledBBC']);
1621
		}
1622
		else
1623
		{
1624
			$disabledBBC = $modSettings['disabledBBC'];
1625
		}
1626
1627
		ParserWrapper::instance()->setDisabled(empty($disabledBBC) ? array() : $disabledBBC);
1628
	}
1629
1630
	return 1;
1631
}
1632
1633
/**
1634
 * This is necessary to support data stored in the pre-1.0.8 way (i.e. serialized)
1635
 *
1636
 * @param string $variable The string to convert
1637
 * @param null|callable $save_callback The function that will save the data to the db
1638
 * @return array the array
1639
 */
1640
function serializeToJson($variable, $save_callback = null)
1641
{
1642
	$array_form = json_decode($variable, true);
1643
1644
	// decoding failed, let's try with unserialize
1645
	if (!is_array($array_form))
1646
	{
1647
		try
1648
		{
1649
			$array_form = Util::unserialize($variable);
1650
		}
1651
		catch (Exception)
1652
		{
1653
			$array_form = false;
1654
		}
1655
1656
		// If unserialize fails as well, let's just store an empty array
1657
		if ($array_form === false)
1658
		{
1659
			$array_form = array(0, '', 0);
1660
		}
1661
1662
		// Time to update the value if necessary
1663
		if ($save_callback !== null)
1664
		{
1665
			$save_callback($array_form);
1666
		}
1667
	}
1668
1669
	return $array_form;
1670
}
1671