Passed
Pull Request — development (#3540)
by Emanuele
07:11
created

ThemeLoader::loadLanguageFiles()   A

Complexity

Conditions 2
Paths 2

Size

Total Lines 20
Code Lines 7

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 6

Importance

Changes 0
Metric Value
cc 2
eloc 7
nc 2
nop 4
dl 0
loc 20
ccs 0
cts 0
cp 0
crap 6
rs 10
c 0
b 0
f 0
1
<?php
2
3
/**
4
 * The main abstract theme class
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
 * @version 2.0 dev
11
 *
12
 */
13
14
namespace ElkArte\Themes;
15
16
use ElkArte\Cache\Cache;
17
use ElkArte\Errors\Errors;
18
use ElkArte\ext\Composer\Autoload\ClassLoader;
0 ignored issues
show
Bug introduced by
The type ElkArte\ext\Composer\Autoload\ClassLoader was not found. Maybe you did not declare it correctly or list all dependencies?

The issue could also be caused by a filter entry in the build configuration. If the path has been excluded in your configuration, e.g. excluded_paths: ["lib/*"], you can move it to the dependency path list as follows:

filter:
    dependency_paths: ["lib/*"]

For further information see https://scrutinizer-ci.com/docs/tools/php/php-scrutinizer/#list-dependency-paths

Loading history...
19
use ElkArte\Hooks;
20
use ElkArte\HttpReq;
21
use ElkArte\User;
22
use ElkArte\UserInfo;
23
use ElkArte\Util;
24
use ElkArte\Debug;
25
use ElkArte\Languages\Txt;
26
27
/**
28
 * Class ThemeLoader
29
 */
30
class ThemeLoader
31
{
32
	/** @var mixed|\ElkArte\ValuesContainer */
33
	public $user;
34
35
	/** @var int The id of the theme being used */
36
	private $id;
37
38
	/** @var Theme The current theme. */
39
	private $theme;
40
41
	/** @var Directories The list of directories. */
42
	protected static $dirs;
43
44
	/**
45
	 * Load a theme, by ID.
46
	 *
47
	 * What it does:
48
	 * - identify the theme to be loaded.
49
	 * - validate that the theme is valid and that the user has permission to use it
50
	 * - load the users theme settings and site settings into $options.
51
	 * - prepares the list of folders to search for template loading.
52
	 * - identify what smiley set to use.
53 229
	 * - sets up $context['user']
54
	 * - detects the users browser and sets a mobile friendly environment if needed
55 229
	 * - loads default JS variables for use in every theme
56 229
	 * - loads default JS scripts for use in every theme
57
	 *
58 229
	 * @param int $id_theme = 0
59 229
	 * @param bool $initialize = true
60 229
	 */
61
	public function __construct($id_theme = 0, $initialize = true)
62 229
	{
63
		global $txt, $scripturl, $mbname, $modSettings, $context, $settings, $options;
64
65
		$this->user = User::$info;
66
		$this->id = $id_theme;
67 229
		$this->initTheme();
68 229
69
		if (!$initialize)
70
		{
71 229
			return;
72
		}
73
74
		$this->loadThemeUrls();
75
		loadUserContext();
76
77
		// Set up some additional interface preference context
78
		if (!empty($options['admin_preferences']))
79
		{
80
			$context['admin_preferences'] = serializeToJson($options['admin_preferences'], function ($array_form) {
81
				global $context;
82
83 229
				$context['admin_preferences'] = $array_form;
84
				require_once(SUBSDIR . '/Admin.subs.php');
85
				updateAdminPreferences();
86 229
			});
87
		}
88 82
		else
89
		{
90
			$context['admin_preferences'] = [];
91
		}
92
93
		if ($this->user->is_guest === false)
94
		{
95
			if (!empty($options['minmax_preferences']))
96
			{
97
				$context['minmax_preferences'] = serializeToJson($options['minmax_preferences'], function ($array_form) {
98
					global $settings;
99
100
					// Update the option.
101
					require_once(SUBSDIR . '/Themes.subs.php');
102
					updateThemeOptions([
103
						$settings['theme_id'],
104
						User::$info->id,
105 82
						'minmax_preferences',
106
						json_encode($array_form),
107
					]);
108
				});
109 147
			}
110
			else
111
			{
112
				$context['minmax_preferences'] = [];
113
			}
114 229
		}
115
		// Guest may have collapsed the header, check the cookie to prevent collapse jumping
116
		elseif ($this->user->is_guest && isset($_COOKIE['upshrink']))
117 229
		{
118 229
			$context['minmax_preferences'] = ['upshrink' => $_COOKIE['upshrink']];
119
		}
120
121 229
		$this->loadThemeContext();
122
123 147
		// @todo These really don't belong here since they are more general than the theme.
124 147
		$context['forum_name'] = $mbname;
125 147
		$context['forum_name_html_safe'] = $context['forum_name'];
126
127
		// Set some permission related settings.
128
		if ($this->user->is_guest && !empty($modSettings['enableVBStyleLogin']))
129 229
		{
130
			$context['show_login_bar'] = true;
131
			$context['theme_header_callbacks'][] = 'login_bar';
132 229
			loadJavascriptFile('sha256.js', ['defer' => true]);
133
		}
134
135 229
		// This determines the server... not used in many places, except for login fixing.
136 229
		detectServer();
137 229
138
		// Set the top level linktree up.
139
		array_unshift($context['linktree'], [
140
			'url' => $scripturl,
141 229
			'name' => $context['forum_name'],
142
		]);
143
144
		// Just some mobile-friendly settings
145
		$req = request();
146
		if (strpos($req->user_agent(), 'Mobi'))
147
		{
148
			// Disable the search dropdown.
149
			$modSettings['search_dropdown'] = false;
150
		}
151
152
		// @todo Hummm this seems a bit wanky
153
		if (!isset($txt))
154 229
		{
155
			$txt = [];
156 1
		}
157
158
		// Defaults in case of odd things
159
		$settings['avatars_on_indexes'] = 0;
160 229
161
		// Initialize the theme.
162
		$settings = array_merge($settings, $this->theme->getSettings());
163 229
164
		// Load the basic layers
165
		$this->theme->loadDefaultLayers();
166 229
167
		// Call initialization theme integration functions.
168
		call_integration_hook('integrate_init_theme', [$this->id, &$settings]);
169 229
170
		// Guests may still need a name.
171
		if ($context['user']['is_guest'] && empty($context['user']['name']))
172 229
		{
173
			$context['user']['name'] = $txt['guest_title'];
174 1
		}
175
176
		// Any theme-related strings that need to be loaded?
177
		if (!empty($settings['require_theme_strings']))
178 229
		{
179
			Txt::load('ThemeStrings', '', false);
180
		}
181
182
		// Load the SVG support file with fallback to default theme
183
		loadCSSFile('icons_svg.css');
184 229
185
		// We allow theme variants, because we're cool.
186 229
		if (!empty($settings['theme_variants']))
187
		{
188
			$this->theme->loadThemeVariant();
189
		}
190 229
191 229
		// A bit lonely maybe, though I think it should be set up *after* the theme variants detection
192
		$context['header_logo_url_html_safe'] =
193
			empty($settings['header_logo_url']) ? $settings['images_url'] . '/' . $context['theme_variant_url'] . 'logo_elk.png' : Util::htmlspecialchars($settings['header_logo_url']);
194 229
195
		// Allow overriding the board wide time/number formats.
196 228
		if (empty(User::$settings['time_format']) && !empty($txt['time_format']))
197
		{
198
			$this->user->time_format = $txt['time_format'];
0 ignored issues
show
Bug Best Practice introduced by
The property time_format does not exist on ElkArte\ValuesContainer. Since you implemented __set, consider adding a @property annotation.
Loading history...
199 229
		}
200
201
		if (isset($settings['use_default_images']) && $settings['use_default_images'] == 'always')
202
		{
203
			$settings['theme_url'] = $settings['default_theme_url'];
204
			$settings['images_url'] = $settings['default_images_url'];
205
			$settings['theme_dir'] = $settings['default_theme_dir'];
206
		}
207 229
208
		// Make a special URL for the language.
209
		$settings['lang_images_url'] = $settings['images_url'] . '/' . (!empty($txt['image_lang']) ? $txt['image_lang'] : $this->user->language);
210 229
211
		// RTL languages require an additional stylesheet.
212
		if ($context['right_to_left'])
213
		{
214
			loadCSSFile('rtl.css');
215 229
		}
216
217
		if (!empty($context['theme_variant']) && $context['right_to_left'])
218
		{
219
			loadCSSFile($context['theme_variant'] . '/rtl' . $context['theme_variant'] . '.css');
220 229
		}
221
222 229
		if (!empty($modSettings['xmlnews_enable']) && (!empty($modSettings['allow_guestAccess']) || $context['user']['is_logged']))
223 229
		{
224 229
			$context['newsfeed_urls'] = [
225
				'rss' => getUrl('action', ['action' => '.xml', 'type' => 'rss2', 'limit' => (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5)]),
226
				'atom' => getUrl('action', ['action' => '.xml', 'type' => 'atom', 'limit' => (!empty($modSettings['xmlnews_limit']) ? $modSettings['xmlnews_limit'] : 5)]),
227
			];
228 229
		}
229
230
		if (!empty($_SESSION['agreement_accepted']))
231
		{
232
			$_SESSION['agreement_accepted'] = null;
233
			$context['accepted_agreement'] = array(
234
				'errors' => array(
235
					'accepted_agreement' => $txt['agreement_accepted']
236
				)
237
			);
238 229
		}
239
240
		if (!empty($_SESSION['privacypolicy_accepted']))
241
		{
242
			$_SESSION['privacypolicy_accepted'] = null;
243
			$context['accepted_agreement'] = array(
244
				'errors' => array(
245
					'accepted_privacy_policy' => $txt['privacypolicy_accepted']
246
				)
247
			);
248 229
		}
249
250 229
		$this->theme->loadThemeJavascript();
251
252
		Hooks::instance()->newPath(['$themedir' => $settings['theme_dir']]);
253 229
254
		// Any files to include at this point?
255
		call_integration_include_hook('integrate_theme_include');
256 229
257
		// Call load theme integration functions.
258
		call_integration_hook('integrate_load_theme');
259 229
260 229
		// We are ready to go.
261
		$context['theme_loaded'] = true;
262
	}
263
264
	/**
265 229
	 * Initialize a theme for use
266
	 */
267 229
	private function initTheme()
268
	{
269
		global $settings, $options, $context;
270 229
271
		// Validate / fetch the themes id
272
		$this->getThemeId();
273 229
274
		// Need to know who we are loading the theme for
275
		$member = empty($this->user->id) ? -1 : $this->user->id;
276 229
277 229
		// Load in the theme variables for them
278 229
		$themeData = $this->getThemeData($member);
279
		$settings = $themeData[0];
280 229
		$options = $themeData[$member];
281 229
282 229
		$settings['theme_id'] = $this->id;
283 229
		$settings['actual_theme_url'] = $settings['theme_url'];
284
		$settings['actual_images_url'] = $settings['images_url'];
285
		$settings['actual_theme_dir'] = $settings['theme_dir'];
286 229
287 229
		// Set the name of the default theme to something PHP will recognize.
288 229
		$themeName = basename($settings['theme_dir']) === 'default'
289
			? 'DefaultTheme'
290
			: ucfirst(basename($settings['theme_dir']));
291 229
292 229
		// The require should not be necessary, but I guess it's better to stay on the safe side.
293 229
		require_once(EXTDIR . '/ClassLoader.php');
294 229
		$loader = new ClassLoader();
295
		$loader->setPsr4('ElkArte\\Themes\\' . $themeName . '\\', $themeData[0]['default_theme_dir']);
296
		$loader->register();
297 229
298 229
		// Setup the theme file.
299 229
		require_once($settings['theme_dir'] . '/Theme.php');
300 229
		$class = 'ElkArte\\Themes\\' . $themeName . '\\Theme';
301
		static::$dirs = new Directories($settings);
302
		User::$info = User::$info ?? new UserInfo([]);
303 229
		$this->theme = new $class($this->id, User::$info, static::$dirs);
304 229
		$context['theme_instance'] = $this->theme;
305
	}
306
307
	/**
308
	 * Resolves the ID of a theme.
309
	 *
310
	 * The identifier can be specified in:
311
	 * - a GET variable if theme selection is enabled
312
	 * - the session
313
	 * - user's preferences
314
	 * - board
315
	 * - forum default
316
	 *
317
	 * In addition, the ID is verified against a comma-separated list of
318
	 * known good themes. This check is skipped if the user is an admin.
319
	 *
320
	 * @return void Theme ID to load
321 229
	 */
322
	private function getThemeId()
323 229
	{
324
		global $modSettings, $board_info;
325
326 229
		// The user has selected a theme
327
		if (!empty($modSettings['theme_allow']) || allowedTo('admin_forum'))
328 229
		{
329
			$this->_chooseTheme();
330
		}
331
		// The theme was specified by the board.
332
		elseif (!empty($board_info['theme']))
333
		{
334
			$this->id = $board_info['theme'];
335
		}
336
		// The theme is the forum's default.
337
		else
338
		{
339
			$this->id = $modSettings['theme_guests'];
340
		}
341
342 229
		// Whatever we found, make sure its valid
343 229
		$this->_validThemeID();
344
	}
345
346
	/**
347
	 * Sets the chosen theme id
348 229
	 */
349
	private function _chooseTheme()
350 229
	{
351
		$_req = HttpReq::instance();
352
353 229
		// The theme was previously set by th (ACP)
354
		if (!empty($this->id) && !empty($_req->isSet('th')))
355
		{
356
			$this->id = (int) $this->id;
357
		}
358 229
		// The theme was specified by Get or Post.
359
		elseif (!empty($_req->getRequest('theme', 'intval', null)))
360
		{
361
			$this->id = $_req->get('theme');
362
			$_SESSION['theme'] = $this->id;
363
		}
364 229
		// The theme was specified by REQUEST... previously.
365
		elseif (!empty($_req->getSession('theme')))
366
		{
367
			$this->id = (int) $_req->getSession('theme');
368
		}
369 229
		// The theme is just the user's choice. (might use ?board=1;theme=0 to force board theme.)
370
		elseif (!empty($this->user->theme))
371
		{
372
			$this->id = $this->user->theme;
373 229
		}
374
	}
375
376
	/**
377
	 * Validates, and corrects if in error, that the theme id is capable of
378
	 * being used.
379 229
	 */
380
	private function _validThemeID()
381 229
	{
382
		global $modSettings, $ssi_theme;
383
384 229
		// Ensure that the theme is known... no foul play.
385
		if (!allowedTo('admin_forum'))
386 9
		{
387 9
			$themes = explode(',', $modSettings['knownThemes']);
388
			if (!in_array($this->id, $themes) || (!empty($ssi_theme) && $this->id != $ssi_theme))
389 9
			{
390
				$this->id = $modSettings['theme_guests'];
391
			}
392 229
		}
393
	}
394
395
	/**
396
	 * Load in the theme variables for a given theme / member combination
397
	 *
398
	 * @param int $member
399
	 *
400
	 * @return array
401
	 */
402 229
	private function getThemeData($member)
403
	{
404 229
		global $modSettings, $boardurl;
405
406 229
		$cache = Cache::instance();
407
408
		// Do we already have this members theme data and specific options loaded (for aggressive cache settings)
409 229
		$temp = [];
410 229
		if ($cache->levelHigherThan(1)
411 229
			&& $cache->getVar($temp, 'theme_settings-' . $this->id . ':' . $member, 60)
412 229
			&& time() - 60 > $modSettings['settings_updated'])
413
		{
414
			$themeData = $temp;
415
			$flag = true;
416
		}
417
		// Or do we just have the system wide theme settings cached
418
		elseif ($cache->getVar($temp, 'theme_settings-' . $this->id, 90)
419 229
			&& time() - 60 > $modSettings['settings_updated'])
420 229
		{
421
			$themeData = $temp + [$member => []];
422
		}
423
		// Nothing at all then
424
		else
425
		{
426
			$themeData = [-1 => [], 0 => [], $member => []];
427
		}
428 229
429
		if (empty($flag))
430
		{
431 229
			$db = database();
432
433 229
			$immutable_theme_data = [
434
				'actual_theme_url',
435
				'actual_images_url',
436 229
				'base_theme_dir',
437
				'base_theme_url',
438
				'default_images_url',
439
				'default_theme_dir',
440
				'default_theme_url',
441
				'default_template',
442
				'images_url',
443
				'number_recent_posts',
444
				'smiley_sets_default',
445
				'theme_dir',
446
				'theme_id',
447
				'theme_layers',
448
				'theme_templates',
449
				'theme_url',
450
			];
451
452
			// Load variables from the current or default theme, global or this user's.
453
			$db->fetchQuery('
454
			SELECT 
455 229
				variable, value, id_member, id_theme
456
			FROM {db_prefix}themes
457
			WHERE id_member' . (empty($themeData[0]) ? ' IN (-1, 0, {int:id_member})' : ' = {int:id_member}') . '
458
				AND id_theme' . ($this->id == 1 ? ' = {int:id_theme}' : ' IN ({int:id_theme}, 1)'),
459 229
				[
460 229
					'id_theme' => $this->id,
461
					'id_member' => $member,
462 229
				]
463 229
			)->fetch_callback(
464
				function ($row) use ($immutable_theme_data, &$themeData) {
465 229
					// There are just things we shouldn't be able to change as members.
466
					if ($row['id_member'] != 0 && in_array($row['variable'], $immutable_theme_data))
467
					{
468 229
						return;
469
					}
470
471
					// If this is the theme_dir of the default theme, store it.
472
					if (in_array($row['variable'], ['theme_dir', 'theme_url', 'images_url'])
473
						&& $row['id_theme'] == 1 && empty($row['id_member']))
474 229
					{
475 229
						$themeData[0]['default_' . $row['variable']] = $row['value'];
476
					}
477 229
478
					// If this isn't set yet, is a theme option, or is not the default theme..
479
					if (!isset($themeData[$row['id_member']][$row['variable']]) || $row['id_theme'] != 1)
480
					{
481 229
						$themeData[$row['id_member']][$row['variable']] = substr($row['variable'], 0, 5) === 'show_' ? $row['value'] == 1 : $row['value'];
482
					}
483 229
				}
484
			);
485 229
486
			if (file_exists($themeData[0]['default_theme_dir'] . '/cache')
487
				&& is_writable($themeData[0]['default_theme_dir'] . '/cache'))
488 229
			{
489 229
				$themeData[0]['default_theme_cache_dir'] = $themeData[0]['default_theme_dir'] . '/cache';
490
				$themeData[0]['default_theme_cache_url'] = $themeData[0]['default_theme_url'] . '/cache';
491 229
			}
492 229
			else
493
			{
494
				$themeData[0]['default_theme_cache_dir'] = CACHEDIR;
495
				$themeData[0]['default_theme_cache_url'] = $boardurl . '/cache';
496
			}
497
498
			// Set the defaults if the user has not chosen on their own
499
			if (!empty($themeData[-1]))
500
			{
501 229
				foreach ($themeData[-1] as $k => $v)
502
				{
503 229
					if (!isset($themeData[$member][$k]))
504
					{
505 229
						$themeData[$member][$k] = $v;
506
					}
507 155
				}
508
			}
509
510
			// If being aggressive we save the site wide and member theme settings
511
			if ($cache->levelHigherThan(1))
512
			{
513 229
				$cache->put('theme_settings-' . $this->id . ':' . $member, $themeData, 60);
514
			}
515
			// Only if we didn't already load that part of the cache...
516
			elseif (!isset($temp))
517
			{
518 229
				$cache->put('theme_settings-' . $this->id, [-1 => $themeData[-1], 0 => $themeData[0]], 90);
519
			}
520 229
		}
521
522
		return $themeData;
523
	}
524 229
525
	/**
526
	 * Detects url and checks against expected boardurl
527
	 *
528
	 * Attempts to correct improper URL's
529
	 */
530
	private function loadThemeUrls()
531
	{
532 229
		global $scripturl, $boardurl, $modSettings;
533
534 229
		// Check to see if they're accessing it from the wrong place.
535
		if (isset($_SERVER['HTTP_HOST']) || isset($_SERVER['SERVER_NAME']))
536
		{
537 229
			$detected_url = isset($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on' ? 'https://' : 'http://';
538
			$detected_url .= empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . (empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] == '80' ? '' : ':' . $_SERVER['SERVER_PORT']) : $_SERVER['HTTP_HOST'];
539
			$temp = preg_replace('~/' . basename($scripturl) . '(/.+)?$~', '',
540
				strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'));
541
			if ($temp != '/')
542
			{
543
				$detected_url .= $temp;
544
			}
545
		}
546
547
		if (isset($detected_url) && $detected_url != $boardurl)
548
		{
549 229
			// Try #1 - check if it's in a list of alias addresses.
550
			if (!empty($modSettings['forum_alias_urls']))
551
			{
552
				$aliases = explode(',', $modSettings['forum_alias_urls']);
553
				foreach ($aliases as $alias)
554
				{
555
					// Rip off all the boring parts, spaces, etc.
556
					if ($detected_url === trim($alias) || strtr($detected_url, [
557
							'http://' => '',
558
							'https://' => '',
559
						]) === trim($alias)
560
					)
561
					{
562
						$do_fix = true;
563
					}
564
				}
565
			}
566
567
			// Hmm... check #2 - is it just different by a www?  Send them to the correct place!!
568
			if (empty($do_fix) && strtr($detected_url,
569
					['://' => '://www.']) == $boardurl && (empty($_GET) || count($_GET) === 1) && ELK != 'SSI'
0 ignored issues
show
introduced by
The condition ElkArte\Themes\ELK != 'SSI' is always false.
Loading history...
570
			)
571
			{
572
				// Okay, this seems weird, but we don't want an endless loop - this will make $_GET not empty ;).
573
				if (empty($_GET))
574
				{
575
					redirectexit('wwwRedirect');
576
				}
577
				elseif (key($_GET) !== 'wwwRedirect')
578
				{
579
					redirectexit('wwwRedirect;' . key($_GET) . '=' . current($_GET));
580
				}
581
			}
582
583
			// #3 is just a check for SSL...
584
			if (strtr($detected_url, ['https://' => 'http://']) == $boardurl)
585
			{
586
				$do_fix = true;
587
			}
588
589
			// Okay, #4 - perhaps it's an IP address?  We're gonna want to use that one, then. (assuming it's the IP or something...)
590
			if (!empty($do_fix) || preg_match('~^http[s]?://(?:[\d\.:]+|\[[\d:]+\](?::\d+)?)(?:$|/)~',
591
					$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...
592
			)
593
			{
594
				$this->fixThemeUrls($detected_url);
595
			}
596
		}
597
	}
598
599 229
	/**
600
	 * Called if the detected URL is not the same as boardurl but is a common
601
	 * variation in which case it updates key system variables so it works.
602
	 *
603
	 * @param string $detected_url
604
	 */
605
	private function fixThemeUrls($detected_url)
606
	{
607
		global $boardurl, $scripturl, $settings, $modSettings, $context, $board_info;
608
609
		// Caching is good ;).
610
		$oldurl = $boardurl;
611
612
		// Fix $boardurl and $scripturl.
613
		$boardurl = $detected_url;
614
		$scripturl = strtr($scripturl, array($oldurl => $boardurl));
615
		$_SERVER['REQUEST_URL'] = strtr($_SERVER['REQUEST_URL'], array($oldurl => $boardurl));
616
617
		// Fix the theme urls...
618
		$settings['theme_url'] = strtr($settings['theme_url'], array($oldurl => $boardurl));
619
		$settings['default_theme_url'] = strtr($settings['default_theme_url'], array($oldurl => $boardurl));
620
		$settings['actual_theme_url'] = strtr($settings['actual_theme_url'], array($oldurl => $boardurl));
621
		$settings['images_url'] = strtr($settings['images_url'], array($oldurl => $boardurl));
622
		$settings['default_images_url'] = strtr($settings['default_images_url'], array($oldurl => $boardurl));
623
		$settings['actual_images_url'] = strtr($settings['actual_images_url'], array($oldurl => $boardurl));
624
625
		// And just a few mod settings :).
626
		$modSettings['smileys_url'] = strtr($modSettings['smileys_url'], array($oldurl => $boardurl));
627
		$modSettings['avatar_url'] = strtr($modSettings['avatar_url'], array($oldurl => $boardurl));
628
629
		// Clean up after loadBoard().
630
		if (isset($board_info['moderators']))
631
		{
632
			foreach ($board_info['moderators'] as $k => $dummy)
633
			{
634
				$board_info['moderators'][$k]['href'] = strtr($dummy['href'], array($oldurl => $boardurl));
635
				$board_info['moderators'][$k]['link'] = strtr($dummy['link'], array('"' . $oldurl => '"' . $boardurl));
636
			}
637
		}
638
639
		foreach ($context['linktree'] as $k => $dummy)
640
		{
641
			$context['linktree'][$k]['url'] = strtr($dummy['url'], array($oldurl => $boardurl));
642
		}
643
	}
644
645
	/**
646
	 * Loads various theme related settings into context and sets system wide theme defaults
647
	 */
648
	private function loadThemeContext()
649
	{
650 229
		global $context, $settings, $modSettings, $txt;
651
652 229
		// Some basic information...
653
		$init = [
654
			'html_headers' => '',
655
			'links' => [],
656 229
			'css_files' => [],
657
			'javascript_files' => [],
658
			'css_rules' => [],
659
			'javascript_inline' => ['standard' => [], 'defer' => []],
660
			'javascript_vars' => [],
661
		];
662
		foreach ($init as $area => $value)
663
		{
664 229
			$context[$area] = $context[$area] ?? $value;
665
		}
666 229
667
		// Set a couple of bits for the template.
668
		$context['right_to_left'] = !empty($txt['lang_rtl']);
669
		$context['tabindex'] = 1;
670 229
671 229
		$context['theme_variant'] = '';
672
		$context['theme_variant_url'] = '';
673 229
674 229
		$context['menu_separator'] = !empty($settings['use_image_buttons']) ? ' ' : ' | ';
675
		$context['can_register'] =
676 229
			empty($modSettings['registration_method']) || $modSettings['registration_method'] != 3;
677 229
678 229
		foreach (['theme_header', 'upper_content'] as $call)
679
		{
680 229
			if (!isset($context[$call . '_callbacks']))
681
			{
682 229
				$context[$call . '_callbacks'] = [];
683
			}
684 115
		}
685
686
		// This allows sticking some HTML on the page output - useful for controls.
687
		$context['insert_after_template'] = '';
688
	}
689 229
690 229
	/**
691
	 * @return Theme the current theme
692
	 */
693
	public function getTheme()
694
	{
695
		return $this->theme;
696
	}
697
698
	/**
699
	 * This loads the bare minimum data.
700
	 *
701
	 * - Needed by scheduled tasks,
702
	 * - Needed by any other code that needs language files before the forum (the theme)
703
	 * is loaded.
704
	 */
705
	public static function loadEssentialThemeData()
706
	{
707
		global $settings, $modSettings, $mbname, $context;
708
709
		if (!function_exists('database'))
710
		{
711
			throw new \Exception('');
712
		}
713
714
		$db = database();
715
716
		// Get all the default theme variables.
717
		$db->fetchQuery('
718
			SELECT 
719
				id_theme, variable, value
720
			FROM {db_prefix}themes
721
			WHERE id_member = {int:no_member}
722
				AND id_theme IN (1, {int:theme_guests})',
723
			[
724
				'no_member' => 0,
725
				'theme_guests' => $modSettings['theme_guests'],
726
			]
727
		)->fetch_callback(
728
			function ($row) {
729
				global $settings;
730
731
				$settings[$row['variable']] = $row['value'];
732
				$indexes_to_default = [
733
					'theme_dir',
734
					'theme_url',
735
					'images_url',
736
				];
737
738
				// Is this the default theme?
739
				if ($row['id_theme'] == '1' && in_array($row['variable'], $indexes_to_default))
740
				{
741
					$settings['default_' . $row['variable']] = $row['value'];
742
				}
743
			}
744
		);
745
746
		static::$dirs = new Directories($settings);
747
748
		// Check we have some directories setup.
749
		if (static::$dirs->hasDirectories() === false)
750
		{
751
			static::$dirs->reloadDirectories($settings);
752
		}
753
754
		// Assume we want this.
755
		$context['forum_name'] = $mbname;
756
		$context['forum_name_html_safe'] = $context['forum_name'];
757
758
		static::loadLanguageFile('index+Addons');
759
	}
760
761
	/**
762
	 * Load a language file.
763
	 *
764
	 * - Tries the current and default themes as well as the user and global languages.
765
	 *
766
	 * @param string[] $template_name
767
	 * @param string $lang = ''
768
	 * @param bool $fatal = true
769
	 * @param bool $force_reload = false
770
	 *
771
	 * @return string The language actually loaded.
772
	 */
773
	public static function loadLanguageFiles(
774
		array $template_name,
775
		$lang = '',
776
		$fatal = true,
777
		$force_reload = false
778
	) {
779
		global $language, $settings, $modSettings;
780
		global $db_show_debug, $txt;
781
		static $already_loaded = [];
782
783
		// For each file open it up and write it out!
784
		foreach ($template_name as $template)
785
		{
786
			$fix_arrays = $template === 'index';
787
788
			Txt::load($template, true, $fix_arrays);
789
		}
790
791
		// Return the language actually loaded.
792
		return $lang;
793
	}
794
795
	/**
796
	 * Load a language file.
797
	 *
798
	 * - Tries the current and default themes as well as the user and global languages.
799
	 *
800
	 * @param string $template_name
801
	 * @param string $lang = ''
802
	 * @param bool $fatal = true
803
	 * @param bool $force_reload = false
804
	 *
805
	 * @return string The language actually loaded.
806
	 */
807
	public static function loadLanguageFile(
808
		$template_name,
809
		$lang = '',
810
		$fatal = false, // @todo reset to true when appropriate
811
		$force_reload = false
812
	) {
813
		return static::loadLanguageFiles(
814
			explode('+', $template_name),
815
			$lang,
816
			$fatal,
817
			$force_reload
818
		);
819
	}
820
}
821