1
|
|
|
<?php |
2
|
|
|
|
3
|
|
|
/** |
4
|
|
|
* Initialize the ElkArte environment. |
5
|
|
|
* |
6
|
|
|
* @name ElkArte Forum |
7
|
|
|
* @copyright ElkArte Forum contributors |
8
|
|
|
* @license BSD http://opensource.org/licenses/BSD-3-Clause |
9
|
|
|
* |
10
|
|
|
* This file contains code covered by: |
11
|
|
|
* copyright: 2011 Simple Machines (http://www.simplemachines.org) |
12
|
|
|
* license: BSD, See included LICENSE.TXT for terms and conditions. |
13
|
|
|
* |
14
|
|
|
* @version 1.1.9 |
15
|
|
|
* |
16
|
|
|
*/ |
17
|
|
|
|
18
|
|
|
/** |
19
|
|
|
* Class Bootstrap |
20
|
|
|
* |
21
|
|
|
* This takes care of the initial loading and feeding of Elkarte from |
22
|
|
|
* either SSI or Index |
23
|
|
|
*/ |
24
|
|
|
class Bootstrap |
25
|
|
|
{ |
26
|
|
|
/** |
27
|
|
|
* Bootstrap constructor. |
28
|
|
|
* |
29
|
|
|
* @param bool $standalone |
30
|
|
|
* - true to boot outside of elkarte |
31
|
|
|
* - false to bootstrap the main elkarte site. |
32
|
|
|
* @throws \Elk_Exception |
33
|
|
|
*/ |
34
|
|
|
public function __construct($standalone = true) |
35
|
|
|
{ |
36
|
|
|
// Bootstrap only once. |
37
|
|
|
if (defined('ELKBOOT')) |
38
|
|
|
{ |
39
|
|
|
return true; |
40
|
|
|
} |
41
|
|
|
|
42
|
|
|
// We're going to set a few globals |
43
|
|
|
global $time_start, $ssi_error_reporting, $db_show_debug; |
44
|
|
|
|
45
|
|
|
// Your on the clock |
46
|
|
|
$time_start = microtime(true); |
47
|
|
|
|
48
|
|
|
// Unless settings.php tells us otherwise |
49
|
|
|
$db_show_debug = false; |
50
|
|
|
|
51
|
|
|
// Report errors but not depreciated ones |
52
|
|
|
$ssi_error_reporting = error_reporting(E_ALL & ~E_DEPRECATED); |
53
|
|
|
|
54
|
|
|
// Get the things needed for ALL modes |
55
|
|
|
$this->bringUpBasics(); |
56
|
|
|
|
57
|
|
|
// Going to run from the side entrance and not directly from inside elkarte |
58
|
|
|
if ($standalone) |
59
|
|
|
{ |
60
|
|
|
$this->ssi_main(); |
61
|
|
|
} |
62
|
|
|
} |
63
|
|
|
|
64
|
|
|
/** |
65
|
|
|
* Calls the various initialization functions in the needed order |
66
|
|
|
*/ |
67
|
|
|
public function bringUpBasics() |
68
|
|
|
{ |
69
|
|
|
$this->setConstants(); |
70
|
|
|
$this->setRusage(); |
71
|
|
|
$this->clearGloballs(); |
72
|
|
|
$this->loadSettingsFile(); |
73
|
|
|
$this->validatePaths(); |
74
|
|
|
$this->loadDependants(); |
75
|
|
|
$this->loadAutoloader(); |
76
|
|
|
$this->checkMaintance(); |
77
|
|
|
$this->setDebug(); |
78
|
|
|
$this->bringUp(); |
79
|
|
|
} |
80
|
|
|
|
81
|
|
|
/** |
82
|
|
|
* Set the core constants, you know the ones we often forget to |
83
|
|
|
* update on new releases. |
84
|
|
|
*/ |
85
|
|
|
private function setConstants() |
86
|
|
|
{ |
87
|
|
|
// First things first, but not necessarily in that order. |
88
|
|
|
if (!defined('ELK')) |
89
|
|
|
{ |
90
|
|
|
define('ELK', '1'); |
91
|
|
|
} |
92
|
|
|
define('ELKBOOT', '1'); |
93
|
|
|
|
94
|
|
|
// The software version |
95
|
|
|
define('FORUM_VERSION', 'ElkArte 1.1.9'); |
96
|
|
|
|
97
|
|
|
// Shortcut for the browser cache stale |
98
|
|
|
define('CACHE_STALE', '?R119'); |
99
|
|
|
} |
100
|
|
|
|
101
|
|
|
/** |
102
|
|
|
* Get initial resource usage |
103
|
|
|
*/ |
104
|
|
|
private function setRusage() |
105
|
|
|
{ |
106
|
|
|
global $rusage_start; |
107
|
|
|
|
108
|
|
|
// Directional only script time usage for display |
109
|
|
|
// getrusage is missing in php < 7 on Windows |
110
|
|
|
if (function_exists('getrusage')) |
111
|
|
|
{ |
112
|
|
|
$rusage_start = getrusage(); |
113
|
|
|
} |
114
|
|
|
else |
115
|
|
|
{ |
116
|
|
|
$rusage_start = array(); |
117
|
|
|
} |
118
|
|
|
} |
119
|
|
|
|
120
|
|
|
/** |
121
|
|
|
* If they glo, they need to be cleaned. |
122
|
|
|
*/ |
123
|
|
|
private function clearGloballs() |
124
|
|
|
{ |
125
|
|
|
// We don't need no globals. (a bug in "old" versions of PHP) |
126
|
|
|
foreach (array('db_character_set', 'cachedir') as $variable) |
127
|
|
|
{ |
128
|
|
|
if (isset($GLOBALS[$variable])) |
129
|
|
|
{ |
130
|
|
|
unset($GLOBALS[$variable], $GLOBALS[$variable]); |
131
|
|
|
} |
132
|
|
|
} |
133
|
|
|
} |
134
|
|
|
|
135
|
|
|
/** |
136
|
|
|
* Loads the settings values into the global space |
137
|
|
|
*/ |
138
|
|
|
private function loadSettingsFile() |
139
|
|
|
{ |
140
|
|
|
// All those wonderful things found in settings |
141
|
|
|
global $maintenance, $mtitle, $msubject, $mmessage, $mbname, $language, $boardurl, $webmaster_email; |
142
|
|
|
global $cookiename, $db_type, $db_server, $db_port, $db_name, $db_user, $db_passwd; |
143
|
|
|
global $ssi_db_user, $ssi_db_passwd, $db_prefix, $db_persist, $db_error_send, $cache_accelerator; |
144
|
|
|
global $cache_uid, $cache_password, $cache_enable, $cache_memcached, $db_show_debug; |
145
|
|
|
global $cachedir, $boarddir, $sourcedir, $extdir, $languagedir, $ignore_install_dir; |
146
|
|
|
|
147
|
|
|
// Where the Settings.php file is located |
148
|
|
|
$settings_loc = __DIR__ . '/Settings.php'; |
149
|
|
|
|
150
|
|
|
// First thing: if the install dir exists, just send anybody there |
151
|
|
|
// The ignore_install_dir var is for developers only. Do not add it on production sites |
152
|
|
|
if (file_exists('install') && (file_exists('install/install.php') || file_exists('install/upgrade.php'))) |
153
|
|
|
{ |
154
|
|
|
if (file_exists($settings_loc)) |
155
|
|
|
{ |
156
|
|
|
require_once($settings_loc); |
157
|
|
|
} |
158
|
|
|
|
159
|
|
|
if (empty($ignore_install_dir)) |
160
|
|
|
{ |
161
|
|
|
if (file_exists($settings_loc) && empty($_SESSION['installing'])) |
162
|
|
|
{ |
163
|
|
|
$redirec_file = 'upgrade.php'; |
164
|
|
|
} |
165
|
|
|
else |
166
|
|
|
{ |
167
|
|
|
$redirec_file = 'install.php'; |
168
|
|
|
} |
169
|
|
|
|
170
|
|
|
$version_running = str_replace('ElkArte ', '', FORUM_VERSION); |
171
|
|
|
$proto = 'http' . (!empty($_SERVER['HTTPS']) && strtolower($_SERVER['HTTPS']) === 'on' ? 's' : ''); |
172
|
|
|
$port = empty($_SERVER['SERVER_PORT']) || $_SERVER['SERVER_PORT'] === '80' ? '' : ':' . $_SERVER['SERVER_PORT']; |
173
|
|
|
$host = empty($_SERVER['HTTP_HOST']) ? $_SERVER['SERVER_NAME'] . $port : $_SERVER['HTTP_HOST']; |
174
|
|
|
$path = strtr(dirname($_SERVER['PHP_SELF']), '\\', '/') == '/' ? '' : strtr(dirname($_SERVER['PHP_SELF']), '\\', '/'); |
175
|
|
|
|
176
|
|
|
header('Location:' . $proto . '://' . $host . $path . '/install/' . $redirec_file . '?v=' . $version_running); |
177
|
|
|
die(); |
178
|
|
|
} |
179
|
|
|
} |
180
|
|
|
else |
181
|
|
|
{ |
182
|
|
|
require_once($settings_loc); |
183
|
|
|
} |
184
|
|
|
} |
185
|
|
|
|
186
|
|
|
/** |
187
|
|
|
* Validate the paths set in Settings.php, correct as needed and move |
188
|
|
|
* them to constants. |
189
|
|
|
*/ |
190
|
|
|
private function validatePaths() |
191
|
|
|
{ |
192
|
|
|
global $boarddir, $sourcedir, $cachedir, $extdir, $languagedir; |
193
|
|
|
|
194
|
|
|
// Make sure the paths are correct... at least try to fix them. |
195
|
|
|
if (!file_exists($boarddir) && file_exists(__DIR__ . '/agreement.txt')) |
196
|
|
|
{ |
197
|
|
|
$boarddir = __DIR__; |
198
|
|
|
} |
199
|
|
|
|
200
|
|
|
if (!file_exists($sourcedir . '/SiteDispatcher.class.php') && file_exists($boarddir . '/sources')) |
201
|
|
|
{ |
202
|
|
|
$sourcedir = $boarddir . '/sources'; |
203
|
|
|
} |
204
|
|
|
|
205
|
|
|
// Check that directories which didn't exist in past releases are initialized. |
206
|
|
|
if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache')) |
207
|
|
|
{ |
208
|
|
|
$cachedir = $boarddir . '/cache'; |
209
|
|
|
} |
210
|
|
|
|
211
|
|
|
if ((empty($extdir) || !file_exists($extdir)) && file_exists($sourcedir . '/ext')) |
212
|
|
|
{ |
213
|
|
|
$extdir = $sourcedir . '/ext'; |
214
|
|
|
} |
215
|
|
|
|
216
|
|
|
if ((empty($languagedir) || !file_exists($languagedir)) && file_exists($boarddir . '/themes/default/languages')) |
217
|
|
|
{ |
218
|
|
|
$languagedir = $boarddir . '/themes/default/languages'; |
219
|
|
|
} |
220
|
|
|
|
221
|
|
|
// Time to forget about variables and go with constants! |
222
|
|
|
define('BOARDDIR', $boarddir); |
223
|
|
|
define('CACHEDIR', $cachedir); |
224
|
|
|
define('EXTDIR', $extdir); |
225
|
|
|
define('LANGUAGEDIR', $languagedir); |
226
|
|
|
define('SOURCEDIR', $sourcedir); |
227
|
|
|
define('ADMINDIR', $sourcedir . '/admin'); |
228
|
|
|
define('CONTROLLERDIR', $sourcedir . '/controllers'); |
229
|
|
|
define('SUBSDIR', $sourcedir . '/subs'); |
230
|
|
|
define('ADDONSDIR', $boarddir . '/addons'); |
231
|
|
|
unset($boarddir, $cachedir, $sourcedir, $languagedir, $extdir); |
232
|
|
|
} |
233
|
|
|
|
234
|
|
|
/** |
235
|
|
|
* We require access to several important files, so load them upfront |
236
|
|
|
*/ |
237
|
|
|
private function loadDependants() |
238
|
|
|
{ |
239
|
|
|
// Files we cannot live without. |
240
|
|
|
require_once(SOURCEDIR . '/QueryString.php'); |
241
|
|
|
require_once(SOURCEDIR . '/Session.php'); |
242
|
|
|
require_once(SOURCEDIR . '/Subs.php'); |
243
|
|
|
require_once(SOURCEDIR . '/Logging.php'); |
244
|
|
|
require_once(SOURCEDIR . '/Load.php'); |
245
|
|
|
require_once(SOURCEDIR . '/Security.php'); |
246
|
|
|
require_once(SUBSDIR . '/Cache.subs.php'); |
247
|
|
|
} |
248
|
|
|
|
249
|
|
|
/** |
250
|
|
|
* The autoloader will take care most requests for files |
251
|
|
|
*/ |
252
|
|
|
private function loadAutoloader() |
253
|
|
|
{ |
254
|
|
|
// Initialize the class Autoloader |
255
|
|
|
require_once(SOURCEDIR . '/Autoloader.class.php'); |
256
|
|
|
$autoloader = Elk_Autoloader::instance(); |
257
|
|
|
$autoloader->setupAutoloader(array(SOURCEDIR, SUBSDIR, CONTROLLERDIR, ADMINDIR, ADDONSDIR)); |
258
|
|
|
$autoloader->register(SOURCEDIR, '\\ElkArte'); |
259
|
|
|
$autoloader->register(SOURCEDIR . '/subs/BBC', '\\BBC'); |
260
|
|
|
} |
261
|
|
|
|
262
|
|
|
/** |
263
|
|
|
* Check if we are in maintance mode, if so end here. |
264
|
|
|
*/ |
265
|
|
|
private function checkMaintance() |
266
|
|
|
{ |
267
|
|
|
global $maintenance, $ssi_maintenance_off; |
268
|
|
|
|
269
|
|
|
// Don't do john didley if the forum's been shut down completely. |
270
|
|
|
if (!empty($maintenance) && $maintenance == 2 && (!isset($ssi_maintenance_off) || $ssi_maintenance_off !== true)) |
271
|
|
|
{ |
272
|
|
|
Errors::instance()->display_maintenance_message(); |
273
|
|
|
} |
274
|
|
|
} |
275
|
|
|
|
276
|
|
|
/** |
277
|
|
|
* If you like lots of debug information in error messages and below the footer |
278
|
|
|
* then set $db_show_debug to true in settings. Don't do this on a production site. |
279
|
|
|
*/ |
280
|
|
|
private function setDebug() |
281
|
|
|
{ |
282
|
|
|
global $db_show_debug, $rusage_start, $ssi_error_reporting; |
283
|
|
|
|
284
|
|
|
// Show lots of debug information below the page, not for production sites |
285
|
|
|
if ($db_show_debug === true) |
286
|
|
|
{ |
287
|
|
|
Debug::instance()->rusage('start', $rusage_start); |
288
|
|
|
$ssi_error_reporting = error_reporting(E_ALL | E_STRICT & ~8192); |
289
|
|
|
} |
290
|
|
|
} |
291
|
|
|
|
292
|
|
|
/** |
293
|
|
|
* Time to see what has been requested, by whom and dispatch it to the proper handler |
294
|
|
|
*/ |
295
|
|
|
private function bringUp() |
296
|
|
|
{ |
297
|
|
|
global $context; |
298
|
|
|
|
299
|
|
|
// Initiate the database connection and define some database functions to use. |
300
|
|
|
loadDatabase(); |
301
|
|
|
|
302
|
|
|
// Let's set up our shiny new hooks handler. |
303
|
|
|
Hooks::init(database(), Debug::instance()); |
304
|
|
|
|
305
|
|
|
// It's time for settings loaded from the database. |
306
|
|
|
reloadSettings(); |
307
|
|
|
|
308
|
|
|
// Clean the request. |
309
|
|
|
cleanRequest(); |
310
|
|
|
|
311
|
|
|
// Our good ole' contextual array, which will hold everything |
312
|
|
|
if (empty($context)) |
313
|
|
|
{ |
314
|
|
|
$context = array(); |
315
|
|
|
} |
316
|
|
|
|
317
|
|
|
// Seed the random generator. |
318
|
|
|
elk_seed_generator(); |
|
|
|
|
319
|
|
|
} |
320
|
|
|
|
321
|
|
|
/** |
322
|
|
|
* If you are running SSI standalone, you need to call this function after bootstrap is |
323
|
|
|
* initialized. |
324
|
|
|
* |
325
|
|
|
* @throws \Elk_Exception |
326
|
|
|
*/ |
327
|
|
|
public function ssi_main() |
328
|
|
|
{ |
329
|
|
|
global $ssi_layers, $ssi_theme, $ssi_gzip, $ssi_ban, $ssi_guest_access; |
330
|
|
|
global $modSettings, $context, $sc, $board, $topic, $user_info, $txt; |
331
|
|
|
|
332
|
|
|
// Check on any hacking attempts. |
333
|
|
|
$this->_validRequestCheck(); |
334
|
|
|
|
335
|
|
|
// Gzip output? (because it must be boolean and true, this can't be hacked.) |
336
|
|
|
if (isset($ssi_gzip) && $ssi_gzip === true && detectServer()->outPutCompressionEnabled()) |
337
|
|
|
{ |
338
|
|
|
ob_start('ob_gzhandler'); |
339
|
|
|
} |
340
|
|
|
else |
341
|
|
|
{ |
342
|
|
|
$modSettings['enableCompressedOutput'] = '0'; |
343
|
|
|
} |
344
|
|
|
|
345
|
|
|
// Primarily, this is to fix the URLs... |
346
|
|
|
ob_start('ob_sessrewrite'); |
347
|
|
|
|
348
|
|
|
// Start the session... known to scramble SSI includes in cases... |
349
|
|
|
if (!headers_sent()) |
350
|
|
|
{ |
351
|
|
|
loadSession(); |
352
|
|
|
} |
353
|
|
|
else |
354
|
|
|
{ |
355
|
|
|
if (isset($_COOKIE[session_name()]) || isset($_REQUEST[session_name()])) |
356
|
|
|
{ |
357
|
|
|
// Make a stab at it, but ignore the E_WARNINGs generated because we can't send headers. |
358
|
|
|
$temp = error_reporting(error_reporting() & !E_WARNING); |
359
|
|
|
loadSession(); |
360
|
|
|
error_reporting($temp); |
361
|
|
|
} |
362
|
|
|
|
363
|
|
|
if (!isset($_SESSION['session_value'])) |
364
|
|
|
{ |
365
|
|
|
$tokenizer = new Token_Hash(); |
366
|
|
|
$_SESSION['session_value'] = $tokenizer->generate_hash(32, session_id()); |
367
|
|
|
$_SESSION['session_var'] = substr(preg_replace('~^\d+~', '', $tokenizer->generate_hash(16, session_id())), 0, rand(7, 12)); |
368
|
|
|
} |
369
|
|
|
|
370
|
|
|
$sc = $_SESSION['session_value']; |
371
|
|
|
// This is here only to avoid session errors in PHP7 |
372
|
|
|
// microtime effectively forces the replacing of the session in the db each |
373
|
|
|
// time the page is loaded |
374
|
|
|
$_SESSION['mictrotime'] = microtime(); |
375
|
|
|
} |
376
|
|
|
|
377
|
|
|
// Get rid of $board and $topic... do stuff loadBoard would do. |
378
|
|
|
unset($board, $topic); |
379
|
|
|
$user_info['is_mod'] = false; |
380
|
|
|
$context['user']['is_mod'] = &$user_info['is_mod']; |
381
|
|
|
$context['linktree'] = array(); |
382
|
|
|
|
383
|
|
|
// Load the user and their cookie, as well as their settings. |
384
|
|
|
loadUserSettings(); |
385
|
|
|
|
386
|
|
|
// Load the current user's permissions.... |
387
|
|
|
loadPermissions(); |
388
|
|
|
|
389
|
|
|
// Load the current or SSI theme. (just use $ssi_theme = id_theme;) |
390
|
|
|
loadTheme(isset($ssi_theme) ? (int) $ssi_theme : 0); |
391
|
|
|
|
392
|
|
|
// Load BadBehavior functions, but not when running from CLI |
393
|
|
|
if (!defined('STDIN')) |
394
|
|
|
{ |
395
|
|
|
loadBadBehavior(); |
396
|
|
|
} |
397
|
|
|
|
398
|
|
|
// @todo: probably not the best place, but somewhere it should be set... |
399
|
|
|
if (!headers_sent()) |
400
|
|
|
{ |
401
|
|
|
header('Content-Type: text/html; charset=UTF-8'); |
402
|
|
|
} |
403
|
|
|
|
404
|
|
|
// Take care of any banning that needs to be done. |
405
|
|
|
if (isset($_REQUEST['ssi_ban']) || (isset($ssi_ban) && $ssi_ban === true)) |
406
|
|
|
{ |
407
|
|
|
is_not_banned(); |
408
|
|
|
} |
409
|
|
|
|
410
|
|
|
// Do we allow guests in here? |
411
|
|
|
if (empty($ssi_guest_access) && empty($modSettings['allow_guestAccess']) && $user_info['is_guest'] && basename($_SERVER['PHP_SELF']) !== 'SSI.php') |
412
|
|
|
{ |
413
|
|
|
$controller = new Auth_Controller(); |
414
|
|
|
$controller->action_kickguest(); |
415
|
|
|
obExit(null, true); |
416
|
|
|
} |
417
|
|
|
|
418
|
|
|
if (!empty($modSettings['front_page']) && class_exists($modSettings['front_page']) |
419
|
|
|
&& in_array('frontPageHook', get_class_methods($modSettings['front_page']))) |
420
|
|
|
{ |
421
|
|
|
$modSettings['default_forum_action'] = '?action=forum;'; |
422
|
|
|
} |
423
|
|
|
else |
424
|
|
|
{ |
425
|
|
|
$modSettings['default_forum_action'] = ''; |
426
|
|
|
} |
427
|
|
|
|
428
|
|
|
// Load the stuff like the menu bar, etc. |
429
|
|
|
if (isset($ssi_layers)) |
430
|
|
|
{ |
431
|
|
|
$template_layers = Template_Layers::instance(); |
432
|
|
|
$template_layers->removeAll(); |
433
|
|
|
foreach ($ssi_layers as $layer) |
434
|
|
|
{ |
435
|
|
|
$template_layers->addBegin($layer); |
436
|
|
|
} |
437
|
|
|
template_header(); |
438
|
|
|
} |
439
|
|
|
else |
440
|
|
|
{ |
441
|
|
|
setupThemeContext(); |
442
|
|
|
} |
443
|
|
|
|
444
|
|
|
// We need to set up user agent, and make more checks on the request |
445
|
|
|
$req = request(); |
446
|
|
|
|
447
|
|
|
// Make sure they didn't muss around with the settings... but only if it's not cli. |
448
|
|
|
if (isset($_SERVER['REMOTE_ADDR']) && session_id() === '') |
449
|
|
|
{ |
450
|
|
|
trigger_error($txt['ssi_session_broken'], E_USER_NOTICE); |
451
|
|
|
} |
452
|
|
|
|
453
|
|
|
// Without visiting the forum this session variable might not be set on submit. |
454
|
|
|
if (!isset($_SESSION['USER_AGENT']) && (!isset($_GET['ssi_function']) || $_GET['ssi_function'] !== 'pollVote')) |
455
|
|
|
{ |
456
|
|
|
$_SESSION['USER_AGENT'] = $req->user_agent(); |
457
|
|
|
} |
458
|
|
|
} |
459
|
|
|
|
460
|
|
|
/** |
461
|
|
|
* Used to ensure SSI requests are valid and not a probing attempt |
462
|
|
|
*/ |
463
|
|
|
private function _validRequestCheck() |
464
|
|
|
{ |
465
|
|
|
global $ssi_theme, $ssi_layers; |
466
|
|
|
|
467
|
|
|
if (isset($_REQUEST['ssi_theme']) && (int) $_REQUEST['ssi_theme'] === (int) $ssi_theme) |
468
|
|
|
{ |
469
|
|
|
die('No access...'); |
|
|
|
|
470
|
|
|
} |
471
|
|
|
elseif (isset($_COOKIE['ssi_theme']) && (int) $_COOKIE['ssi_theme'] === (int) $ssi_theme) |
472
|
|
|
{ |
473
|
|
|
die('No access...'); |
|
|
|
|
474
|
|
|
} |
475
|
|
|
elseif (isset($_REQUEST['ssi_layers'], $ssi_layers) && $_REQUEST['ssi_layers'] == $ssi_layers) |
476
|
|
|
{ |
477
|
|
|
die('No access...'); |
|
|
|
|
478
|
|
|
} |
479
|
|
|
|
480
|
|
|
// Yeah right |
481
|
|
|
if (isset($_REQUEST['context'])) |
482
|
|
|
{ |
483
|
|
|
die('No access...'); |
|
|
|
|
484
|
|
|
} |
485
|
|
|
} |
486
|
|
|
} |
487
|
|
|
|