Passed
Push — development ( f45da9...98b5d9 )
by Emanuele
01:06 queued 26s
created

ThemeLoader::loadEssentialThemeData()   A

Complexity

Conditions 5
Paths 3

Size

Total Lines 53
Code Lines 28

Duplication

Lines 0
Ratio 0 %

Code Coverage

Tests 0
CRAP Score 30

Importance

Changes 0
Metric Value
cc 5
eloc 28
nc 3
nop 0
dl 0
loc 53
ccs 0
cts 0
cp 0
crap 30
rs 9.1608
c 0
b 0
f 0

How to fix   Long Method   

Long Method

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

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

Commonly applied refactorings include:

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