Passed
Pull Request — release-2.1 (#7040)
by Jon
04:37
created

upgrade_unserialize()   A

Complexity

Conditions 6
Paths 6

Size

Total Lines 42
Code Lines 18

Duplication

Lines 0
Ratio 0 %

Importance

Changes 1
Bugs 0 Features 0
Metric Value
cc 6
eloc 18
c 1
b 0
f 0
nc 6
nop 1
dl 0
loc 42
rs 9.0444
1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2021 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC4
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1 RC4');
16
define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION);
17
define('SMF_SOFTWARE_YEAR', '2021');
18
define('SMF_LANG_VERSION', '2.1 RC4');
19
define('SMF_INSTALLING', 1);
20
21
define('JQUERY_VERSION', '3.6.0');
22
define('POSTGRE_TITLE', 'PostgreSQL');
23
define('MYSQL_TITLE', 'MySQL');
24
define('SMF_USER_AGENT', 'Mozilla/5.0 (' . php_uname('s') . ' ' . php_uname('m') . ') AppleWebKit/605.1.15 (KHTML, like Gecko)  SMF/' . strtr(SMF_VERSION, ' ', '.'));
25
if (!defined('TIME_START'))
26
	define('TIME_START', microtime(true));
27
28
/**
29
 * The minimum required PHP version.
30
 *
31
 * @var string
32
 */
33
$GLOBALS['required_php_version'] = '5.6.0';
34
35
/**
36
 * A list of supported database systems.
37
 *
38
 * @var array
39
 */
40
$databases = array(
41
	'mysql' => array(
42
		'name' => 'MySQL',
43
		'version' => '5.0.22',
44
		'version_check' => 'global $db_connection; return min(mysqli_get_server_info($db_connection), mysqli_get_client_info());',
45
		'utf8_support' => true,
46
		'utf8_version' => '5.0.22',
47
		'utf8_version_check' => 'global $db_connection; return mysqli_get_server_info($db_connection);',
48
		'alter_support' => true,
49
	),
50
	'postgresql' => array(
51
		'name' => 'PostgreSQL',
52
		'version' => '9.6',
53
		'version_check' => '$version = pg_version(); return $version[\'client\'];',
54
		'always_has_db' => true,
55
	),
56
);
57
58
/**
59
 * The maximum time a single substep may take, in seconds.
60
 *
61
 * @var int
62
 */
63
$timeLimitThreshold = 3;
64
65
/**
66
 * The current path to the upgrade.php file.
67
 *
68
 * @var string
69
 */
70
$upgrade_path = dirname(__FILE__);
71
72
/**
73
 * The URL of the current page.
74
 *
75
 * @var string
76
 */
77
$upgradeurl = $_SERVER['PHP_SELF'];
78
79
/**
80
 * Flag to disable the required administrator login.
81
 *
82
 * @var bool
83
 */
84
$disable_security = false;
85
86
/**
87
 * The amount of seconds allowed between logins.
88
 * If the first user to login is inactive for this amount of seconds, a second login is allowed.
89
 *
90
 * @var int
91
 */
92
$upcontext['inactive_timeout'] = 10;
93
94
global $txt;
95
96
// All the steps in detail.
97
// Number,Name,Function,Progress Weight.
98
$upcontext['steps'] = array(
99
	0 => array(1, 'upgrade_step_login', 'WelcomeLogin', 2),
100
	1 => array(2, 'upgrade_step_options', 'UpgradeOptions', 2),
101
	2 => array(3, 'upgrade_step_backup', 'BackupDatabase', 10),
102
	3 => array(4, 'upgrade_step_database', 'DatabaseChanges', 50),
103
	4 => array(5, 'upgrade_step_convertjson', 'serialize_to_json', 10),
104
	5 => array(6, 'upgrade_step_convertutf', 'ConvertUtf8', 20),
105
	6 => array(7, 'upgrade_step_delete', 'DeleteUpgrade', 1),
106
);
107
// Just to remember which one has files in it.
108
$upcontext['database_step'] = 3;
109
@set_time_limit(600);
110
if (!ini_get('safe_mode'))
111
{
112
	ini_set('mysql.connect_timeout', -1);
113
	ini_set('default_socket_timeout', 900);
114
}
115
// Clean the upgrade path if this is from the client.
116
if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
117
	for ($i = 1; $i < $_SERVER['argc']; $i++)
118
	{
119
		// Provide the help without possible errors if the enviornment isn't sane.
120
		if (in_array($_SERVER['argv'][$i], array('-h', '--help')))
121
		{
122
			cmdStep0();
123
			exit;
124
		}
125
126
		if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0)
127
			$upgrade_path = realpath(substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1]);
128
129
		// Cases where we do php other/upgrade.php --path=./
130
		if ($upgrade_path == './' && isset($_SERVER['PWD']))
131
			$upgrade_path = realpath($_SERVER['PWD']);
132
		// Cases where we do php upgrade.php --path=../
133
		elseif ($upgrade_path == '../' && isset($_SERVER['PWD']))
134
			$upgrade_path = dirname(realpath($_SERVER['PWD']));
135
	}
136
137
// Are we from the client?
138
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
139
{
140
	$command_line = true;
141
	$disable_security = true;
142
}
143
else
144
	$command_line = false;
145
146
// We can't do anything without these files.
147
foreach (array(
148
	dirname(__FILE__) . '/upgrade-helper.php',
149
	$upgrade_path . '/Settings.php'
150
) as $required_file)
151
{
152
	if (!file_exists($required_file))
153
		die(basename($required_file) . ' was not found where it was expected: ' . $required_file . '! Make sure you have uploaded ALL files from the upgrade package to your forum\'s root directory. The upgrader cannot continue.');
154
155
	require_once($required_file);
156
}
157
158
// We don't use "-utf8" anymore...  Tweak the entry that may have been loaded by Settings.php
159
if (isset($language))
160
	$language = str_ireplace('-utf8', '', basename($language, '.lng'));
161
162
// Figure out a valid language request (if any)
163
// Can't use $_GET until it's been cleaned, so do this manually and VERY restrictively! This even strips off those '-utf8' bits that we don't want.
164
if (isset($_SERVER['QUERY_STRING']) && preg_match('~\blang=(\w+)~', $_SERVER['QUERY_STRING'], $matches))
165
	$upcontext['lang'] = $matches[1];
166
167
// Are we logged in?
168
if (isset($upgradeData))
169
{
170
	$upcontext['user'] = json_decode(base64_decode($upgradeData), true);
171
172
	// Check for sensible values.
173
	if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400)
174
		$upcontext['user']['started'] = time();
175
	if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400)
176
		$upcontext['user']['updated'] = 0;
177
178
	$upcontext['started'] = $upcontext['user']['started'];
179
	$upcontext['updated'] = $upcontext['user']['updated'];
180
181
	$is_debug = !empty($upcontext['user']['debug']) ? true : false;
182
183
	$upcontext['skip_db_substeps'] = !empty($upcontext['user']['skip_db_substeps']);
184
}
185
186
// Nothing sensible?
187
if (empty($upcontext['updated']))
188
{
189
	$upcontext['started'] = time();
190
	$upcontext['updated'] = 0;
191
	$upcontext['skip_db_substeps'] = false;
192
	$upcontext['user'] = array(
193
		'id' => 0,
194
		'name' => 'Guest',
195
		'pass' => 0,
196
		'started' => $upcontext['started'],
197
		'updated' => $upcontext['updated'],
198
	);
199
}
200
201
// Try to load the language file... or at least define a few necessary strings for now.
202
load_lang_file();
203
204
// Load up some essential data...
205
loadEssentialData();
206
207
// Are we going to be mimic'ing SSI at this point?
208
if (isset($_GET['ssi']))
209
{
210
	require_once($sourcedir . '/Errors.php');
211
	require_once($sourcedir . '/Logging.php');
212
	require_once($sourcedir . '/Load.php');
213
	require_once($sourcedir . '/Security.php');
214
	require_once($sourcedir . '/Subs-Package.php');
215
216
	// SMF isn't started up properly, but loadUserSettings calls our cookies.
217
	if (!isset($smcFunc['json_encode']))
218
	{
219
		$smcFunc['json_encode'] = 'json_encode';
220
		$smcFunc['json_decode'] = 'smf_json_decode';
221
	}
222
223
	loadUserSettings();
224
	loadPermissions();
225
	reloadSettings();
226
}
227
228
// Include our helper functions.
229
require_once($sourcedir . '/Subs.php');
230
require_once($sourcedir . '/LogInOut.php');
231
require_once($sourcedir . '/Subs-Editor.php');
232
233
// Don't do security check if on Yabbse
234
if (!isset($modSettings['smfVersion']))
235
	$disable_security = true;
236
237
// This only exists if we're on SMF ;)
238
if (isset($modSettings['smfVersion']))
239
{
240
	$request = $smcFunc['db_query']('', '
241
		SELECT variable, value
242
		FROM {db_prefix}themes
243
		WHERE id_theme = {int:id_theme}
244
			AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
245
		array(
246
			'id_theme' => 1,
247
			'theme_url' => 'theme_url',
248
			'theme_dir' => 'theme_dir',
249
			'images_url' => 'images_url',
250
			'db_error_skip' => true,
251
		)
252
	);
253
	while ($row = $smcFunc['db_fetch_assoc']($request))
254
		$modSettings[$row['variable']] = $row['value'];
255
	$smcFunc['db_free_result']($request);
256
}
257
258
if (!isset($modSettings['theme_url']))
259
{
260
	$modSettings['theme_dir'] = $boarddir . '/Themes/default';
261
	$modSettings['theme_url'] = 'Themes/default';
262
	$modSettings['images_url'] = 'Themes/default/images';
263
}
264
if (!isset($settings['default_theme_url']))
265
	$settings['default_theme_url'] = $modSettings['theme_url'];
266
if (!isset($settings['default_theme_dir']))
267
	$settings['default_theme_dir'] = $modSettings['theme_dir'];
268
269
// Old DBs won't have this
270
if (!isset($modSettings['rand_seed']))
271
{
272
	if (!function_exists('cache_put_data'))
273
		require_once($sourcedir . '/Load.php');
274
	smf_seed_generator();
275
}
276
277
// This is needed in case someone invokes the upgrader using https when upgrading an http forum
278
if (httpsOn())
279
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
280
281
$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000;
282
283
// Have we got tracking data - if so use it (It will be clean!)
284
if (isset($_GET['data']))
285
{
286
	global $is_debug;
287
288
	$upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true);
289
	$upcontext['current_step'] = $upcontext['upgrade_status']['curstep'];
290
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
291
	$upcontext['rid'] = $upcontext['upgrade_status']['rid'];
292
	$support_js = $upcontext['upgrade_status']['js'];
293
294
	// Only set this if the upgrader status says so.
295
	if (empty($is_debug))
296
		$is_debug = $upcontext['upgrade_status']['debug'];
297
}
298
// Set the defaults.
299
else
300
{
301
	$upcontext['current_step'] = 0;
302
	$upcontext['rid'] = mt_rand(0, 5000);
303
	$upcontext['upgrade_status'] = array(
304
		'curstep' => 0,
305
		'lang' => isset($upcontext['lang']) ? $upcontext['lang'] : basename($language, '.lng'),
306
		'rid' => $upcontext['rid'],
307
		'pass' => 0,
308
		'debug' => 0,
309
		'js' => 0,
310
	);
311
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
312
}
313
314
// Now that we have the necessary info, make sure we loaded the right language file.
315
load_lang_file();
316
317
// Default title...
318
$upcontext['page_title'] = $txt['updating_smf_installation'];
319
320
// If this isn't the first stage see whether they are logging in and resuming.
321
if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step']))
322
	checkLogin();
323
324
if ($command_line)
325
	cmdStep0();
326
327
// Don't error if we're using xml.
328
if (isset($_GET['xml']))
329
	$upcontext['return_error'] = true;
330
331
// Loop through all the steps doing each one as required.
332
$upcontext['overall_percent'] = 0;
333
foreach ($upcontext['steps'] as $num => $step)
334
{
335
	if ($num >= $upcontext['current_step'])
336
	{
337
		// The current weight of this step in terms of overall progress.
338
		$upcontext['step_weight'] = $step[3];
339
		// Make sure we reset the skip button.
340
		$upcontext['skip'] = false;
341
342
		// We cannot proceed if we're not logged in.
343
		if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass'])
344
		{
345
			$upcontext['steps'][0][2]();
346
			break;
347
		}
348
349
		// Call the step and if it returns false that means pause!
350
		if (function_exists($step[2]) && $step[2]() === false)
351
			break;
352
		elseif (function_exists($step[2]))
353
		{
354
			//Start each new step with this unset, so the 'normal' template is called first
355
			unset($_GET['xml']);
356
			//Clear out warnings at the start of each step
357
			unset($upcontext['custom_warning']);
358
			$_GET['substep'] = 0;
359
			$upcontext['current_step']++;
360
		}
361
	}
362
	$upcontext['overall_percent'] += $step[3];
363
}
364
365
upgradeExit();
366
367
// Exit the upgrade script.
368
function upgradeExit($fallThrough = false)
369
{
370
	global $upcontext, $upgradeurl, $sourcedir, $command_line, $is_debug, $txt;
371
372
	// Save where we are...
373
	if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id']))
374
	{
375
		$upcontext['user']['step'] = $upcontext['current_step'];
376
		$upcontext['user']['substep'] = $_GET['substep'];
377
		$upcontext['user']['updated'] = time();
378
		$upcontext['user']['skip_db_substeps'] = !empty($upcontext['skip_db_substeps']);
379
		$upcontext['debug'] = $is_debug;
380
		$upgradeData = base64_encode(json_encode($upcontext['user']));
381
		require_once($sourcedir . '/Subs.php');
382
		require_once($sourcedir . '/Subs-Admin.php');
383
		updateSettingsFile(array('upgradeData' => $upgradeData));
384
		updateDbLastError(0);
385
	}
386
387
	// Handle the progress of the step, if any.
388
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
389
	{
390
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
391
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
392
	}
393
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
394
395
	// We usually dump our templates out.
396
	if (!$fallThrough)
397
	{
398
		// This should not happen my dear... HELP ME DEVELOPERS!!
399
		if (!empty($command_line))
400
		{
401
			if (function_exists('debug_print_backtrace'))
402
				debug_print_backtrace();
403
404
			printf("\n" . $txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
405
			flush();
406
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
407
		}
408
409
		if (!isset($_GET['xml']))
410
			template_upgrade_above();
411
		else
412
		{
413
			header('content-type: text/xml; charset=UTF-8');
414
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
415
			$upcontext['get_data'] = array();
416
			foreach ($_GET as $k => $v)
417
			{
418
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
419
				{
420
					$upcontext['get_data'][$k] = $v;
421
				}
422
			}
423
			template_xml_above();
424
		}
425
426
		// Call the template.
427
		if (isset($upcontext['sub_template']))
428
		{
429
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
430
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
431
432
			// Custom stuff to pass back?
433
			if (!empty($upcontext['query_string']))
434
				$upcontext['form_url'] .= $upcontext['query_string'];
435
436
			// Call the appropriate subtemplate
437
			if (is_callable('template_' . $upcontext['sub_template']))
438
				call_user_func('template_' . $upcontext['sub_template']);
439
			else
440
				die(sprintf($txt['error_invalid_template'], $upcontext['sub_template']));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
441
		}
442
443
		// Was there an error?
444
		if (!empty($upcontext['forced_error_message']))
445
			echo $upcontext['forced_error_message'];
446
447
		// Show the footer.
448
		if (!isset($_GET['xml']))
449
			template_upgrade_below();
450
		else
451
			template_xml_below();
452
	}
453
454
	// Show the upgrade time for CLI when we are completely done, if in debug mode.
455
	if (!empty($command_line) && $is_debug)
456
	{
457
		$active = time() - $upcontext['started'];
458
		$hours = floor($active / 3600);
459
		$minutes = intval(($active / 60) % 60);
460
		$seconds = intval($active % 60);
461
462
		if ($hours > 0)
463
			echo "\n" . '', sprintf($txt['upgrade_completed_time_hms'], $hours, $minutes, $seconds), '' . "\n";
464
		elseif ($minutes > 0)
465
			echo "\n" . '', sprintf($txt['upgrade_completed_time_ms'], $minutes, $seconds), '' . "\n";
466
		elseif ($seconds > 0)
467
			echo "\n" . '', sprintf($txt['upgrade_completed_time_s'], $seconds), '' . "\n";
468
	}
469
470
	// Bang - gone!
471
	die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
472
}
473
474
// Load the list of language files, and the current language file.
475
function load_lang_file()
476
{
477
	global $txt, $upcontext, $language, $modSettings, $upgrade_path, $command_line;
478
479
	static $lang_dir = '', $detected_languages = array(), $loaded_langfile = '';
480
481
	// Do we know where to look for the language files, or shall we just guess for now?
482
	$temp = isset($modSettings['theme_dir']) ? $modSettings['theme_dir'] . '/languages' : $upgrade_path . '/Themes/default/languages';
483
484
	if ($lang_dir != $temp)
485
	{
486
		$lang_dir = $temp;
487
		$detected_languages = array();
488
	}
489
490
	// Override the language file?
491
	if (isset($upcontext['language']))
492
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['language'] . '.php';
493
	elseif (isset($upcontext['lang']))
494
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['lang'] . '.php';
495
	elseif (isset($language))
496
		$_SESSION['upgrader_langfile'] = 'Install.' . $language . '.php';
497
498
	// Avoid pointless repetition
499
	if (isset($_SESSION['upgrader_langfile']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_langfile'])
500
		return;
501
502
	// Now try to find the language files
503
	if (empty($detected_languages))
504
	{
505
		// Make sure the languages directory actually exists.
506
		if (file_exists($lang_dir))
507
		{
508
			// Find all the "Install" language files in the directory.
509
			$dir = dir($lang_dir);
510
			while ($entry = $dir->read())
511
			{
512
				// Skip any old '-utf8' language files that might be lying around
513
				if (strpos($entry, '-utf8') !== false)
514
					continue;
515
516
				if (substr($entry, 0, 8) == 'Install.' && substr($entry, -4) == '.php')
517
					$detected_languages[$entry] = ucfirst(substr($entry, 8, strlen($entry) - 12));
518
			}
519
			$dir->close();
520
		}
521
		// Our guess was wrong, but that's fine. We'll try again after $modSettings['theme_dir'] is defined.
522
		elseif (!isset($modSettings['theme_dir']))
523
		{
524
			// Define a few essential strings for now.
525
			$txt['error_db_connect_settings'] = 'Cannot connect to the database server.<br><br>Please check that the database info variables are correct in Settings.php.';
526
			$txt['error_sourcefile_missing'] = 'Unable to find the Sources/%1$s file. Please make sure it was uploaded properly, and then try again.';
527
528
			$txt['warning_lang_old'] = 'The language files for your selected language, %1$s, have not been updated to the latest version. Upgrade will continue with the forum default, %2$s.';
529
			$txt['warning_lang_missing'] = 'The upgrader could not find the &quot;Install&quot; language file for your selected language, %1$s. Upgrade will continue with the forum default, %2$s.';
530
531
			return;
532
		}
533
	}
534
535
	// Didn't find any, show an error message!
536
	if (empty($detected_languages))
537
	{
538
		$from = explode('/', $command_line ? $upgrade_path : $_SERVER['PHP_SELF']);
539
		$to = explode('/', $lang_dir);
540
		$relPath = $to;
541
542
		foreach($from as $depth => $dir)
543
		{
544
			if ($dir === $to[$depth])
545
				array_shift($relPath);
546
			else
547
			{
548
				$remaining = count($from) - $depth;
549
				if ($remaining > 1)
550
				{
551
					$padLength = (count($relPath) + $remaining - 1) * -1;
552
					$relPath = array_pad($relPath, $padLength, '..');
553
					break;
554
				}
555
				else
556
					$relPath[0] = './' . $relPath[0];
557
			}
558
		}
559
		$relPath = implode(DIRECTORY_SEPARATOR, $relPath);
560
561
		// Command line?
562
		if ($command_line)
563
		{
564
			echo 'This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:', "\n",
565
				$relPath, "\n",
566
				'In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you have uploaded all the files in the distribution', "\n",
567
				'If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.', "\n";
568
			die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
569
		}
570
571
		// Let's not cache this message, eh?
572
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
573
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
574
		header('Cache-Control: no-cache');
575
576
		echo '<!DOCTYPE html>
577
			<html>
578
				<head>
579
					<title>SMF Upgrader: Error!</title>
580
						<style>
581
							body {
582
								font-family: sans-serif;
583
								max-width: 700px; }
584
585
								h1 {
586
									font-size: 14pt; }
587
588
								.directory {
589
									margin: 0.3em;
590
									font-family: monospace;
591
									font-weight: bold; }
592
						</style>
593
				</head>
594
				<body>
595
					<h1>A critical error has occurred.</h1>
596
						<p>This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:</p>
597
						<div class="directory">', $relPath, '</div>
598
						<p>In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you <strong>have uploaded all the files in the distribution</strong>.</p>
599
						<p>If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.</p>
600
						<p>If you continue to get this error message, feel free to <a href="https://support.simplemachines.org/">look to us for support</a>.</p>
601
				</body>
602
			</html>';
603
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
604
	}
605
606
	// Make sure it exists. If it doesn't, reset it.
607
	if (!isset($_SESSION['upgrader_langfile']) || preg_match('~[^\w.-]~', $_SESSION['upgrader_langfile']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_langfile']))
608
	{
609
		// Use the first one...
610
		list ($_SESSION['upgrader_langfile']) = array_keys($detected_languages);
611
612
		// If we have English and some other language, use the other language.
613
		if ($_SESSION['upgrader_langfile'] == 'Install.english.php' && count($detected_languages) > 1)
614
			list (, $_SESSION['upgrader_langfile']) = array_keys($detected_languages);
615
	}
616
617
	// For backup we load English at first, then the second language will overwrite it.
618
	if ($_SESSION['upgrader_langfile'] != 'Install.english.php')
619
	{
620
		require_once($lang_dir . '/index.english.php');
621
		require_once($lang_dir . '/Install.english.php');
622
	}
623
624
	// And now include the actual language file itself.
625
	require_once($lang_dir . '/' . str_replace('Install.', 'index.', $_SESSION['upgrader_langfile']));
626
	require_once($lang_dir . '/' . $_SESSION['upgrader_langfile']);
627
628
	// Remember what we've done
629
	$loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_langfile'];
630
}
631
632
// Used to direct the user to another location.
633
function redirectLocation($location, $addForm = true)
634
{
635
	global $upgradeurl, $upcontext, $command_line;
636
637
	// Command line users can't be redirected.
638
	if ($command_line)
639
		upgradeExit(true);
640
641
	// Are we providing the core info?
642
	if ($addForm)
643
	{
644
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
645
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
646
	}
647
648
	while (@ob_end_clean())
649
		header('location: ' . strtr($location, array('&amp;' => '&')));
650
651
	// Exit - saving status as we go.
652
	upgradeExit(true);
653
}
654
655
// Load all essential data and connect to the DB as this is pre SSI.php
656
function loadEssentialData()
657
{
658
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection;
659
	global $db_prefix, $db_character_set, $db_type, $db_port, $db_show_debug;
660
	global $db_mb4, $modSettings, $sourcedir, $smcFunc, $txt, $utf8;
661
662
	// Report all errors if admin wants them or this is a pre-release version.
663
	if (!empty($db_show_debug) || strspn(SMF_VERSION, '1234567890.') !== strlen(SMF_VERSION))
664
		error_reporting(E_ALL);
665
	// Otherwise, report all errors except for deprecation notices.
666
	else
667
		error_reporting(E_ALL & ~E_DEPRECATED);
668
669
	define('SMF', 1);
670
	header('X-Frame-Options: SAMEORIGIN');
671
	header('X-XSS-Protection: 1');
672
	header('X-Content-Type-Options: nosniff');
673
674
	// Start the session.
675
	if (@ini_get('session.save_handler') == 'user')
676
		@ini_set('session.save_handler', 'files');
677
	@session_start();
678
679
	if (empty($smcFunc))
680
		$smcFunc = array();
681
682
	require_once($sourcedir . '/Subs.php');
683
684
	$smcFunc['random_int'] = function($min = 0, $max = PHP_INT_MAX)
685
	{
686
		global $sourcedir;
687
688
		// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
689
		if (!is_callable('random_int'))
690
			require_once($sourcedir . '/random_compat/random.php');
691
692
		return random_int($min, $max);
693
	};
694
695
	// This is now needed for loadUserSettings()
696
	$smcFunc['random_bytes'] = function($bytes)
697
	{
698
		global $sourcedir;
699
700
		if (!is_callable('random_bytes'))
701
			require_once($sourcedir . '/random_compat/random.php');
702
703
		return random_bytes($bytes);
704
	};
705
706
	// We need this for authentication and some upgrade code
707
	require_once($sourcedir . '/Subs-Auth.php');
708
	require_once($sourcedir . '/Class-Package.php');
709
710
	$smcFunc['strtolower'] = 'smf_strtolower';
711
712
	// Initialize everything...
713
	initialize_inputs();
714
715
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
716
717
	// Get the database going!
718
	if (empty($db_type) || $db_type == 'mysqli')
719
	{
720
		$db_type = 'mysql';
721
		// If overriding $db_type, need to set its settings.php entry too
722
		$changes = array();
723
		$changes['db_type'] = 'mysql';
724
		require_once($sourcedir . '/Subs-Admin.php');
725
		updateSettingsFile($changes);
726
	}
727
728
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
729
	{
730
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
731
732
		// Make the connection...
733
		if (empty($db_connection))
734
		{
735
			$options = array('non_fatal' => true);
736
			// Add in the port if needed
737
			if (!empty($db_port))
738
				$options['port'] = $db_port;
739
740
			if (!empty($db_mb4))
741
				$options['db_mb4'] = $db_mb4;
742
743
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
744
		}
745
		else
746
			// If we've returned here, ping/reconnect to be safe
747
			$smcFunc['db_ping']($db_connection);
748
749
		// Oh dear god!!
750
		if ($db_connection === null)
751
		{
752
			// Get error info...  Recast just in case we get false or 0...
753
			$error_message = $smcFunc['db_connect_error']();
754
			if (empty($error_message))
755
				$error_message = '';
756
			$error_number = $smcFunc['db_connect_errno']();
757
			if (empty($error_number))
758
				$error_number = '';
759
			$db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message;
760
761
			die($txt['error_db_connect_settings'] . '<br><br>' . $db_error);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
762
		}
763
764
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
765
			$smcFunc['db_query']('', '
766
				SET NAMES {string:db_character_set}',
767
				array(
768
					'db_error_skip' => true,
769
					'db_character_set' => $db_character_set,
770
				)
771
			);
772
773
		// Load the modSettings data...
774
		$request = $smcFunc['db_query']('', '
775
			SELECT variable, value
776
			FROM {db_prefix}settings',
777
			array(
778
				'db_error_skip' => true,
779
			)
780
		);
781
		$modSettings = array();
782
		while ($row = $smcFunc['db_fetch_assoc']($request))
783
			$modSettings[$row['variable']] = $row['value'];
784
		$smcFunc['db_free_result']($request);
785
	}
786
	else
787
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

787
		return /** @scrutinizer ignore-call */ throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
Loading history...
788
789
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
790
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
791
	{
792
		require_once($sourcedir . '/QueryString.php');
793
		cleanRequest();
794
	}
795
796
	if (!isset($_GET['substep']))
797
		$_GET['substep'] = 0;
798
}
799
800
function initialize_inputs()
801
{
802
	global $start_time, $db_type, $upgrade_path;
803
804
	$start_time = time();
805
806
	umask(0);
807
808
	ob_start();
809
810
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
811
	ignore_user_abort(true);
812
813
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
814
	if (isset($_GET['delete']))
815
	{
816
		deleteFile(__FILE__);
0 ignored issues
show
Bug introduced by
The function deleteFile was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

816
		/** @scrutinizer ignore-call */ 
817
  deleteFile(__FILE__);
Loading history...
817
818
		// And the extra little files ;).
819
		deleteFile(dirname(__FILE__) . '/upgrade_1-0.sql');
820
		deleteFile(dirname(__FILE__) . '/upgrade_1-1.sql');
821
		deleteFile(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
822
		deleteFile(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
823
		deleteFile(dirname(__FILE__) . '/upgrade-helper.php');
824
825
		$dh = opendir(dirname(__FILE__));
826
		while ($file = readdir($dh))
827
		{
828
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
829
				deleteFile(dirname(__FILE__) . '/' . $file);
830
		}
831
		closedir($dh);
832
833
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
834
		// 1.1 Sources files not in 2.0+
835
		deleteFile($upgrade_path . '/Sources/ModSettings.php');
836
		// 1.1 Templates that don't exist any more (e.g. renamed)
837
		deleteFile($upgrade_path . '/Themes/default/Combat.template.php');
838
		deleteFile($upgrade_path . '/Themes/default/Modlog.template.php');
839
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
840
		deleteFile($upgrade_path . '/Themes/default/fader.js');
841
		deleteFile($upgrade_path . '/Themes/default/script.js');
842
		deleteFile($upgrade_path . '/Themes/default/spellcheck.js');
843
		deleteFile($upgrade_path . '/Themes/default/xml_board.js');
844
		deleteFile($upgrade_path . '/Themes/default/xml_topic.js');
845
846
		// 2.0 Sources files not in 2.1+
847
		deleteFile($upgrade_path . '/Sources/DumpDatabase.php');
848
		deleteFile($upgrade_path . '/Sources/LockTopic.php');
849
850
		header('location: http' . (httpsOn() ? 's' : '') . '://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
851
		exit;
852
	}
853
854
	// Something is causing this to happen, and it's annoying.  Stop it.
855
	$temp = 'upgrade_php?step';
856
	while (strlen($temp) > 4)
857
	{
858
		if (isset($_GET[$temp]))
859
			unset($_GET[$temp]);
860
		$temp = substr($temp, 1);
861
	}
862
863
	// Force a step, defaulting to 0.
864
	$_GET['step'] = (int) @$_GET['step'];
865
	$_GET['substep'] = (int) @$_GET['substep'];
866
}
867
868
// Step 0 - Let's welcome them in and ask them to login!
869
function WelcomeLogin()
870
{
871
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
872
	global $smcFunc, $db_type, $databases, $boardurl, $upgrade_path;
873
874
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
875
	global $txt;
876
877
	$upcontext['sub_template'] = 'welcome_message';
878
879
	// Check for some key files - one template, one language, and a new and an old source file.
880
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
881
		&& @file_exists($sourcedir . '/QueryString.php')
882
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
883
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
884
885
	// Need legacy scripts?
886
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
887
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
888
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
889
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
890
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
891
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
892
893
	// We don't need "-utf8" files anymore...
894
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
895
896
	if (!$check)
897
		// Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb.
898
		return throw_error($txt['error_upgrade_files_missing']);
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

898
		return /** @scrutinizer ignore-call */ throw_error($txt['error_upgrade_files_missing']);
Loading history...
899
900
	// Do they meet the install requirements?
901
	if (!php_version_check())
902
		return throw_error($txt['error_php_too_low']);
903
904
	if (!db_version_check())
905
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
906
907
	// Do some checks to make sure they have proper privileges
908
	db_extend('packages');
909
910
	// CREATE
911
	$create = $smcFunc['db_create_table']('{db_prefix}priv_check', array(array('name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true)), array(array('columns' => array('id_test'), 'type' => 'primary')), array(), 'overwrite');
912
913
	// ALTER
914
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
915
916
	// DROP
917
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
918
919
	// Sorry... we need CREATE, ALTER and DROP
920
	if (!$create || !$alter || !$drop)
921
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
922
923
	// Do a quick version spot check.
924
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
0 ignored issues
show
Bug introduced by
It seems like @file($boarddir . '/index.php') can also be of type false; however, parameter $pieces of implode() 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

924
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
925
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
926
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
927
		return throw_error($txt['error_upgrade_old_files']);
928
929
	// What absolutely needs to be writable?
930
	$writable_files = array(
931
		$boarddir . '/Settings.php',
932
		$boarddir . '/Settings_bak.php',
933
	);
934
935
	// Only check for minified writable files if we have it enabled or not set.
936
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
937
		$writable_files += array(
938
			$modSettings['theme_dir'] . '/css/minified.css',
939
			$modSettings['theme_dir'] . '/scripts/minified.js',
940
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
941
		);
942
943
	// Do we need to add this setting?
944
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
945
946
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
947
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
948
949
	// This little fellow has to cooperate...
950
	quickFileWritable($custom_av_dir);
0 ignored issues
show
Bug introduced by
The function quickFileWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

950
	/** @scrutinizer ignore-call */ 
951
 quickFileWritable($custom_av_dir);
Loading history...
951
952
	// Are we good now?
953
	if (!is_writable($custom_av_dir))
954
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
955
	elseif ($need_settings_update)
956
	{
957
		if (!function_exists('cache_put_data'))
958
			require_once($sourcedir . '/Load.php');
959
960
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
961
		updateSettings(array('custom_avatar_url' => $custom_av_url));
962
	}
963
964
	require_once($sourcedir . '/Security.php');
965
966
	// Check the cache directory.
967
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
968
	if (!file_exists($cachedir_temp))
969
		@mkdir($cachedir_temp);
970
971
	if (!file_exists($cachedir_temp))
972
		return throw_error($txt['error_cache_not_found']);
973
974
	quickFileWritable($cachedir_temp . '/db_last_error.php');
975
976
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
977
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
978
	elseif (!isset($_GET['skiplang']))
979
	{
980
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
981
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
982
983
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
984
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
985
	}
986
987
	if (!makeFilesWritable($writable_files))
0 ignored issues
show
Bug introduced by
The function makeFilesWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

987
	if (!/** @scrutinizer ignore-call */ makeFilesWritable($writable_files))
Loading history...
988
		return false;
989
990
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
991
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
992
		return throw_error($txt['error_agreement_not_writable']);
993
994
	// Upgrade the agreement.
995
	elseif (isset($modSettings['agreement']))
996
	{
997
		$fp = fopen($boarddir . '/agreement.txt', 'w');
998
		fwrite($fp, $modSettings['agreement']);
999
		fclose($fp);
1000
	}
1001
1002
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
1003
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr($upgrade_path, array('/' => '', '\\' => '')))
1004
		$upcontext['warning'] = '
1005
			' . sprintf($txt['upgrade_forumdir_settings'], $boarddir, $upgrade_path) . '<br>
1006
			<ul>
1007
				<li>' . $txt['upgrade_forumdir'] . '  ' . $boarddir . '</li>
1008
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
1009
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
1010
			</ul>
1011
			' . $txt['upgrade_incorrect_settings'] . '';
1012
1013
	// Confirm mbstring is loaded...
1014
	if (!extension_loaded('mbstring'))
1015
		return throw_error($txt['install_no_mbstring']);
1016
1017
	// Check for https stream support.
1018
	$supported_streams = stream_get_wrappers();
1019
	if (!in_array('https', $supported_streams))
1020
		$upcontext['custom_warning'] = $txt['install_no_https'];
1021
1022
	// Either we're logged in or we're going to present the login.
1023
	if (checkLogin())
1024
		return true;
1025
1026
	$upcontext += createToken('login');
1027
1028
	return false;
1029
}
1030
1031
// Step 0.5: Does the login work?
1032
function checkLogin()
1033
{
1034
	global $modSettings, $upcontext, $disable_security;
1035
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
1036
1037
	// Are we trying to login?
1038
	if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security))
1039
	{
1040
		// If we've disabled security pick a suitable name!
1041
		if (empty($_POST['user']))
1042
			$_POST['user'] = 'Administrator';
1043
1044
		// Before 2.0 these column names were different!
1045
		$oldDB = false;
1046
		if (empty($db_type) || $db_type == 'mysql')
1047
		{
1048
			$request = $smcFunc['db_query']('', '
1049
				SHOW COLUMNS
1050
				FROM {db_prefix}members
1051
				LIKE {string:member_name}',
1052
				array(
1053
					'member_name' => 'memberName',
1054
					'db_error_skip' => true,
1055
				)
1056
			);
1057
			if ($smcFunc['db_num_rows']($request) != 0)
1058
				$oldDB = true;
1059
			$smcFunc['db_free_result']($request);
1060
		}
1061
1062
		// Get what we believe to be their details.
1063
		if (!$disable_security)
1064
		{
1065
			if ($oldDB)
1066
				$request = $smcFunc['db_query']('', '
1067
					SELECT id_member, memberName AS member_name, passwd, id_group,
1068
						additionalGroups AS additional_groups, lngfile
1069
					FROM {db_prefix}members
1070
					WHERE memberName = {string:member_name}',
1071
					array(
1072
						'member_name' => $_POST['user'],
1073
						'db_error_skip' => true,
1074
					)
1075
				);
1076
			else
1077
				$request = $smcFunc['db_query']('', '
1078
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
1079
					FROM {db_prefix}members
1080
					WHERE member_name = {string:member_name}',
1081
					array(
1082
						'member_name' => $_POST['user'],
1083
						'db_error_skip' => true,
1084
					)
1085
				);
1086
			if ($smcFunc['db_num_rows']($request) != 0)
1087
			{
1088
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1089
1090
				$groups = explode(',', $addGroups);
1091
				$groups[] = $id_group;
1092
1093
				foreach ($groups as $k => $v)
1094
					$groups[$k] = (int) $v;
1095
1096
				$sha_passwd = sha1(strtolower($name) . $_REQUEST['passwrd']);
1097
1098
				// We don't use "-utf8" anymore...
1099
				$user_language = str_ireplace('-utf8', '', $user_language);
1100
			}
1101
			else
1102
				$upcontext['username_incorrect'] = true;
1103
1104
			$smcFunc['db_free_result']($request);
1105
		}
1106
		$upcontext['username'] = $_POST['user'];
1107
1108
		// Track whether javascript works!
1109
		if (isset($_POST['js_works']))
1110
		{
1111
			if (!empty($_POST['js_works']))
1112
			{
1113
				$upcontext['upgrade_status']['js'] = 1;
1114
				$support_js = 1;
1115
			}
1116
			else
1117
				$support_js = 0;
1118
		}
1119
1120
		// Note down the version we are coming from.
1121
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1122
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1123
1124
		// Didn't get anywhere?
1125
		if (!$disable_security && (empty($sha_passwd) || (!empty($password) ? $password : '') != $sha_passwd) && !hash_verify_password((!empty($name) ? $name : ''), $_REQUEST['passwrd'], (!empty($password) ? $password : '')) && empty($upcontext['username_incorrect']))
1126
		{
1127
			// MD5?
1128
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1129
			if ($md5pass != $password)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.
Loading history...
1130
			{
1131
				$upcontext['password_failed'] = true;
1132
				// Disable the hashing this time.
1133
				$upcontext['disable_login_hashing'] = true;
1134
			}
1135
		}
1136
1137
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1138
		{
1139
			// Set the password.
1140
			if (!$disable_security)
1141
			{
1142
				// Do we actually have permission?
1143
				if (!in_array(1, $groups))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $groups does not seem to be defined for all execution paths leading up to this point.
Loading history...
1144
				{
1145
					$request = $smcFunc['db_query']('', '
1146
						SELECT permission
1147
						FROM {db_prefix}permissions
1148
						WHERE id_group IN ({array_int:groups})
1149
							AND permission = {string:admin_forum}',
1150
						array(
1151
							'groups' => $groups,
1152
							'admin_forum' => 'admin_forum',
1153
							'db_error_skip' => true,
1154
						)
1155
					);
1156
					if ($smcFunc['db_num_rows']($request) == 0)
1157
						return throw_error($txt['error_not_admin']);
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1157
						return /** @scrutinizer ignore-call */ throw_error($txt['error_not_admin']);
Loading history...
1158
					$smcFunc['db_free_result']($request);
1159
				}
1160
1161
				$upcontext['user']['id'] = $id_member;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_member does not seem to be defined for all execution paths leading up to this point.
Loading history...
1162
				$upcontext['user']['name'] = $name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name does not seem to be defined for all execution paths leading up to this point.
Loading history...
1163
			}
1164
			else
1165
			{
1166
				$upcontext['user']['id'] = 1;
1167
				$upcontext['user']['name'] = 'Administrator';
1168
			}
1169
1170
			if (!is_callable('random_int'))
1171
				require_once('Sources/random_compat/random.php');
1172
1173
			$upcontext['user']['pass'] = random_int(0, 60000);
1174
			// This basically is used to match the GET variables to Settings.php.
1175
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1176
1177
			// Set the language to that of the user?
1178
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1179
			{
1180
				$user_language = basename($user_language, '.lng');
1181
				$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
0 ignored issues
show
Bug introduced by
It seems like @file($modSettings['them...user_language . '.php') can also be of type false; however, parameter $pieces of implode() 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

1181
				$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
Loading history...
1182
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1183
1184
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1185
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1186
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1187
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1188
				else
1189
				{
1190
					// Set this as the new language.
1191
					$upcontext['language'] = $user_language;
1192
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1193
1194
					// Include the file.
1195
					load_lang_file();
1196
				}
1197
			}
1198
1199
			// If we're resuming set the step and substep to be correct.
1200
			if (isset($_POST['cont']))
1201
			{
1202
				$upcontext['current_step'] = $upcontext['user']['step'];
1203
				$_GET['substep'] = $upcontext['user']['substep'];
1204
			}
1205
1206
			return true;
1207
		}
1208
	}
1209
1210
	return false;
1211
}
1212
1213
// Step 1: Do the maintenance and backup.
1214
function UpgradeOptions()
1215
{
1216
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1217
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1218
	global $auth_secret;
1219
1220
	$upcontext['sub_template'] = 'upgrade_options';
1221
	$upcontext['page_title'] = $txt['upgrade_options'];
1222
1223
	db_extend('packages');
1224
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1225
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1226
1227
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1228
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1229
1230
	$upcontext['migrate_settings_recommended'] = empty($modSettings['smfVersion']) || version_compare(strtolower($modSettings['smfVersion']), substr(SMF_VERSION, 0, strpos(SMF_VERSION, '.') + 1 + strspn(SMF_VERSION, '1234567890', strpos(SMF_VERSION, '.') + 1)) . ' foo', '<');
1231
1232
	unset($member_columns);
1233
1234
	// If we've not submitted then we're done.
1235
	if (empty($_POST['upcont']))
1236
		return false;
1237
1238
	// We cannot execute this step in strict mode - strict mode data fixes are not applied yet
1239
	setSqlMode(false);
1240
1241
	// Firstly, if they're enabling SM stat collection just do it.
1242
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1243
	{
1244
		$upcontext['allow_sm_stats'] = true;
1245
1246
		// Don't register if we still have a key.
1247
		if (empty($modSettings['sm_stats_key']))
1248
		{
1249
			// Attempt to register the site etc.
1250
			$fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr);
1251
			if (!$fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1252
				$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1253
			if ($fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1254
			{
1255
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1256
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1257
				$out .= 'Connection: Close' . "\r\n\r\n";
1258
				fwrite($fp, $out);
1259
1260
				$return_data = '';
1261
				while (!feof($fp))
1262
					$return_data .= fgets($fp, 128);
1263
1264
				fclose($fp);
1265
1266
				// Get the unique site ID.
1267
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1268
1269
				if (!empty($ID[1]))
1270
					$smcFunc['db_insert']('replace',
1271
						$db_prefix . 'settings',
1272
						array('variable' => 'string', 'value' => 'string'),
1273
						array(
1274
							array('sm_stats_key', $ID[1]),
1275
							array('enable_sm_stats', 1),
1276
						),
1277
						array('variable')
1278
					);
1279
			}
1280
		}
1281
		else
1282
		{
1283
			$smcFunc['db_insert']('replace',
1284
				$db_prefix . 'settings',
1285
				array('variable' => 'string', 'value' => 'string'),
1286
				array('enable_sm_stats', 1),
1287
				array('variable')
1288
			);
1289
		}
1290
	}
1291
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1292
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1293
		$smcFunc['db_query']('', '
1294
			DELETE FROM {db_prefix}settings
1295
			WHERE variable = {string:enable_sm_stats}',
1296
			array(
1297
				'enable_sm_stats' => 'enable_sm_stats',
1298
				'db_error_skip' => true,
1299
			)
1300
		);
1301
1302
	// Deleting old karma stuff?
1303
	$_SESSION['delete_karma'] = !empty($_POST['delete_karma']);
1304
1305
	// Emptying the error log?
1306
	$_SESSION['empty_error'] = !empty($_POST['empty_error']);
1307
1308
	$changes = array();
1309
1310
	// Add proxy settings.
1311
	if (!isset($GLOBALS['image_proxy_secret']) || $GLOBALS['image_proxy_secret'] == 'smfisawesome')
1312
		$changes['image_proxy_secret'] = substr(sha1(mt_rand()), 0, 20);
1313
	if (!isset($GLOBALS['image_proxy_maxsize']))
1314
		$changes['image_proxy_maxsize'] = 5190;
1315
	if (!isset($GLOBALS['image_proxy_enabled']))
1316
		$changes['image_proxy_enabled'] = false;
1317
1318
	// If $boardurl reflects https, set force_ssl
1319
	if (!function_exists('cache_put_data'))
1320
		require_once($sourcedir . '/Load.php');
1321
	if (stripos($boardurl, 'https://') !== false && !isset($modSettings['force_ssl']))
1322
		updateSettings(array('force_ssl' => '1'));
1323
1324
	// If we're overriding the language follow it through.
1325
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1326
		$changes['language'] = $upcontext['lang'];
1327
1328
	if (!empty($_POST['maint']))
1329
	{
1330
		$changes['maintenance'] = 2;
1331
		// Remember what it was...
1332
		$upcontext['user']['main'] = $maintenance;
1333
1334
		if (!empty($_POST['maintitle']))
1335
		{
1336
			$changes['mtitle'] = $_POST['maintitle'];
1337
			$changes['mmessage'] = $_POST['mainmessage'];
1338
		}
1339
		else
1340
		{
1341
			$changes['mtitle'] = $txt['mtitle'];
1342
			$changes['mmessage'] = $txt['mmessage'];
1343
		}
1344
	}
1345
1346
	if ($command_line)
1347
		echo ' * Updating Settings.php...';
1348
1349
	// Fix some old paths.
1350
	if (substr($boarddir, 0, 1) == '.')
1351
		$changes['boarddir'] = fixRelativePath($boarddir);
1352
1353
	if (substr($sourcedir, 0, 1) == '.')
1354
		$changes['sourcedir'] = fixRelativePath($sourcedir);
1355
1356
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1357
		$changes['cachedir'] = fixRelativePath($boarddir) . '/cache';
1358
1359
	// Migrate cache settings.
1360
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1361
	if (!isset($GLOBALS['cache_enable']))
1362
		$changes += array(
1363
			'cache_accelerator' => upgradeCacheSettings(),
0 ignored issues
show
Bug introduced by
The function upgradeCacheSettings was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1363
			'cache_accelerator' => /** @scrutinizer ignore-call */ upgradeCacheSettings(),
Loading history...
1364
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1365
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? $modSettings['cache_memcached'] : '',
1366
		);
1367
1368
	// If they have a "host:port" setup for the host, split that into separate values
1369
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1370
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1371
	{
1372
		list ($db_server, $db_port) = explode(':', $db_server);
1373
1374
		$changes['db_server'] = $db_server;
1375
1376
		// Only set this if we're not using the default port
1377
		if ($db_port != ini_get('mysqli.default_port'))
1378
			$changes['db_port'] = (int) $db_port;
1379
	}
1380
1381
	// If db_port is set and is the same as the default, set it to 0.
1382
	if (!empty($db_port))
1383
	{
1384
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1385
			$changes['db_port'] = 0;
1386
1387
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1388
			$changes['db_port'] = 0;
1389
	}
1390
1391
	// Maybe we haven't had this option yet?
1392
	if (empty($packagesdir))
1393
		$changes['packagesdir'] = fixRelativePath($boarddir) . '/Packages';
1394
1395
	// Add support for $tasksdir var.
1396
	if (empty($tasksdir))
1397
		$changes['tasksdir'] = fixRelativePath($sourcedir) . '/tasks';
1398
1399
	// Make sure we fix the language as well.
1400
	if (stristr($language, '-utf8'))
1401
		$changes['language'] = str_ireplace('-utf8', '', $language);
1402
1403
	// @todo Maybe change the cookie name if going to 1.1, too?
1404
1405
	// Ensure this doesn't get lost in translation.
1406
	$changes['upgradeData'] = base64_encode(json_encode($upcontext['user']));
1407
1408
	// Update Settings.php with the new settings, and rebuild if they selected that option.
1409
	require_once($sourcedir . '/Subs.php');
1410
	require_once($sourcedir . '/Subs-Admin.php');
1411
	$res = updateSettingsFile($changes, false, !empty($_POST['migrateSettings']));
1412
1413
	if ($command_line && $res)
1414
		echo ' Successful.' . "\n";
1415
	elseif ($command_line && !$res)
1416
	{
1417
		echo ' FAILURE.' . "\n";
1418
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1419
	}
1420
1421
	// Are we doing debug?
1422
	if (isset($_POST['debug']))
1423
	{
1424
		$upcontext['upgrade_status']['debug'] = true;
1425
		$is_debug = true;
1426
	}
1427
1428
	// If we're not backing up then jump one.
1429
	if (empty($_POST['backup']))
1430
		$upcontext['current_step']++;
1431
1432
	// If we've got here then let's proceed to the next step!
1433
	return true;
1434
}
1435
1436
// Backup the database - why not...
1437
function BackupDatabase()
1438
{
1439
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1440
1441
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1442
	$upcontext['page_title'] = $txt['backup_database'];
1443
1444
	// Done it already - js wise?
1445
	if (!empty($_POST['backup_done']))
1446
		return true;
1447
1448
	// We cannot execute this step in strict mode - strict mode data fixes are not applied yet
1449
	setSqlMode(false);
1450
1451
	// Some useful stuff here.
1452
	db_extend();
1453
1454
	// Might need this as well
1455
	db_extend('packages');
1456
1457
	// Get all the table names.
1458
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1459
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1460
	$tables = $smcFunc['db_list_tables']($db, $filter);
1461
1462
	$table_names = array();
1463
	foreach ($tables as $table)
1464
		if (substr($table, 0, 7) !== 'backup_')
1465
			$table_names[] = $table;
1466
1467
	$upcontext['table_count'] = count($table_names);
1468
	$upcontext['cur_table_num'] = $_GET['substep'];
1469
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1470
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1471
	// For non-java auto submit...
1472
	$file_steps = $upcontext['table_count'];
1473
1474
	// What ones have we already done?
1475
	foreach ($table_names as $id => $table)
1476
		if ($id < $_GET['substep'])
1477
			$upcontext['previous_tables'][] = $table;
1478
1479
	if ($command_line)
1480
		echo 'Backing Up Tables.';
1481
1482
	// If we don't support javascript we backup here.
1483
	if (!$support_js || isset($_GET['xml']))
1484
	{
1485
		// Backup each table!
1486
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1487
		{
1488
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1489
			$upcontext['cur_table_num'] = $substep + 1;
1490
1491
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1492
1493
			// Do we need to pause?
1494
			nextSubstep($substep);
1495
1496
			backupTable($table_names[$substep]);
1497
1498
			// If this is XML to keep it nice for the user do one table at a time anyway!
1499
			if (isset($_GET['xml']))
1500
				return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1501
		}
1502
1503
		if ($command_line)
1504
		{
1505
			echo "\n" . ' Successful.\'' . "\n";
1506
			flush();
1507
		}
1508
		$upcontext['step_progress'] = 100;
1509
1510
		$_GET['substep'] = 0;
1511
		// Make sure we move on!
1512
		return true;
1513
	}
1514
1515
	// Either way next place to post will be database changes!
1516
	$_GET['substep'] = 0;
1517
	return false;
1518
}
1519
1520
// Backup one table...
1521
function backupTable($table)
1522
{
1523
	global $command_line, $db_prefix, $smcFunc;
1524
1525
	if ($command_line)
1526
	{
1527
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1528
		flush();
1529
	}
1530
1531
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1532
1533
	if ($command_line)
1534
		echo ' done.';
1535
}
1536
1537
// Step 2: Everything.
1538
function DatabaseChanges()
1539
{
1540
	global $db_prefix, $modSettings, $smcFunc, $txt;
1541
	global $upcontext, $support_js, $db_type, $boarddir;
1542
1543
	// Have we just completed this?
1544
	if (!empty($_POST['database_done']))
1545
		return true;
1546
1547
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1548
	$upcontext['page_title'] = $txt['database_changes'];
1549
1550
	$upcontext['delete_karma'] = !empty($_SESSION['delete_karma']);
1551
	$upcontext['empty_error'] = !empty($_SESSION['empty_error']);
1552
1553
	// All possible files.
1554
	// Name, < version, insert_on_complete
1555
	// Last entry in array indicates whether to use sql_mode of STRICT or not.
1556
	$files = array(
1557
		array('upgrade_1-0.sql', '1.1', '1.1 RC0', false),
1558
		array('upgrade_1-1.sql', '2.0', '2.0 a', false),
1559
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0', false),
1560
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION, true),
1561
	);
1562
1563
	// How many files are there in total?
1564
	if (isset($_GET['filecount']))
1565
		$upcontext['file_count'] = (int) $_GET['filecount'];
1566
	else
1567
	{
1568
		$upcontext['file_count'] = 0;
1569
		foreach ($files as $file)
1570
		{
1571
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1572
				$upcontext['file_count']++;
1573
		}
1574
	}
1575
1576
	// Do each file!
1577
	$did_not_do = count($files) - $upcontext['file_count'];
1578
	$upcontext['step_progress'] = 0;
1579
	$upcontext['cur_file_num'] = 0;
1580
	foreach ($files as $file)
1581
	{
1582
		if ($did_not_do)
1583
			$did_not_do--;
1584
		else
1585
		{
1586
			$upcontext['cur_file_num']++;
1587
			$upcontext['cur_file_name'] = $file[0];
1588
			// Do we actually need to do this still?
1589
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1590
			{
1591
				// Use STRICT mode on more recent steps
1592
				setSqlMode($file[3]);
1593
1594
				// Reload modSettings to capture any adds/updates made along the way
1595
				$request = $smcFunc['db_query']('', '
1596
					SELECT variable, value
1597
					FROM {db_prefix}settings',
1598
					array(
1599
						'db_error_skip' => true,
1600
					)
1601
				);
1602
1603
				$modSettings = array();
1604
				while ($row = $smcFunc['db_fetch_assoc']($request))
1605
					$modSettings[$row['variable']] = $row['value'];
1606
1607
				$smcFunc['db_free_result']($request);
1608
1609
				// Some theme settings are in $modSettings
1610
				// Note we still might be doing yabbse (no smf ver)
1611
				if (isset($modSettings['smfVersion']))
1612
				{
1613
					$request = $smcFunc['db_query']('', '
1614
						SELECT variable, value
1615
						FROM {db_prefix}themes
1616
						WHERE id_theme = {int:id_theme}
1617
							AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
1618
						array(
1619
							'id_theme' => 1,
1620
							'theme_url' => 'theme_url',
1621
							'theme_dir' => 'theme_dir',
1622
							'images_url' => 'images_url',
1623
							'db_error_skip' => true,
1624
						)
1625
					);
1626
1627
					while ($row = $smcFunc['db_fetch_assoc']($request))
1628
						$modSettings[$row['variable']] = $row['value'];
1629
1630
					$smcFunc['db_free_result']($request);
1631
				}
1632
1633
				if (!isset($modSettings['theme_url']))
1634
				{
1635
					$modSettings['theme_dir'] = $boarddir . '/Themes/default';
1636
					$modSettings['theme_url'] = 'Themes/default';
1637
					$modSettings['images_url'] = 'Themes/default/images';
1638
				}
1639
1640
				// Now process the file...
1641
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1642
				if ($nextFile)
1643
				{
1644
					// Only update the version of this if complete.
1645
					$smcFunc['db_insert']('replace',
1646
						$db_prefix . 'settings',
1647
						array('variable' => 'string', 'value' => 'string'),
1648
						array('smfVersion', $file[2]),
1649
						array('variable')
1650
					);
1651
1652
					$modSettings['smfVersion'] = $file[2];
1653
				}
1654
1655
				// If this is XML we only do this stuff once.
1656
				if (isset($_GET['xml']))
1657
				{
1658
					// Flag to move on to the next.
1659
					$upcontext['completed_step'] = true;
1660
					// Did we complete the whole file?
1661
					if ($nextFile)
1662
						$upcontext['current_debug_item_num'] = -1;
1663
					return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1664
				}
1665
				elseif ($support_js)
1666
					break;
1667
			}
1668
			// Set the progress bar to be right as if we had - even if we hadn't...
1669
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1670
		}
1671
	}
1672
1673
	$_GET['substep'] = 0;
1674
	// So the template knows we're done.
1675
	if (!$support_js)
1676
	{
1677
		$upcontext['changes_complete'] = true;
1678
1679
		return true;
1680
	}
1681
	return false;
1682
}
1683
1684
// Different versions of the files use different sql_modes
1685
function setSqlMode($strict = true)
1686
{
1687
	global $db_type, $db_connection;
1688
1689
	if ($db_type != 'mysql')
1690
		return;
1691
1692
	if ($strict)
1693
		$mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_AUTO_CREATE_USER,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT';
1694
	else
1695
		$mode = '';
1696
1697
	mysqli_query($db_connection, 'SET SESSION sql_mode = \'' . $mode . '\'');
1698
1699
	return;
1700
}
1701
1702
// Delete the damn thing!
1703
function DeleteUpgrade()
1704
{
1705
	global $command_line, $language, $upcontext, $sourcedir;
1706
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1707
1708
	// Now it's nice to have some of the basic SMF source files.
1709
	if (!isset($_GET['ssi']) && !$command_line)
1710
		redirectLocation('&ssi=1');
1711
1712
	$upcontext['sub_template'] = 'upgrade_complete';
1713
	$upcontext['page_title'] = $txt['upgrade_complete'];
1714
1715
	$endl = $command_line ? "\n" : '<br>' . "\n";
1716
1717
	$changes = array(
1718
		'language' => (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language),
1719
		'db_error_send' => true,
1720
		'upgradeData' => null,
1721
	);
1722
1723
	// Are we in maintenance mode?
1724
	if (isset($upcontext['user']['main']))
1725
	{
1726
		if ($command_line)
1727
			echo ' * ';
1728
		$upcontext['removed_maintenance'] = true;
1729
		$changes['maintenance'] = $upcontext['user']['main'];
1730
	}
1731
	// Otherwise if somehow we are in 2 let's go to 1.
1732
	elseif (!empty($maintenance) && $maintenance == 2)
1733
		$changes['maintenance'] = 1;
1734
1735
	// Wipe this out...
1736
	$upcontext['user'] = array();
1737
1738
	require_once($sourcedir . '/Subs.php');
1739
	require_once($sourcedir . '/Subs-Admin.php');
1740
	updateSettingsFile($changes);
1741
1742
	// Clean any old cache files away.
1743
	upgrade_clean_cache();
0 ignored issues
show
Bug introduced by
The function upgrade_clean_cache was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1743
	/** @scrutinizer ignore-call */ 
1744
 upgrade_clean_cache();
Loading history...
1744
1745
	// Can we delete the file?
1746
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1747
1748
	// Now is the perfect time to fetch the SM files.
1749
	if ($command_line)
1750
		cli_scheduled_fetchSMfiles();
1751
	else
1752
	{
1753
		require_once($sourcedir . '/ScheduledTasks.php');
1754
		scheduled_fetchSMfiles(); // Now go get those files!
1755
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1756
		if (httpsOn())
1757
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1758
	}
1759
1760
	// Log what we've done.
1761
	if (empty($user_info['id']))
1762
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1763
1764
	// Log the action manually, so CLI still works.
1765
	$smcFunc['db_insert']('',
1766
		'{db_prefix}log_actions',
1767
		array(
1768
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1769
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1770
		),
1771
		array(
1772
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1773
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1774
		),
1775
		array('id_action')
1776
	);
1777
	$user_info['id'] = 0;
1778
1779
	if ($command_line)
1780
	{
1781
		echo $endl;
1782
		echo 'Upgrade Complete!', $endl;
1783
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1784
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
1785
	}
1786
1787
	// Make sure it says we're done.
1788
	$upcontext['overall_percent'] = 100;
1789
	if (isset($upcontext['step_progress']))
1790
		unset($upcontext['step_progress']);
1791
1792
	$_GET['substep'] = 0;
1793
	return false;
1794
}
1795
1796
// Just like the built in one, but setup for CLI to not use themes.
1797
function cli_scheduled_fetchSMfiles()
1798
{
1799
	global $sourcedir, $language, $modSettings, $smcFunc;
1800
1801
	if (empty($modSettings['time_format']))
1802
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1803
1804
	// What files do we want to get
1805
	$request = $smcFunc['db_query']('', '
1806
		SELECT id_file, filename, path, parameters
1807
		FROM {db_prefix}admin_info_files',
1808
		array(
1809
		)
1810
	);
1811
1812
	$js_files = array();
1813
	while ($row = $smcFunc['db_fetch_assoc']($request))
1814
	{
1815
		$js_files[$row['id_file']] = array(
1816
			'filename' => $row['filename'],
1817
			'path' => $row['path'],
1818
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1819
		);
1820
	}
1821
	$smcFunc['db_free_result']($request);
1822
1823
	// We're gonna need fetch_web_data() to pull this off.
1824
	require_once($sourcedir . '/Subs.php');
1825
1826
	foreach ($js_files as $ID_FILE => $file)
1827
	{
1828
		// Create the url
1829
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
1830
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1831
1832
		// Get the file
1833
		$file_data = fetch_web_data($url);
1834
1835
		// If we got an error - give up - the site might be down.
1836
		if ($file_data === false)
1837
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
0 ignored issues
show
Bug introduced by
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1837
			return /** @scrutinizer ignore-call */ throw_error(sprintf('Could not retrieve the file %1$s.', $url));
Loading history...
1838
1839
		// Save the file to the database.
1840
		$smcFunc['db_query']('substring', '
1841
			UPDATE {db_prefix}admin_info_files
1842
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1843
			WHERE id_file = {int:id_file}',
1844
			array(
1845
				'id_file' => $ID_FILE,
1846
				'file_data' => $file_data,
1847
			)
1848
		);
1849
	}
1850
	return true;
1851
}
1852
1853
function convertSettingsToTheme()
1854
{
1855
	global $db_prefix, $modSettings, $smcFunc;
1856
1857
	$values = array(
1858
		'show_latest_member' => @$GLOBALS['showlatestmember'],
1859
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
1860
		'show_modify' => @$GLOBALS['showmodify'],
1861
		'show_user_images' => @$GLOBALS['showuserpic'],
1862
		'show_blurb' => @$GLOBALS['showusertext'],
1863
		'show_gender' => @$GLOBALS['showgenderimage'],
1864
		'show_newsfader' => @$GLOBALS['shownewsfader'],
1865
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
1866
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
1867
		'linktree_link' => @$GLOBALS['curposlinks'],
1868
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
1869
		'show_mark_read' => @$GLOBALS['showmarkread'],
1870
		'show_board_desc' => @$GLOBALS['ShowBDescrip'],
1871
		'newsfader_time' => @$GLOBALS['fadertime'],
1872
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
1873
		'enable_news' => @$GLOBALS['enable_news'],
1874
		'linktree_inline' => @$modSettings['enableInlineLinks'],
1875
		'return_to_post' => @$modSettings['returnToPost'],
1876
	);
1877
1878
	$themeData = array();
1879
	foreach ($values as $variable => $value)
1880
	{
1881
		if (!isset($value) || $value === null)
1882
			$value = 0;
1883
1884
		$themeData[] = array(0, 1, $variable, $value);
1885
	}
1886
	if (!empty($themeData))
1887
	{
1888
		$smcFunc['db_insert']('ignore',
1889
			$db_prefix . 'themes',
1890
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
1891
			$themeData,
1892
			array('id_member', 'id_theme', 'variable')
1893
		);
1894
	}
1895
}
1896
1897
// This function only works with MySQL but that's fine as it is only used for v1.0.
1898
function convertSettingstoOptions()
1899
{
1900
	global $modSettings, $smcFunc;
1901
1902
	// Format: new_setting -> old_setting_name.
1903
	$values = array(
1904
		'calendar_start_day' => 'cal_startmonday',
1905
		'view_newest_first' => 'viewNewestFirst',
1906
		'view_newest_pm_first' => 'viewNewestFirst',
1907
	);
1908
1909
	foreach ($values as $variable => $value)
1910
	{
1911
		if (empty($modSettings[$value[0]]))
1912
			continue;
1913
1914
		$smcFunc['db_query']('', '
1915
			INSERT IGNORE INTO {db_prefix}themes
1916
				(id_member, id_theme, variable, value)
1917
			SELECT id_member, 1, {string:variable}, {string:value}
1918
			FROM {db_prefix}members',
1919
			array(
1920
				'variable' => $variable,
1921
				'value' => $modSettings[$value[0]],
1922
				'db_error_skip' => true,
1923
			)
1924
		);
1925
1926
		$smcFunc['db_query']('', '
1927
			INSERT IGNORE INTO {db_prefix}themes
1928
				(id_member, id_theme, variable, value)
1929
			VALUES (-1, 1, {string:variable}, {string:value})',
1930
			array(
1931
				'variable' => $variable,
1932
				'value' => $modSettings[$value[0]],
1933
				'db_error_skip' => true,
1934
			)
1935
		);
1936
	}
1937
}
1938
1939
function php_version_check()
1940
{
1941
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
1942
}
1943
1944
function db_version_check()
1945
{
1946
	global $db_type, $databases;
1947
1948
	$curver = eval($databases[$db_type]['version_check']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1949
	$curver = preg_replace('~\-.+?$~', '', $curver);
1950
1951
	return version_compare($databases[$db_type]['version'], $curver, '<=');
1952
}
1953
1954
function fixRelativePath($path)
1955
{
1956
	global $install_path;
1957
1958
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
1959
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
1960
}
1961
1962
function parse_sql($filename)
1963
{
1964
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
1965
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set, $smcFunc;
1966
1967
/*
1968
	Failure allowed on:
1969
		- INSERT INTO but not INSERT IGNORE INTO.
1970
		- UPDATE IGNORE but not UPDATE.
1971
		- ALTER TABLE and ALTER IGNORE TABLE.
1972
		- DROP TABLE.
1973
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
1974
1975
	If a comment...
1976
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
1977
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
1978
		- is only ---#, it is "done." and then a break - only shown in debug.
1979
		- begins with ---{ it is a code block terminating at ---}.
1980
1981
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
1982
1983
	Replaces the following variables:
1984
		- {$boarddir}
1985
		- {$boardurl}
1986
		- {$db_prefix}
1987
		- {$db_collation}
1988
*/
1989
1990
	// May want to use extended functionality.
1991
	db_extend();
1992
	db_extend('packages');
1993
1994
	// Our custom error handler - does nothing but does stop public errors from XML!
1995
	// Note that php error suppression - @ - used heavily in the upgrader, calls the error handler
1996
	// but error_reporting() will return 0 as it does so.
1997
	set_error_handler(
1998
		function($errno, $errstr, $errfile, $errline) use ($support_js)
1999
		{
2000
			if ($support_js)
2001
				return true;
2002
			elseif (error_reporting() != 0)
2003
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
2004
		}
2005
	);
2006
2007
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
2008
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
2009
	if ($db_type == 'mysql')
2010
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
2011
	else
2012
		$db_collation = '';
2013
2014
	$endl = $command_line ? "\n" : '<br>' . "\n";
2015
2016
	$lines = file($filename);
2017
2018
	$current_type = 'sql';
2019
	$current_data = '';
2020
	$substep = 0;
2021
	$last_step = '';
2022
2023
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
2024
	$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
2025
2026
	// Count the total number of steps within this file - for progress.
2027
	$file_steps = substr_count(implode('', $lines), '---#');
2028
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
2029
	$upcontext['debug_items'] = $file_steps;
2030
	$upcontext['current_item_num'] = 0;
2031
	$upcontext['current_item_name'] = '';
2032
	$upcontext['current_debug_item_num'] = 0;
2033
	$upcontext['current_debug_item_name'] = '';
2034
	// This array keeps a record of what we've done in case java is dead...
2035
	$upcontext['actioned_items'] = array();
2036
2037
	$done_something = false;
2038
2039
	foreach ($lines as $line_number => $line)
2040
	{
2041
		$do_current = $substep >= $_GET['substep'];
2042
2043
		// Get rid of any comments in the beginning of the line...
2044
		if (substr(trim($line), 0, 2) === '/*')
2045
			$line = preg_replace('~/\*.+?\*/~', '', $line);
2046
2047
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
2048
		if ($is_debug && !$support_js && $command_line)
2049
			flush();
2050
2051
		if (trim($line) === '')
2052
			continue;
2053
2054
		if (trim(substr($line, 0, 3)) === '---')
2055
		{
2056
			$type = substr($line, 3, 1);
2057
2058
			// An error??
2059
			if (trim($current_data) != '' && $type !== '}')
2060
			{
2061
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
2062
				if ($command_line)
2063
					echo $upcontext['error_message'];
2064
			}
2065
2066
			if ($type == ' ')
2067
			{
2068
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
2069
				{
2070
					echo ' Successful.', $endl;
2071
					flush();
2072
				}
2073
2074
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
2075
				$upcontext['current_item_num']++;
2076
				$upcontext['current_item_name'] = $last_step;
2077
2078
				if ($do_current)
2079
				{
2080
					$upcontext['actioned_items'][] = $last_step;
2081
					if ($command_line)
2082
						echo ' * ';
2083
2084
					// Starting a new main step in our DB changes, so it's time to reset this.
2085
					$upcontext['skip_db_substeps'] = false;
2086
				}
2087
			}
2088
			elseif ($type == '#')
2089
			{
2090
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
2091
2092
				$upcontext['current_debug_item_num']++;
2093
				if (trim($line) != '---#')
2094
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
2095
2096
				// Have we already done something?
2097
				if (isset($_GET['xml']) && $done_something)
2098
				{
2099
					restore_error_handler();
2100
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
2101
				}
2102
2103
				if ($do_current)
2104
				{
2105
					if (trim($line) == '---#' && $command_line)
2106
						echo ' done.', $endl;
2107
					elseif ($command_line)
2108
						echo ' +++ ', rtrim(substr($line, 4));
2109
					elseif (trim($line) != '---#')
2110
					{
2111
						if ($is_debug)
2112
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
2113
					}
2114
				}
2115
2116
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
2117
				{
2118
					if ($command_line)
2119
						echo ' * ';
2120
					else
2121
						$upcontext['actioned_items'][] = $last_step;
2122
				}
2123
2124
				// Small step - only if we're actually doing stuff.
2125
				if ($do_current)
2126
					nextSubstep(++$substep);
2127
				else
2128
					$substep++;
2129
			}
2130
			elseif ($type == '{')
2131
				$current_type = 'code';
2132
			elseif ($type == '}')
2133
			{
2134
				$current_type = 'sql';
2135
2136
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2137
				{
2138
					$current_data = '';
2139
2140
					// Avoid confusion when skipping something we normally would have done
2141
					if ($do_current)
2142
						$done_something = true;
2143
2144
					continue;
2145
				}
2146
2147
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2148
				if (eval('global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false)
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2149
				{
2150
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2151
					if ($command_line)
2152
						echo $upcontext['error_message'];
2153
				}
2154
2155
				// Done with code!
2156
				$current_data = '';
2157
				$done_something = true;
2158
			}
2159
2160
			continue;
2161
		}
2162
2163
		$current_data .= $line;
2164
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2165
		{
2166
			if ((!$support_js || isset($_GET['xml'])))
2167
			{
2168
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2169
				{
2170
					$current_data = '';
2171
2172
					if ($do_current)
2173
						$done_something = true;
2174
2175
					continue;
2176
				}
2177
2178
				// {$sboarddir} is deprecated, but blah blah backward compatibility blah...
2179
				$current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $smcFunc['db_escape_string']($boarddir), '{$sboarddir}' => $smcFunc['db_escape_string']($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation));
2180
2181
				upgrade_query($current_data);
2182
2183
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2184
				/*
2185
				$result = $smcFunc['db_query']('', $current_data, false, false);
2186
				// Went wrong?
2187
				if (!$result)
2188
				{
2189
					// Bit of a bodge - do we want the error?
2190
					if (!empty($upcontext['return_error']))
2191
					{
2192
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2193
						return false;
2194
					}
2195
				}*/
2196
				$done_something = true;
2197
			}
2198
			$current_data = '';
2199
		}
2200
		// If this is xml based and we're just getting the item name then that's grand.
2201
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2202
		{
2203
			restore_error_handler();
2204
			return false;
2205
		}
2206
2207
		// Clean up by cleaning any step info.
2208
		$step_progress = array();
2209
		$custom_warning = '';
2210
	}
2211
2212
	// Put back the error handler.
2213
	restore_error_handler();
2214
2215
	if ($command_line)
2216
	{
2217
		echo ' Successful.' . "\n";
2218
		flush();
2219
	}
2220
2221
	$_GET['substep'] = 0;
2222
	return true;
2223
}
2224
2225
function upgrade_query($string, $unbuffered = false)
2226
{
2227
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2228
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2229
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2230
2231
	// Get the query result - working around some SMF specific security - just this once!
2232
	$modSettings['disableQueryCheck'] = true;
2233
	$db_unbuffered = $unbuffered;
2234
	$ignore_insert_error = false;
2235
2236
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2237
	$db_unbuffered = false;
2238
2239
	// Failure?!
2240
	if ($result !== false)
2241
		return $result;
2242
2243
	$db_error_message = $smcFunc['db_error']($db_connection);
2244
	// If MySQL we do something more clever.
2245
	if ($db_type == 'mysql')
2246
	{
2247
		$mysqli_errno = mysqli_errno($db_connection);
2248
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR', 'INSERT IGNO'));
2249
2250
		// Error numbers:
2251
		//    1016: Can't open file '....MYI'
2252
		//    1050: Table already exists.
2253
		//    1054: Unknown column name.
2254
		//    1060: Duplicate column name.
2255
		//    1061: Duplicate key name.
2256
		//    1062: Duplicate entry for unique key.
2257
		//    1068: Multiple primary keys.
2258
		//    1072: Key column '%s' doesn't exist in table.
2259
		//    1091: Can't drop key, doesn't exist.
2260
		//    1146: Table doesn't exist.
2261
		//    2013: Lost connection to server during query.
2262
2263
		if ($mysqli_errno == 1016)
2264
		{
2265
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2266
			{
2267
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2268
				$result = mysqli_query($db_connection, $string);
2269
				if ($result !== false)
2270
					return $result;
2271
			}
2272
		}
2273
		elseif ($mysqli_errno == 2013)
2274
		{
2275
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
0 ignored issues
show
Bug introduced by
The call to mysqli_connect() has too few arguments starting with database. ( Ignorable by Annotation )

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

2275
			$db_connection = /** @scrutinizer ignore-call */ mysqli_connect($db_server, $db_user, $db_passwd);

This check compares calls to functions or methods with their respective definitions. If the call has less arguments than are defined, it raises an issue.

If a function is defined several times with a different number of parameters, the check may pick up the wrong definition and report false positives. One codebase where this has been known to happen is Wordpress. Please note the @ignore annotation hint above.

Loading history...
2276
			mysqli_select_db($db_connection, $db_name);
2277
			if ($db_connection)
2278
			{
2279
				$result = mysqli_query($db_connection, $string);
2280
				if ($result !== false)
2281
					return $result;
2282
			}
2283
		}
2284
		// Duplicate column name... should be okay ;).
2285
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2286
			return false;
2287
		// Duplicate insert... make sure it's the proper type of query ;).
2288
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2289
			return false;
2290
		// Creating an index on a non-existent column.
2291
		elseif ($mysqli_errno == 1072)
2292
			return false;
2293
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2294
			return false;
2295
		// Testing for legacy tables or columns? Needed for 1.0 & 1.1 scripts.
2296
		elseif (in_array($mysqli_errno, array(1054, 1146)) && in_array(substr(trim($string), 0, 7), array('SELECT ', 'SHOW CO')))
2297
			return false;
2298
	}
2299
	// If a table already exists don't go potty.
2300
	else
2301
	{
2302
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2303
		{
2304
			if (strpos($db_error_message, 'exist') !== false)
2305
				return true;
2306
		}
2307
		elseif (strpos(trim($string), 'INSERT ') !== false)
2308
		{
2309
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2310
				return true;
2311
		}
2312
	}
2313
2314
	// Get the query string so we pass everything.
2315
	$query_string = '';
2316
	foreach ($_GET as $k => $v)
2317
		$query_string .= ';' . $k . '=' . $v;
2318
	if (strlen($query_string) != 0)
2319
		$query_string = '?' . substr($query_string, 1);
2320
2321
	if ($command_line)
2322
	{
2323
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2324
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2325
	}
2326
2327
	// Bit of a bodge - do we want the error?
2328
	if (!empty($upcontext['return_error']))
2329
	{
2330
		$upcontext['error_message'] = $db_error_message;
2331
		$upcontext['error_string'] = $string;
2332
		return false;
2333
	}
2334
2335
	// Otherwise we have to display this somewhere appropriate if possible.
2336
	$upcontext['forced_error_message'] = '
2337
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2338
2339
			<div style="margin: 2ex;">
2340
				' . $txt['upgrade_thisquery'] . '
2341
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2342
2343
				' . $txt['upgrade_causerror'] . '
2344
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2345
			</div>
2346
2347
			<form action="' . $upgradeurl . $query_string . '" method="post">
2348
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2349
			</form>
2350
		</div>';
2351
2352
	upgradeExit();
2353
}
2354
2355
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2356
function protected_alter($change, $substep, $is_test = false)
2357
{
2358
	global $db_prefix, $smcFunc;
2359
2360
	db_extend('packages');
2361
2362
	// Firstly, check whether the current index/column exists.
2363
	$found = false;
2364
	if ($change['type'] === 'column')
2365
	{
2366
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2367
		foreach ($columns as $column)
2368
		{
2369
			// Found it?
2370
			if ($column['name'] === $change['name'])
2371
			{
2372
				$found |= true;
2373
				// Do some checks on the data if we have it set.
2374
				if (isset($change['col_type']))
2375
					$found &= $change['col_type'] === $column['type'];
2376
				if (isset($change['null_allowed']))
2377
					$found &= $column['null'] == $change['null_allowed'];
2378
				if (isset($change['default']))
2379
					$found &= $change['default'] === $column['default'];
2380
			}
2381
		}
2382
	}
2383
	elseif ($change['type'] === 'index')
2384
	{
2385
		$request = upgrade_query('
2386
			SHOW INDEX
2387
			FROM ' . $db_prefix . $change['table']);
2388
		if ($request !== false)
2389
		{
2390
			$cur_index = array();
2391
2392
			while ($row = $smcFunc['db_fetch_assoc']($request))
2393
				if ($row['Key_name'] === $change['name'])
2394
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2395
2396
			ksort($cur_index, SORT_NUMERIC);
2397
			$found = array_values($cur_index) === $change['target_columns'];
2398
2399
			$smcFunc['db_free_result']($request);
2400
		}
2401
	}
2402
2403
	// If we're trying to add and it's added, we're done.
2404
	if ($found && in_array($change['method'], array('add', 'change')))
0 ignored issues
show
Bug Best Practice introduced by
The expression $found of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2405
		return true;
2406
	// Otherwise if we're removing and it wasn't found we're also done.
2407
	elseif (!$found && in_array($change['method'], array('remove', 'change_remove')))
0 ignored issues
show
Bug Best Practice introduced by
The expression $found of type false|integer is loosely compared to false; this is ambiguous if the integer can be 0. You might want to explicitly use === false instead.

In PHP, under loose comparison (like ==, or !=, or switch conditions), values of different types might be equal.

For integer values, zero is a special case, in particular the following results might be unexpected:

0   == false // true
0   == null  // true
123 == false // false
123 == null  // false

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2408
		return true;
2409
	// Otherwise is it just a test?
2410
	elseif ($is_test)
2411
		return false;
2412
2413
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2414
	$running = false;
2415
	$found = false;
2416
	while (1 == 1)
2417
	{
2418
		$request = upgrade_query('
2419
			SHOW FULL PROCESSLIST');
2420
		while ($row = $smcFunc['db_fetch_assoc']($request))
2421
		{
2422
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2423
				$found = true;
2424
		}
2425
2426
		// Can't find it? Then we need to run it fools!
2427
		if (!$found && !$running)
2428
		{
2429
			$smcFunc['db_free_result']($request);
2430
2431
			$success = upgrade_query('
2432
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2433
				' . $change['text'], true) !== false;
2434
2435
			if (!$success)
2436
				return false;
2437
2438
			// Return
2439
			$running = true;
2440
		}
2441
		// What if we've not found it, but we'd ran it already? Must of completed.
2442
		elseif (!$found)
2443
		{
2444
			$smcFunc['db_free_result']($request);
2445
			return true;
2446
		}
2447
2448
		// Pause execution for a sec or three.
2449
		sleep(3);
2450
2451
		// Can never be too well protected.
2452
		nextSubstep($substep);
2453
	}
2454
2455
	// Protect it.
2456
	nextSubstep($substep);
2457
}
2458
2459
/**
2460
 * Alter a text column definition preserving its character set.
2461
 *
2462
 * @param array $change
2463
 * @param int $substep
2464
 */
2465
function textfield_alter($change, $substep)
2466
{
2467
	global $db_prefix, $smcFunc;
2468
2469
	$request = $smcFunc['db_query']('', '
2470
		SHOW FULL COLUMNS
2471
		FROM {db_prefix}' . $change['table'] . '
2472
		LIKE {string:column}',
2473
		array(
2474
			'column' => $change['column'],
2475
			'db_error_skip' => true,
2476
		)
2477
	);
2478
	if ($smcFunc['db_num_rows']($request) === 0)
2479
		die('Unable to find column ' . $change['column'] . ' inside table ' . $db_prefix . $change['table']);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2480
	$table_row = $smcFunc['db_fetch_assoc']($request);
2481
	$smcFunc['db_free_result']($request);
2482
2483
	// If something of the current column definition is different, fix it.
2484
	$column_fix = $table_row['Type'] !== $change['type'] || (strtolower($table_row['Null']) === 'yes') !== $change['null_allowed'] || ($table_row['Default'] === null) !== !isset($change['default']) || (isset($change['default']) && $change['default'] !== $table_row['Default']);
2485
2486
	// Columns that previously allowed null, need to be converted first.
2487
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2488
2489
	// Get the character set that goes with the collation of the column.
2490
	if ($column_fix && !empty($table_row['Collation']))
2491
	{
2492
		$request = $smcFunc['db_query']('', '
2493
			SHOW COLLATION
2494
			LIKE {string:collation}',
2495
			array(
2496
				'collation' => $table_row['Collation'],
2497
				'db_error_skip' => true,
2498
			)
2499
		);
2500
		// No results? Just forget it all together.
2501
		if ($smcFunc['db_num_rows']($request) === 0)
2502
			unset($table_row['Collation']);
2503
		else
2504
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2505
		$smcFunc['db_free_result']($request);
2506
	}
2507
2508
	if ($column_fix)
2509
	{
2510
		// Make sure there are no NULL's left.
2511
		if ($null_fix)
2512
			$smcFunc['db_query']('', '
2513
				UPDATE {db_prefix}' . $change['table'] . '
2514
				SET ' . $change['column'] . ' = {string:default}
2515
				WHERE ' . $change['column'] . ' IS NULL',
2516
				array(
2517
					'default' => isset($change['default']) ? $change['default'] : '',
2518
					'db_error_skip' => true,
2519
				)
2520
			);
2521
2522
		// Do the actual alteration.
2523
		$smcFunc['db_query']('', '
2524
			ALTER TABLE {db_prefix}' . $change['table'] . '
2525
			CHANGE COLUMN ' . $change['column'] . ' ' . $change['column'] . ' ' . $change['type'] . (isset($collation_info['Charset']) ? ' CHARACTER SET ' . $collation_info['Charset'] . ' COLLATE ' . $collation_info['Collation'] : '') . ($change['null_allowed'] ? '' : ' NOT NULL') . (isset($change['default']) ? ' default {string:default}' : ''),
2526
			array(
2527
				'default' => isset($change['default']) ? $change['default'] : '',
2528
				'db_error_skip' => true,
2529
			)
2530
		);
2531
	}
2532
	nextSubstep($substep);
2533
}
2534
2535
// Check if we need to alter this query.
2536
function checkChange(&$change)
2537
{
2538
	global $smcFunc, $db_type, $databases;
2539
	static $database_version, $where_field_support;
2540
2541
	// Attempt to find a database_version.
2542
	if (empty($database_version))
2543
	{
2544
		$database_version = $databases[$db_type]['version_check'];
2545
		$where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version, '<=');
2546
	}
2547
2548
	// Not a column we need to check on?
2549
	if (!in_array($change['name'], array('memberGroups', 'passwordSalt')))
2550
		return;
2551
2552
	// Break it up you (six|seven).
2553
	$temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text']));
2554
2555
	// Can we support a shortcut method?
2556
	if ($where_field_support)
2557
	{
2558
		// Get the details about this change.
2559
		$request = $smcFunc['db_query']('', '
2560
			SHOW FIELDS
2561
			FROM {db_prefix}{raw:table}
2562
			WHERE Field = {string:old_name} OR Field = {string:new_name}',
2563
			array(
2564
				'table' => $change['table'],
2565
				'old_name' => $temp[1],
2566
				'new_name' => $temp[2],
2567
			)
2568
		);
2569
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2570
		if ($smcFunc['db_num_rows'] != 1)
2571
			return;
2572
2573
		list (, $current_type) = $smcFunc['db_fetch_assoc']($request);
2574
		$smcFunc['db_free_result']($request);
2575
	}
2576
	else
2577
	{
2578
		// Do this the old fashion, sure method way.
2579
		$request = $smcFunc['db_query']('', '
2580
			SHOW FIELDS
2581
			FROM {db_prefix}{raw:table}',
2582
			array(
2583
				'table' => $change['table'],
2584
			)
2585
		);
2586
		// Mayday!
2587
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2588
		if ($smcFunc['db_num_rows'] == 0)
2589
			return;
2590
2591
		// Oh where, oh where has my little field gone. Oh where can it be...
2592
		while ($row = $smcFunc['db_query']($request))
2593
			if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2])
2594
			{
2595
				$current_type = $row['Type'];
2596
				break;
2597
			}
2598
	}
2599
2600
	// If this doesn't match, the column may of been altered for a reason.
2601
	if (trim($current_type) != trim($temp[3]))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $current_type does not seem to be defined for all execution paths leading up to this point.
Loading history...
2602
		$temp[3] = $current_type;
2603
2604
	// Piece this back together.
2605
	$change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp));
2606
}
2607
2608
// The next substep.
2609
function nextSubstep($substep)
2610
{
2611
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2612
	global $step_progress, $is_debug, $upcontext;
2613
2614
	if ($_GET['substep'] < $substep)
2615
		$_GET['substep'] = $substep;
2616
2617
	if ($command_line)
2618
	{
2619
		if (time() - $start_time > 1 && empty($is_debug))
2620
		{
2621
			echo '.';
2622
			$start_time = time();
2623
		}
2624
		return;
2625
	}
2626
2627
	@set_time_limit(300);
2628
	if (function_exists('apache_reset_timeout'))
2629
		@apache_reset_timeout();
2630
2631
	if (time() - $start_time <= $timeLimitThreshold)
2632
		return;
2633
2634
	// Do we have some custom step progress stuff?
2635
	if (!empty($step_progress))
2636
	{
2637
		$upcontext['substep_progress'] = 0;
2638
		$upcontext['substep_progress_name'] = $step_progress['name'];
2639
		if ($step_progress['current'] > $step_progress['total'])
2640
			$upcontext['substep_progress'] = 99.9;
2641
		else
2642
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2643
2644
		// Make it nicely rounded.
2645
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2646
	}
2647
2648
	// If this is XML we just exit right away!
2649
	if (isset($_GET['xml']))
2650
		return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
2651
2652
	// We're going to pause after this!
2653
	$upcontext['pause'] = true;
2654
2655
	$upcontext['query_string'] = '';
2656
	foreach ($_GET as $k => $v)
2657
	{
2658
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2659
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2660
	}
2661
2662
	// Custom warning?
2663
	if (!empty($custom_warning))
2664
		$upcontext['custom_warning'] = $custom_warning;
2665
2666
	upgradeExit();
2667
}
2668
2669
function cmdStep0()
2670
{
2671
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2672
	global $is_debug, $boardurl, $txt;
2673
	$start_time = time();
2674
2675
	while (ob_get_level() > 0)
2676
		ob_end_clean();
2677
	ob_implicit_flush(1);
2678
	@set_time_limit(600);
2679
2680
	if (!isset($_SERVER['argv']))
2681
		$_SERVER['argv'] = array();
2682
	$_GET['maint'] = 1;
2683
2684
	foreach ($_SERVER['argv'] as $i => $arg)
2685
	{
2686
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2687
			$upcontext['lang'] = $match[1];
2688
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2689
			continue;
2690
		elseif ($arg == '--no-maintenance')
2691
			$_GET['maint'] = 0;
2692
		elseif ($arg == '--debug')
2693
			$is_debug = true;
2694
		elseif ($arg == '--backup')
2695
			$_POST['backup'] = 1;
2696
		elseif ($arg == '--rebuild-settings')
2697
			$_POST['migrateSettings'] = 1;
2698
		elseif ($arg == '--allow-stats')
2699
			$_POST['stats'] = 1;
2700
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2701
			$_GET['conv'] = 1;
2702
		elseif ($i != 0)
2703
		{
2704
			echo 'SMF Command-line Upgrader
2705
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2706
2707
	--path=/path/to/SMF     Specify custom SMF root directory.
2708
	--language=LANG         Reset the forum\'s language to LANG.
2709
	--no-maintenance        Don\'t put the forum into maintenance mode.
2710
	--debug                 Output debugging information.
2711
	--backup                Create backups of tables with "backup_" prefix.
2712
	--allow-stats           Allow Simple Machines stat collection
2713
	--rebuild-settings      Rebuild the Settings.php file';
2714
			echo "\n";
2715
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

In general, usage of exit should be done with care and only when running in a scripting context like a CLI script.

Loading history...
2716
		}
2717
	}
2718
2719
	if (!php_version_check())
2720
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
0 ignored issues
show
Bug introduced by
The function print_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2720
		/** @scrutinizer ignore-call */ 
2721
  print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
Loading history...
2721
	if (!db_version_check())
2722
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2723
2724
	// Do some checks to make sure they have proper privileges
2725
	db_extend('packages');
2726
2727
	// CREATE
2728
	$create = $smcFunc['db_create_table']('{db_prefix}priv_check', array(array('name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true)), array(array('columns' => array('id_test'), 'primary' => true)), array(), 'overwrite');
2729
2730
	// ALTER
2731
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2732
2733
	// DROP
2734
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2735
2736
	// Sorry... we need CREATE, ALTER and DROP
2737
	if (!$create || !$alter || !$drop)
2738
		print_error("The " . $databases[$db_type]['name'] . " user you have set in Settings.php does not have proper privileges.\n\nPlease ask your host to give this user the ALTER, CREATE, and DROP privileges.", true);
2739
2740
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2741
		&& @file_exists($sourcedir . '/QueryString.php')
2742
		&& @file_exists($sourcedir . '/ManageBoards.php');
2743
	if (!$check && !isset($modSettings['smfVersion']))
2744
		print_error('Error: Some files are missing or out-of-date.', true);
2745
2746
	// Do a quick version spot check.
2747
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
0 ignored issues
show
Bug introduced by
It seems like @file($boarddir . '/index.php') can also be of type false; however, parameter $pieces of implode() 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

2747
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
2748
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2749
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2750
		print_error('Error: Some files have not yet been updated properly.');
2751
2752
	// Make sure Settings.php is writable.
2753
	quickFileWritable($boarddir . '/Settings.php');
0 ignored issues
show
Bug introduced by
The function quickFileWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2753
	/** @scrutinizer ignore-call */ 
2754
 quickFileWritable($boarddir . '/Settings.php');
Loading history...
2754
	if (!is_writable($boarddir . '/Settings.php'))
2755
		print_error('Error: Unable to obtain write access to "Settings.php".', true);
2756
2757
	// Make sure Settings_bak.php is writable.
2758
	quickFileWritable($boarddir . '/Settings_bak.php');
2759
	if (!is_writable($boarddir . '/Settings_bak.php'))
2760
		print_error('Error: Unable to obtain write access to "Settings_bak.php".');
2761
2762
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
2763
		print_error('Error: Unable to obtain write access to "agreement.txt".');
2764
	elseif (isset($modSettings['agreement']))
2765
	{
2766
		$fp = fopen($boarddir . '/agreement.txt', 'w');
2767
		fwrite($fp, $modSettings['agreement']);
2768
		fclose($fp);
2769
	}
2770
2771
	// Make sure Themes is writable.
2772
	quickFileWritable($modSettings['theme_dir']);
2773
2774
	if (!is_writable($modSettings['theme_dir']) && !isset($modSettings['smfVersion']))
2775
		print_error('Error: Unable to obtain write access to "Themes".');
2776
2777
	// Make sure cache directory exists and is writable!
2778
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
2779
	if (!file_exists($cachedir_temp))
2780
		@mkdir($cachedir_temp);
2781
2782
	// Make sure the cache temp dir is writable.
2783
	quickFileWritable($cachedir_temp);
2784
2785
	if (!is_writable($cachedir_temp))
2786
		print_error('Error: Unable to obtain write access to "cache".', true);
2787
2788
	// Make sure db_last_error.php is writable.
2789
	quickFileWritable($cachedir_temp . '/db_last_error.php');
2790
	if (!is_writable($cachedir_temp . '/db_last_error.php'))
2791
		print_error('Error: Unable to obtain write access to "db_last_error.php".');
2792
2793
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
2794
		print_error('Error: Unable to find language files!', true);
2795
	else
2796
	{
2797
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
2798
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
2799
2800
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
2801
			print_error('Error: Language files out of date.', true);
2802
		if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'))
2803
			print_error('Error: Install language is missing for selected language.', true);
2804
2805
		// Otherwise include it!
2806
		require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php');
2807
	}
2808
2809
	// Do we need to add this setting?
2810
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
2811
2812
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $boarddir . '/custom_avatar';
2813
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
2814
2815
	// This little fellow has to cooperate...
2816
	quickFileWritable($custom_av_dir);
2817
2818
	// Are we good now?
2819
	if (!is_writable($custom_av_dir))
2820
		print_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
2821
	elseif ($need_settings_update)
2822
	{
2823
		if (!function_exists('cache_put_data'))
2824
			require_once($sourcedir . '/Load.php');
2825
2826
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
2827
		updateSettings(array('custom_avatar_url' => $custom_av_url));
2828
	}
2829
2830
	// Make sure we skip the HTML for login.
2831
	$_POST['upcont'] = true;
2832
	$upcontext['current_step'] = 1;
2833
}
2834
2835
/**
2836
 * Handles converting your database to UTF-8
2837
 */
2838
function ConvertUtf8()
2839
{
2840
	global $upcontext, $db_character_set, $sourcedir, $smcFunc, $modSettings, $language;
2841
	global $db_prefix, $db_type, $command_line, $support_js, $txt;
2842
2843
	// Done it already?
2844
	if (!empty($_POST['utf8_done']))
2845
	{
2846
		if ($command_line)
2847
			return DeleteUpgrade();
2848
		else
2849
			return true;
2850
	}
2851
	// First make sure they aren't already on UTF-8 before we go anywhere...
2852
	if ($db_type == 'postgresql' || ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8'))
2853
	{
2854
		$smcFunc['db_insert']('replace',
2855
			'{db_prefix}settings',
2856
			array('variable' => 'string', 'value' => 'string'),
2857
			array(array('global_character_set', 'UTF-8')),
2858
			array('variable')
2859
		);
2860
2861
		if ($command_line)
2862
			return DeleteUpgrade();
2863
		else
2864
			return true;
2865
	}
2866
	else
2867
	{
2868
		$upcontext['page_title'] = $txt['converting_utf8'];
2869
		$upcontext['sub_template'] = isset($_GET['xml']) ? 'convert_xml' : 'convert_utf8';
2870
2871
		// The character sets used in SMF's language files with their db equivalent.
2872
		$charsets = array(
2873
			// Armenian
2874
			'armscii8' => 'armscii8',
2875
			// Chinese-traditional.
2876
			'big5' => 'big5',
2877
			// Chinese-simplified.
2878
			'gbk' => 'gbk',
2879
			// West European.
2880
			'ISO-8859-1' => 'latin1',
2881
			// Romanian.
2882
			'ISO-8859-2' => 'latin2',
2883
			// Turkish.
2884
			'ISO-8859-9' => 'latin5',
2885
			// Latvian
2886
			'ISO-8859-13' => 'latin7',
2887
			// West European with Euro sign.
2888
			'ISO-8859-15' => 'latin9',
2889
			// Thai.
2890
			'tis-620' => 'tis620',
2891
			// Persian, Chinese, etc.
2892
			'UTF-8' => 'utf8',
2893
			// Russian.
2894
			'windows-1251' => 'cp1251',
2895
			// Greek.
2896
			'windows-1253' => 'utf8',
2897
			// Hebrew.
2898
			'windows-1255' => 'utf8',
2899
			// Arabic.
2900
			'windows-1256' => 'cp1256',
2901
		);
2902
2903
		// Get a list of character sets supported by your MySQL server.
2904
		$request = $smcFunc['db_query']('', '
2905
			SHOW CHARACTER SET',
2906
			array(
2907
			)
2908
		);
2909
		$db_charsets = array();
2910
		while ($row = $smcFunc['db_fetch_assoc']($request))
2911
			$db_charsets[] = $row['Charset'];
2912
2913
		$smcFunc['db_free_result']($request);
2914
2915
		// Character sets supported by both MySQL and SMF's language files.
2916
		$charsets = array_intersect($charsets, $db_charsets);
2917
2918
		// Use the messages.body column as indicator for the database charset.
2919
		$request = $smcFunc['db_query']('', '
2920
			SHOW FULL COLUMNS
2921
			FROM {db_prefix}messages
2922
			LIKE {string:body_like}',
2923
			array(
2924
				'body_like' => 'body',
2925
			)
2926
		);
2927
		$column_info = $smcFunc['db_fetch_assoc']($request);
2928
		$smcFunc['db_free_result']($request);
2929
2930
		// A collation looks like latin1_swedish. We only need the character set.
2931
		list($upcontext['database_charset']) = explode('_', $column_info['Collation']);
2932
		$upcontext['database_charset'] = in_array($upcontext['database_charset'], $charsets) ? array_search($upcontext['database_charset'], $charsets) : $upcontext['database_charset'];
2933
2934
		// Detect whether a fulltext index is set.
2935
		$request = $smcFunc['db_query']('', '
2936
			SHOW INDEX
2937
			FROM {db_prefix}messages',
2938
			array(
2939
			)
2940
		);
2941
2942
		$upcontext['dropping_index'] = false;
2943
2944
		// If there's a fulltext index, we need to drop it first...
2945
		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
2946
		{
2947
			while ($row = $smcFunc['db_fetch_assoc']($request))
2948
				if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
2949
					$upcontext['fulltext_index'][] = $row['Key_name'];
2950
			$smcFunc['db_free_result']($request);
2951
2952
			if (isset($upcontext['fulltext_index']))
2953
				$upcontext['fulltext_index'] = array_unique($upcontext['fulltext_index']);
2954
		}
2955
2956
		// Drop it and make a note...
2957
		if (!empty($upcontext['fulltext_index']))
2958
		{
2959
			$upcontext['dropping_index'] = true;
2960
2961
			$smcFunc['db_query']('', '
2962
				ALTER TABLE {db_prefix}messages
2963
				DROP INDEX ' . implode(',
2964
				DROP INDEX ', $upcontext['fulltext_index']),
2965
				array(
2966
					'db_error_skip' => true,
2967
				)
2968
			);
2969
2970
			// Update the settings table
2971
			$smcFunc['db_insert']('replace',
2972
				'{db_prefix}settings',
2973
				array('variable' => 'string', 'value' => 'string'),
2974
				array('db_search_index', ''),
2975
				array('variable')
2976
			);
2977
		}
2978
2979
		// Figure out what charset we should be converting from...
2980
		$lang_charsets = array(
2981
			'arabic' => 'windows-1256',
2982
			'armenian_east' => 'armscii-8',
2983
			'armenian_west' => 'armscii-8',
2984
			'azerbaijani_latin' => 'ISO-8859-9',
2985
			'bangla' => 'UTF-8',
2986
			'belarusian' => 'ISO-8859-5',
2987
			'bulgarian' => 'windows-1251',
2988
			'cambodian' => 'UTF-8',
2989
			'chinese_simplified' => 'gbk',
2990
			'chinese_traditional' => 'big5',
2991
			'croation' => 'ISO-8859-2',
2992
			'czech' => 'ISO-8859-2',
2993
			'czech_informal' => 'ISO-8859-2',
2994
			'english_pirate' => 'UTF-8',
2995
			'esperanto' => 'ISO-8859-3',
2996
			'estonian' => 'ISO-8859-15',
2997
			'filipino_tagalog' => 'UTF-8',
2998
			'filipino_vasayan' => 'UTF-8',
2999
			'georgian' => 'UTF-8',
3000
			'greek' => 'ISO-8859-3',
3001
			'hebrew' => 'windows-1255',
3002
			'hungarian' => 'ISO-8859-2',
3003
			'irish' => 'UTF-8',
3004
			'japanese' => 'UTF-8',
3005
			'khmer' => 'UTF-8',
3006
			'korean' => 'UTF-8',
3007
			'kurdish_kurmanji' => 'ISO-8859-9',
3008
			'kurdish_sorani' => 'windows-1256',
3009
			'lao' => 'tis-620',
3010
			'latvian' => 'ISO-8859-13',
3011
			'lithuanian' => 'ISO-8859-4',
3012
			'macedonian' => 'UTF-8',
3013
			'malayalam' => 'UTF-8',
3014
			'mongolian' => 'UTF-8',
3015
			'nepali' => 'UTF-8',
3016
			'persian' => 'UTF-8',
3017
			'polish' => 'ISO-8859-2',
3018
			'romanian' => 'ISO-8859-2',
3019
			'russian' => 'windows-1252',
3020
			'sakha' => 'UTF-8',
3021
			'serbian_cyrillic' => 'ISO-8859-5',
3022
			'serbian_latin' => 'ISO-8859-2',
3023
			'sinhala' => 'UTF-8',
3024
			'slovak' => 'ISO-8859-2',
3025
			'slovenian' => 'ISO-8859-2',
3026
			'telugu' => 'UTF-8',
3027
			'thai' => 'tis-620',
3028
			'turkish' => 'ISO-8859-9',
3029
			'turkmen' => 'ISO-8859-9',
3030
			'ukranian' => 'windows-1251',
3031
			'urdu' => 'UTF-8',
3032
			'uzbek_cyrillic' => 'ISO-8859-5',
3033
			'uzbek_latin' => 'ISO-8859-5',
3034
			'vietnamese' => 'UTF-8',
3035
			'yoruba' => 'UTF-8'
3036
		);
3037
3038
		// Default to ISO-8859-1 unless we detected another supported charset
3039
		$upcontext['charset_detected'] = (isset($lang_charsets[$language]) && isset($charsets[strtr(strtolower($upcontext['charset_detected']), array('utf' => 'UTF', 'iso' => 'ISO'))])) ? $lang_charsets[$language] : 'ISO-8859-1';
3040
3041
		$upcontext['charset_list'] = array_keys($charsets);
3042
3043
		// Translation table for the character sets not native for MySQL.
3044
		$translation_tables = array(
3045
			'windows-1255' => array(
3046
				'0x81' => '\'\'',		'0x8A' => '\'\'',		'0x8C' => '\'\'',
3047
				'0x8D' => '\'\'',		'0x8E' => '\'\'',		'0x8F' => '\'\'',
3048
				'0x90' => '\'\'',		'0x9A' => '\'\'',		'0x9C' => '\'\'',
3049
				'0x9D' => '\'\'',		'0x9E' => '\'\'',		'0x9F' => '\'\'',
3050
				'0xCA' => '\'\'',		'0xD9' => '\'\'',		'0xDA' => '\'\'',
3051
				'0xDB' => '\'\'',		'0xDC' => '\'\'',		'0xDD' => '\'\'',
3052
				'0xDE' => '\'\'',		'0xDF' => '\'\'',		'0xFB' => '0xD792',
3053
				'0xFC' => '0xE282AC',		'0xFF' => '0xD6B2',		'0xC2' => '0xFF',
3054
				'0x80' => '0xFC',		'0xE2' => '0xFB',		'0xA0' => '0xC2A0',
3055
				'0xA1' => '0xC2A1',		'0xA2' => '0xC2A2',		'0xA3' => '0xC2A3',
3056
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
3057
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
3058
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
3059
				'0xAF' => '0xC2AF',		'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',
3060
				'0xB2' => '0xC2B2',		'0xB3' => '0xC2B3',		'0xB4' => '0xC2B4',
3061
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
3062
				'0xB8' => '0xC2B8',		'0xB9' => '0xC2B9',		'0xBB' => '0xC2BB',
3063
				'0xBC' => '0xC2BC',		'0xBD' => '0xC2BD',		'0xBE' => '0xC2BE',
3064
				'0xBF' => '0xC2BF',		'0xD7' => '0xD7B3',		'0xD1' => '0xD781',
3065
				'0xD4' => '0xD7B0',		'0xD5' => '0xD7B1',		'0xD6' => '0xD7B2',
3066
				'0xE0' => '0xD790',		'0xEA' => '0xD79A',		'0xEC' => '0xD79C',
3067
				'0xED' => '0xD79D',		'0xEE' => '0xD79E',		'0xEF' => '0xD79F',
3068
				'0xF0' => '0xD7A0',		'0xF1' => '0xD7A1',		'0xF2' => '0xD7A2',
3069
				'0xF3' => '0xD7A3',		'0xF5' => '0xD7A5',		'0xF6' => '0xD7A6',
3070
				'0xF7' => '0xD7A7',		'0xF8' => '0xD7A8',		'0xF9' => '0xD7A9',
3071
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
3072
				'0x86' => '0xE280A0',	'0x87' => '0xE280A1',	'0x89' => '0xE280B0',
3073
				'0x8B' => '0xE280B9',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
3074
				'0x95' => '0xE280A2',	'0x97' => '0xE28094',	'0x99' => '0xE284A2',
3075
				'0xC0' => '0xD6B0',		'0xC1' => '0xD6B1',		'0xC3' => '0xD6B3',
3076
				'0xC4' => '0xD6B4',		'0xC5' => '0xD6B5',		'0xC6' => '0xD6B6',
3077
				'0xC7' => '0xD6B7',		'0xC8' => '0xD6B8',		'0xC9' => '0xD6B9',
3078
				'0xCB' => '0xD6BB',		'0xCC' => '0xD6BC',		'0xCD' => '0xD6BD',
3079
				'0xCE' => '0xD6BE',		'0xCF' => '0xD6BF',		'0xD0' => '0xD780',
3080
				'0xD2' => '0xD782',		'0xE3' => '0xD793',		'0xE4' => '0xD794',
3081
				'0xE5' => '0xD795',		'0xE7' => '0xD797',		'0xE9' => '0xD799',
3082
				'0xFD' => '0xE2808E',	'0xFE' => '0xE2808F',	'0x92' => '0xE28099',
3083
				'0x83' => '0xC692',		'0xD3' => '0xD783',		'0x88' => '0xCB86',
3084
				'0x98' => '0xCB9C',		'0x91' => '0xE28098',	'0x96' => '0xE28093',
3085
				'0xBA' => '0xC3B7',		'0x9B' => '0xE280BA',	'0xAA' => '0xC397',
3086
				'0xA4' => '0xE282AA',	'0xE1' => '0xD791',		'0xE6' => '0xD796',
3087
				'0xE8' => '0xD798',		'0xEB' => '0xD79B',		'0xF4' => '0xD7A4',
3088
				'0xFA' => '0xD7AA',
3089
			),
3090
			'windows-1253' => array(
3091
				'0x81' => '\'\'',			'0x88' => '\'\'',			'0x8A' => '\'\'',
3092
				'0x8C' => '\'\'',			'0x8D' => '\'\'',			'0x8E' => '\'\'',
3093
				'0x8F' => '\'\'',			'0x90' => '\'\'',			'0x98' => '\'\'',
3094
				'0x9A' => '\'\'',			'0x9C' => '\'\'',			'0x9D' => '\'\'',
3095
				'0x9E' => '\'\'',			'0x9F' => '\'\'',			'0xAA' => '\'\'',
3096
				'0xD2' => '0xE282AC',			'0xFF' => '0xCE92',			'0xCE' => '0xCE9E',
3097
				'0xB8' => '0xCE88',		'0xBA' => '0xCE8A',		'0xBC' => '0xCE8C',
3098
				'0xBE' => '0xCE8E',		'0xBF' => '0xCE8F',		'0xC0' => '0xCE90',
3099
				'0xC8' => '0xCE98',		'0xCA' => '0xCE9A',		'0xCC' => '0xCE9C',
3100
				'0xCD' => '0xCE9D',		'0xCF' => '0xCE9F',		'0xDA' => '0xCEAA',
3101
				'0xE8' => '0xCEB8',		'0xEA' => '0xCEBA',		'0xEC' => '0xCEBC',
3102
				'0xEE' => '0xCEBE',		'0xEF' => '0xCEBF',		'0xC2' => '0xFF',
3103
				'0xBD' => '0xC2BD',		'0xED' => '0xCEBD',		'0xB2' => '0xC2B2',
3104
				'0xA0' => '0xC2A0',		'0xA3' => '0xC2A3',		'0xA4' => '0xC2A4',
3105
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
3106
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
3107
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
3108
				'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',		'0xB3' => '0xC2B3',
3109
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
3110
				'0xBB' => '0xC2BB',		'0xE2' => '0xCEB2',		'0x80' => '0xD2',
3111
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
3112
				'0x86' => '0xE280A0',	'0xA1' => '0xCE85',		'0xA2' => '0xCE86',
3113
				'0x87' => '0xE280A1',	'0x89' => '0xE280B0',	'0xB9' => '0xCE89',
3114
				'0x8B' => '0xE280B9',	'0x91' => '0xE28098',	'0x99' => '0xE284A2',
3115
				'0x92' => '0xE28099',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
3116
				'0x95' => '0xE280A2',	'0x96' => '0xE28093',	'0x97' => '0xE28094',
3117
				'0x9B' => '0xE280BA',	'0xAF' => '0xE28095',	'0xB4' => '0xCE84',
3118
				'0xC1' => '0xCE91',		'0xC3' => '0xCE93',		'0xC4' => '0xCE94',
3119
				'0xC5' => '0xCE95',		'0xC6' => '0xCE96',		'0x83' => '0xC692',
3120
				'0xC7' => '0xCE97',		'0xC9' => '0xCE99',		'0xCB' => '0xCE9B',
3121
				'0xD0' => '0xCEA0',		'0xD1' => '0xCEA1',		'0xD3' => '0xCEA3',
3122
				'0xD4' => '0xCEA4',		'0xD5' => '0xCEA5',		'0xD6' => '0xCEA6',
3123
				'0xD7' => '0xCEA7',		'0xD8' => '0xCEA8',		'0xD9' => '0xCEA9',
3124
				'0xDB' => '0xCEAB',		'0xDC' => '0xCEAC',		'0xDD' => '0xCEAD',
3125
				'0xDE' => '0xCEAE',		'0xDF' => '0xCEAF',		'0xE0' => '0xCEB0',
3126
				'0xE1' => '0xCEB1',		'0xE3' => '0xCEB3',		'0xE4' => '0xCEB4',
3127
				'0xE5' => '0xCEB5',		'0xE6' => '0xCEB6',		'0xE7' => '0xCEB7',
3128
				'0xE9' => '0xCEB9',		'0xEB' => '0xCEBB',		'0xF0' => '0xCF80',
3129
				'0xF1' => '0xCF81',		'0xF2' => '0xCF82',		'0xF3' => '0xCF83',
3130
				'0xF4' => '0xCF84',		'0xF5' => '0xCF85',		'0xF6' => '0xCF86',
3131
				'0xF7' => '0xCF87',		'0xF8' => '0xCF88',		'0xF9' => '0xCF89',
3132
				'0xFA' => '0xCF8A',		'0xFB' => '0xCF8B',		'0xFC' => '0xCF8C',
3133
				'0xFD' => '0xCF8D',		'0xFE' => '0xCF8E',
3134
			),
3135
		);
3136
3137
		// Make some preparations.
3138
		if (isset($translation_tables[$upcontext['charset_detected']]))
3139
		{
3140
			$replace = '%field%';
3141
3142
			// Build a huge REPLACE statement...
3143
			foreach ($translation_tables[$upcontext['charset_detected']] as $from => $to)
3144
				$replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')';
3145
		}
3146
3147
		// Get a list of table names ahead of time... This makes it easier to set our substep and such
3148
		db_extend();
3149
		$queryTables = $smcFunc['db_list_tables'](false, $db_prefix . '%');
3150
3151
		$upcontext['table_count'] = count($queryTables);
3152
3153
		// What ones have we already done?
3154
		foreach ($queryTables as $id => $table)
3155
			if ($id < $_GET['substep'])
3156
				$upcontext['previous_tables'][] = $table;
3157
3158
		$upcontext['cur_table_num'] = $_GET['substep'];
3159
		$upcontext['cur_table_name'] = str_replace($db_prefix, '', $queryTables[$_GET['substep']]);
3160
		$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3161
3162
		// Make sure we're ready & have painted the template before proceeding
3163
		if ($support_js && !isset($_GET['xml']))
3164
		{
3165
			$_GET['substep'] = 0;
3166
			return false;
3167
		}
3168
3169
		// We want to start at the first table.
3170
		for ($substep = $_GET['substep'], $n = count($queryTables); $substep < $n; $substep++)
3171
		{
3172
			$table = $queryTables[$substep];
3173
3174
			$getTableStatus = $smcFunc['db_query']('', '
3175
				SHOW TABLE STATUS
3176
				LIKE {string:table_name}',
3177
				array(
3178
					'table_name' => str_replace('_', '\_', $table)
3179
				)
3180
			);
3181
3182
			// Only one row so we can just fetch_assoc and free the result...
3183
			$table_info = $smcFunc['db_fetch_assoc']($getTableStatus);
3184
			$smcFunc['db_free_result']($getTableStatus);
3185
3186
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($queryTables[$substep + 1]) ? $queryTables[$substep + 1] : $queryTables[$substep]));
3187
			$upcontext['cur_table_num'] = $substep + 1;
3188
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3189
3190
			// Do we need to pause?
3191
			nextSubstep($substep);
3192
3193
			// Just to make sure it doesn't time out.
3194
			if (function_exists('apache_reset_timeout'))
3195
				@apache_reset_timeout();
3196
3197
			$table_charsets = array();
3198
3199
			// Loop through each column.
3200
			$queryColumns = $smcFunc['db_query']('', '
3201
				SHOW FULL COLUMNS
3202
				FROM ' . $table_info['Name'],
3203
				array(
3204
				)
3205
			);
3206
			while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns))
3207
			{
3208
				// Only text'ish columns have a character set and need converting.
3209
				if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
3210
				{
3211
					$collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation'];
3212
					if (!empty($collation) && $collation !== 'NULL')
3213
					{
3214
						list($charset) = explode('_', $collation);
3215
3216
						// Build structure of columns to operate on organized by charset; only operate on columns not yet utf8
3217
						if ($charset != 'utf8')
3218
						{
3219
							if (!isset($table_charsets[$charset]))
3220
								$table_charsets[$charset] = array();
3221
3222
							$table_charsets[$charset][] = $column_info;
3223
						}
3224
					}
3225
				}
3226
			}
3227
			$smcFunc['db_free_result']($queryColumns);
3228
3229
			// Only change the non-utf8 columns identified above
3230
			if (count($table_charsets) > 0)
3231
			{
3232
				$updates_blob = '';
3233
				$updates_text = '';
3234
				foreach ($table_charsets as $charset => $columns)
3235
				{
3236
					if ($charset !== $charsets[$upcontext['charset_detected']])
3237
					{
3238
						foreach ($columns as $column)
3239
						{
3240
							$updates_blob .= '
3241
								CHANGE COLUMN `' . $column['Field'] . '` `' . $column['Field'] . '` ' . strtr($column['Type'], array('text' => 'blob', 'char' => 'binary')) . ($column['Null'] === 'YES' ? ' NULL' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
3242
							$updates_text .= '
3243
								CHANGE COLUMN `' . $column['Field'] . '` `' . $column['Field'] . '` ' . $column['Type'] . ' CHARACTER SET ' . $charsets[$upcontext['charset_detected']] . ($column['Null'] === 'YES' ? '' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
3244
						}
3245
					}
3246
				}
3247
3248
				// Change the columns to binary form.
3249
				$smcFunc['db_query']('', '
3250
					ALTER TABLE {raw:table_name}{raw:updates_blob}',
3251
					array(
3252
						'table_name' => $table_info['Name'],
3253
						'updates_blob' => substr($updates_blob, 0, -1),
3254
					)
3255
				);
3256
3257
				// Convert the character set if MySQL has no native support for it.
3258
				if (isset($translation_tables[$upcontext['charset_detected']]))
3259
				{
3260
					$update = '';
3261
					foreach ($table_charsets as $charset => $columns)
3262
						foreach ($columns as $column)
3263
							$update .= '
3264
								' . $column['Field'] . ' = ' . strtr($replace, array('%field%' => $column['Field'])) . ',';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $replace does not seem to be defined for all execution paths leading up to this point.
Loading history...
3265
3266
					$smcFunc['db_query']('', '
3267
						UPDATE {raw:table_name}
3268
						SET {raw:updates}',
3269
						array(
3270
							'table_name' => $table_info['Name'],
3271
							'updates' => substr($update, 0, -1),
3272
						)
3273
					);
3274
				}
3275
3276
				// Change the columns back, but with the proper character set.
3277
				$smcFunc['db_query']('', '
3278
					ALTER TABLE {raw:table_name}{raw:updates_text}',
3279
					array(
3280
						'table_name' => $table_info['Name'],
3281
						'updates_text' => substr($updates_text, 0, -1),
3282
					)
3283
				);
3284
			}
3285
3286
			// Now do the actual conversion (if still needed).
3287
			if ($charsets[$upcontext['charset_detected']] !== 'utf8')
3288
			{
3289
				if ($command_line)
3290
					echo 'Converting table ' . $table_info['Name'] . ' to UTF-8...';
3291
3292
				$smcFunc['db_query']('', '
3293
					ALTER TABLE {raw:table_name}
3294
					CONVERT TO CHARACTER SET utf8',
3295
					array(
3296
						'table_name' => $table_info['Name'],
3297
					)
3298
				);
3299
3300
				if ($command_line)
3301
					echo " done.\n";
3302
			}
3303
			// If this is XML to keep it nice for the user do one table at a time anyway!
3304
			if (isset($_GET['xml']) && $upcontext['cur_table_num'] < $upcontext['table_count'])
3305
				return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
3306
		}
3307
3308
		$prev_charset = empty($translation_tables[$upcontext['charset_detected']]) ? $charsets[$upcontext['charset_detected']] : $translation_tables[$upcontext['charset_detected']];
3309
3310
		$smcFunc['db_insert']('replace',
3311
			'{db_prefix}settings',
3312
			array('variable' => 'string', 'value' => 'string'),
3313
			array(array('global_character_set', 'UTF-8'), array('previousCharacterSet', $prev_charset)),
3314
			array('variable')
3315
		);
3316
3317
		// Store it in Settings.php too because it's needed before db connection.
3318
		// Hopefully this works...
3319
		require_once($sourcedir . '/Subs.php');
3320
		require_once($sourcedir . '/Subs-Admin.php');
3321
		updateSettingsFile(array('db_character_set' => 'utf8'));
3322
3323
		// The conversion might have messed up some serialized strings. Fix them!
3324
		$request = $smcFunc['db_query']('', '
3325
			SELECT id_action, extra
3326
			FROM {db_prefix}log_actions
3327
			WHERE action IN ({string:remove}, {string:delete})',
3328
			array(
3329
				'remove' => 'remove',
3330
				'delete' => 'delete',
3331
			)
3332
		);
3333
		while ($row = $smcFunc['db_fetch_assoc']($request))
3334
		{
3335
			if (@safe_unserialize($row['extra']) === false && preg_match('~^(a:3:{s:5:"topic";i:\d+;s:7:"subject";s:)(\d+):"(.+)"(;s:6:"member";s:5:"\d+";})$~', $row['extra'], $matches) === 1)
3336
				$smcFunc['db_query']('', '
3337
					UPDATE {db_prefix}log_actions
3338
					SET extra = {string:extra}
3339
					WHERE id_action = {int:current_action}',
3340
					array(
3341
						'current_action' => $row['id_action'],
3342
						'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4],
3343
					)
3344
				);
3345
		}
3346
		$smcFunc['db_free_result']($request);
3347
3348
		if ($upcontext['dropping_index'] && $command_line)
3349
		{
3350
			echo "\n" . '', $txt['upgrade_fulltext_error'], '';
3351
			flush();
3352
		}
3353
	}
3354
3355
	// Make sure we move on!
3356
	if ($command_line)
3357
		return DeleteUpgrade();
3358
3359
	$_GET['substep'] = 0;
3360
	return false;
3361
}
3362
3363
/**
3364
 * Wrapper for unserialize that attempts to repair corrupted serialized data strings
3365
 *
3366
 * @param string $string Serialized data that may or may not have been corrupted
3367
 * @return string|bool The unserialized data, or false if the repair failed
3368
 */
3369
function upgrade_unserialize($string)
3370
{
3371
	if (!is_string($string))
0 ignored issues
show
introduced by
The condition is_string($string) is always true.
Loading history...
3372
	{
3373
		$data = false;
3374
	}
3375
	// Might be JSON already.
3376
	elseif (strpos($string, '{') === 0)
3377
	{
3378
		$data = @json_decode($string, true);
3379
3380
		if (is_null($data))
3381
			$data = false;
3382
	}
3383
	elseif (in_array(substr($string, 0, 2), array('b:', 'i:', 'd:', 's:', 'a:', 'N;')))
3384
	{
3385
		$data = @safe_unserialize($string);
3386
3387
		// The serialized data is broken.
3388
		if ($data === false)
3389
		{
3390
			// This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8)
3391
			$new_string = preg_replace_callback(
0 ignored issues
show
Unused Code introduced by
The assignment to $new_string is dead and can be removed.
Loading history...
3392
				'~\bs:(\d+):"(.*?)";(?=$|[bidsaO]:|[{}}]|N;)~s',
3393
				function ($matches)
3394
				{
3395
					return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';
3396
				},
3397
				$string
3398
			);
3399
3400
			// @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc.
3401
3402
			// Did it work?
3403
			$data = @safe_unserialize($string);
3404
		}
3405
	}
3406
	// Just a plain string, then.
3407
	else
3408
		$data = false;
3409
3410
	return $data;
3411
}
3412
3413
function serialize_to_json()
3414
{
3415
	global $command_line, $smcFunc, $modSettings, $sourcedir, $upcontext, $support_js, $txt;
3416
3417
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json';
3418
	// First thing's first - did we already do this?
3419
	if (!empty($modSettings['json_done']))
3420
	{
3421
		if ($command_line)
3422
			return ConvertUtf8();
3423
		else
3424
			return true;
3425
	}
3426
3427
	// Needed when writing settings
3428
	if (!function_exists('cache_put_data'))
3429
		require_once($sourcedir . '/Load.php');
3430
3431
	// Done it already - js wise?
3432
	if (!empty($_POST['json_done']))
3433
	{
3434
		updateSettings(array('json_done' => true));
3435
		return true;
3436
	}
3437
3438
	// List of tables affected by this function
3439
	// name => array('key', col1[,col2|true[,col3]])
3440
	// If 3rd item in array is true, it indicates that col1 could be empty...
3441
	$tables = array(
3442
		'background_tasks' => array('id_task', 'task_data'),
3443
		'log_actions' => array('id_action', 'extra'),
3444
		'log_online' => array('session', 'url'),
3445
		'log_packages' => array('id_install', 'db_changes', 'failed_steps', 'credits'),
3446
		'log_spider_hits' => array('id_hit', 'url'),
3447
		'log_subscribed' => array('id_sublog', 'pending_details'),
3448
		'pm_rules' => array('id_rule', 'criteria', 'actions'),
3449
		'qanda' => array('id_question', 'answers'),
3450
		'subscriptions' => array('id_subscribe', 'cost'),
3451
		'user_alerts' => array('id_alert', 'extra', true),
3452
		'user_drafts' => array('id_draft', 'to_list', true),
3453
		// These last two are a bit different - we'll handle those separately
3454
		'settings' => array(),
3455
		'themes' => array()
3456
	);
3457
3458
	// Set up some context stuff...
3459
	// Because we're not using numeric indices, we need this to figure out the current table name...
3460
	$keys = array_keys($tables);
3461
3462
	$upcontext['page_title'] = $txt['converting_json'];
3463
	$upcontext['table_count'] = count($keys);
3464
	$upcontext['cur_table_num'] = $_GET['substep'];
3465
	$upcontext['cur_table_name'] = isset($keys[$_GET['substep']]) ? $keys[$_GET['substep']] : $keys[0];
3466
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3467
3468
	foreach ($keys as $id => $table)
3469
		if ($id < $_GET['substep'])
3470
			$upcontext['previous_tables'][] = $table;
3471
3472
	if ($command_line)
3473
		echo 'Converting data from serialize() to json_encode().';
3474
3475
	if (!$support_js || isset($_GET['xml']))
3476
	{
3477
		// Fix the data in each table
3478
		for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++)
3479
		{
3480
			$upcontext['cur_table_name'] = isset($keys[$substep + 1]) ? $keys[$substep + 1] : $keys[$substep];
3481
			$upcontext['cur_table_num'] = $substep + 1;
3482
3483
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3484
3485
			// Do we need to pause?
3486
			nextSubstep($substep);
3487
3488
			// Initialize a few things...
3489
			$where = '';
3490
			$vars = array();
3491
			$table = $keys[$substep];
3492
			$info = $tables[$table];
3493
3494
			// Now the fun - build our queries and all that fun stuff
3495
			if ($table == 'settings')
3496
			{
3497
				// Now a few settings...
3498
				$serialized_settings = array(
3499
					'attachment_basedirectories',
3500
					'attachmentUploadDir',
3501
					'cal_today_birthday',
3502
					'cal_today_event',
3503
					'cal_today_holiday',
3504
					'displayFields',
3505
					'last_attachments_directory',
3506
					'memberlist_cache',
3507
					'search_custom_index_config',
3508
					'spider_name_cache'
3509
				);
3510
3511
				// Loop through and fix these...
3512
				$new_settings = array();
3513
				if ($command_line)
3514
					echo "\n" . 'Fixing some settings...';
3515
3516
				foreach ($serialized_settings as $var)
3517
				{
3518
					if (isset($modSettings[$var]))
3519
					{
3520
						// Attempt to unserialize the setting
3521
						$temp = upgrade_unserialize($modSettings[$var]);
3522
3523
						if (!$temp && $command_line)
3524
							echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping.";
3525
						elseif ($temp !== false)
3526
							$new_settings[$var] = json_encode($temp);
3527
					}
3528
				}
3529
3530
				// Update everything at once
3531
				updateSettings($new_settings, true);
3532
3533
				if ($command_line)
3534
					echo ' done.';
3535
			}
3536
			elseif ($table == 'themes')
3537
			{
3538
				// Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point...
3539
				$query = $smcFunc['db_query']('', '
3540
					SELECT id_member, id_theme, value FROM {db_prefix}themes
3541
					WHERE variable = {string:admin_prefs}',
3542
					array(
3543
						'admin_prefs' => 'admin_preferences'
3544
					)
3545
				);
3546
3547
				if ($smcFunc['db_num_rows']($query) != 0)
3548
				{
3549
					while ($row = $smcFunc['db_fetch_assoc']($query))
3550
					{
3551
						$temp = upgrade_unserialize($row['value']);
3552
3553
						if ($command_line)
3554
						{
3555
							if ($temp === false)
3556
								echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.';
3557
							else
3558
								echo "\n" . 'Fixing admin preferences...';
3559
						}
3560
3561
						if ($temp !== false)
3562
						{
3563
							$row['value'] = json_encode($temp);
3564
3565
							// Even though we have all values from the table, UPDATE is still faster than REPLACE
3566
							$smcFunc['db_query']('', '
3567
								UPDATE {db_prefix}themes
3568
								SET value = {string:prefs}
3569
								WHERE id_theme = {int:theme}
3570
									AND id_member = {int:member}
3571
									AND variable = {string:admin_prefs}',
3572
								array(
3573
									'prefs' => $row['value'],
3574
									'theme' => $row['id_theme'],
3575
									'member' => $row['id_member'],
3576
									'admin_prefs' => 'admin_preferences'
3577
								)
3578
							);
3579
3580
							if ($command_line)
3581
								echo ' done.';
3582
						}
3583
					}
3584
3585
					$smcFunc['db_free_result']($query);
3586
				}
3587
			}
3588
			else
3589
			{
3590
				// First item is always the key...
3591
				$key = $info[0];
3592
				unset($info[0]);
3593
3594
				// Now we know what columns we have and such...
3595
				if (count($info) == 2 && $info[2] === true)
3596
				{
3597
					$col_select = $info[1];
3598
					$where = ' WHERE ' . $info[1] . ' != {empty}';
3599
				}
3600
				else
3601
				{
3602
					$col_select = implode(', ', $info);
3603
				}
3604
3605
				$query = $smcFunc['db_query']('', '
3606
					SELECT ' . $key . ', ' . $col_select . '
3607
					FROM {db_prefix}' . $table . $where,
3608
					array()
3609
				);
3610
3611
				if ($smcFunc['db_num_rows']($query) != 0)
3612
				{
3613
					if ($command_line)
3614
					{
3615
						echo "\n" . ' +++ Fixing the "' . $table . '" table...';
3616
						flush();
3617
					}
3618
3619
					while ($row = $smcFunc['db_fetch_assoc']($query))
3620
					{
3621
						$update = '';
3622
3623
						// We already know what our key is...
3624
						foreach ($info as $col)
3625
						{
3626
							if ($col !== true && $row[$col] != '')
3627
							{
3628
								$temp = upgrade_unserialize($row[$col]);
3629
3630
								// Oh well...
3631
								if ($temp === false)
3632
								{
3633
									$temp = array();
3634
3635
									if ($command_line)
3636
										echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n";
3637
								}
3638
3639
								$row[$col] = json_encode($temp);
3640
3641
								// Build our SET string and variables array
3642
								$update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}';
3643
								$vars[$col] = $row[$col];
3644
							}
3645
						}
3646
3647
						$vars[$key] = $row[$key];
3648
3649
						// In a few cases, we might have empty data, so don't try to update in those situations...
3650
						if (!empty($update))
3651
						{
3652
							$smcFunc['db_query']('', '
3653
								UPDATE {db_prefix}' . $table . '
3654
								SET ' . $update . '
3655
								WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}',
3656
								$vars
3657
							);
3658
						}
3659
					}
3660
3661
					if ($command_line)
3662
						echo ' done.';
3663
3664
					// Free up some memory...
3665
					$smcFunc['db_free_result']($query);
3666
				}
3667
			}
3668
			// If this is XML to keep it nice for the user do one table at a time anyway!
3669
			if (isset($_GET['xml']))
3670
				return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
3671
		}
3672
3673
		if ($command_line)
3674
		{
3675
			echo "\n" . 'Successful.' . "\n";
3676
			flush();
3677
		}
3678
		$upcontext['step_progress'] = 100;
3679
3680
		// Last but not least, insert a dummy setting so we don't have to do this again in the future...
3681
		updateSettings(array('json_done' => true));
3682
3683
		$_GET['substep'] = 0;
3684
		// Make sure we move on!
3685
		if ($command_line)
3686
			return ConvertUtf8();
3687
3688
		return true;
3689
	}
3690
3691
	// If this fails we just move on to deleting the upgrade anyway...
3692
	$_GET['substep'] = 0;
3693
	return false;
3694
}
3695
3696
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3697
						Templates are below this point
3698
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
3699
3700
// This is what is displayed if there's any chmod to be done. If not it returns nothing...
3701
function template_chmod()
3702
{
3703
	global $upcontext, $txt, $settings;
3704
3705
	// Don't call me twice!
3706
	if (!empty($upcontext['chmod_called']))
3707
		return;
3708
3709
	$upcontext['chmod_called'] = true;
3710
3711
	// Nothing?
3712
	if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error']))
3713
		return;
3714
3715
	// Was it a problem with Windows?
3716
	if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess')
3717
	{
3718
		echo '
3719
		<div class="error">
3720
			<p>', $txt['upgrade_writable_files'], '</p>
3721
			<ul class="error_content">
3722
				<li>' . implode('</li>
3723
				<li>', $upcontext['chmod']['files']) . '</li>
3724
			</ul>
3725
		</div>';
3726
3727
		return false;
3728
	}
3729
3730
	echo '
3731
		<div class="panel">
3732
			<h2>', $txt['upgrade_ftp_login'], '</h2>
3733
			<h3>', $txt['upgrade_ftp_perms'], '</h3>
3734
			<script>
3735
				function warning_popup()
3736
				{
3737
					popup = window.open(\'\',\'popup\',\'height=150,width=400,scrollbars=yes\');
3738
					var content = popup.document;
3739
					content.write(\'<!DOCTYPE html>\n\');
3740
					content.write(\'<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>\n\t<head>\n\t\t<meta name="robots" content="noindex">\n\t\t\');
3741
					content.write(\'<title>', $txt['upgrade_ftp_warning'], '</title>\n\t\t<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">\n\t</head>\n\t<body id="popup">\n\t\t\');
3742
					content.write(\'<div class="windowbg description">\n\t\t\t<h4>', $txt['upgrade_ftp_files'], '</h4>\n\t\t\t\');
3743
					content.write(\'<p>', implode('<br>\n\t\t\t', $upcontext['chmod']['files']), '</p>\n\t\t\t\');';
3744
3745
	if (isset($upcontext['systemos']) && $upcontext['systemos'] == 'linux')
3746
		echo '
3747
					content.write(\'<hr>\n\t\t\t\');
3748
					content.write(\'<p>', $txt['upgrade_ftp_shell'], '</p>\n\t\t\t\');
3749
					content.write(\'<tt># chmod a+w ', implode(' ', $upcontext['chmod']['files']), '</tt>\n\t\t\t\');';
3750
3751
	echo '
3752
					content.write(\'<a href="javascript:self.close();">close</a>\n\t\t</div>\n\t</body>\n</html>\');
3753
					content.close();
3754
				}
3755
			</script>';
3756
3757
	if (!empty($upcontext['chmod']['ftp_error']))
3758
		echo '
3759
			<div class="error">
3760
				<p>', $txt['upgrade_ftp_error'], '<p>
3761
				<code>', $upcontext['chmod']['ftp_error'], '</code>
3762
			</div>';
3763
3764
	if (empty($upcontext['chmod_in_form']))
3765
		echo '
3766
			<form action="', $upcontext['form_url'], '" method="post">';
3767
3768
	echo '
3769
				<dl class="settings">
3770
					<dt>
3771
						<label for="ftp_server">', $txt['ftp_server'], ':</label>
3772
					</dt>
3773
					<dd>
3774
						<div class="floatright">
3775
							<label for="ftp_port" class="textbox"><strong>', $txt['ftp_port'], ':</strong></label>
3776
							<input type="text" size="3" name="ftp_port" id="ftp_port" value="', isset($upcontext['chmod']['port']) ? $upcontext['chmod']['port'] : '21', '">
3777
						</div>
3778
						<input type="text" size="30" name="ftp_server" id="ftp_server" value="', isset($upcontext['chmod']['server']) ? $upcontext['chmod']['server'] : 'localhost', '">
3779
						<div class="smalltext">', $txt['ftp_server_info'], '</div>
3780
					</dd>
3781
					<dt>
3782
						<label for="ftp_username">', $txt['ftp_username'], ':</label>
3783
					</dt>
3784
					<dd>
3785
						<input type="text" size="30" name="ftp_username" id="ftp_username" value="', isset($upcontext['chmod']['username']) ? $upcontext['chmod']['username'] : '', '">
3786
						<div class="smalltext">', $txt['ftp_username_info'], '</div>
3787
					</dd>
3788
					<dt>
3789
						<label for="ftp_password">', $txt['ftp_password'], ':</label>
3790
					</dt>
3791
					<dd>
3792
						<input type="password" size="30" name="ftp_password" id="ftp_password">
3793
						<div class="smalltext">', $txt['ftp_password_info'], '</div>
3794
					</dd>
3795
					<dt>
3796
						<label for="ftp_path">', $txt['ftp_path'], ':</label>
3797
					</dt>
3798
					<dd>
3799
						<input type="text" size="30" name="ftp_path" id="ftp_path" value="', isset($upcontext['chmod']['path']) ? $upcontext['chmod']['path'] : '', '">
3800
						<div class="smalltext">', !empty($upcontext['chmod']['path']) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info'], '</div>
3801
					</dd>
3802
				</dl>
3803
3804
				<div class="righttext buttons">
3805
					<input type="submit" value="', $txt['ftp_connect'], '" class="button">
3806
				</div>';
3807
3808
	if (empty($upcontext['chmod_in_form']))
3809
		echo '
3810
			</form>';
3811
3812
	echo '
3813
		</div><!-- .panel -->';
3814
}
3815
3816
function template_upgrade_above()
3817
{
3818
	global $modSettings, $txt, $settings, $upcontext, $upgradeurl;
3819
3820
	echo '<!DOCTYPE html>
3821
<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>
3822
<head>
3823
	<meta charset="', isset($txt['lang_character_set']) ? $txt['lang_character_set'] : 'UTF-8', '">
3824
	<meta name="robots" content="noindex">
3825
	<title>', $txt['upgrade_upgrade_utility'], '</title>
3826
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">
3827
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/install.css">
3828
	', $txt['lang_rtl'] == true ? '<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/rtl.css">' : '', '
3829
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/', JQUERY_VERSION, '/jquery.min.js"></script>
3830
	<script src="', $settings['default_theme_url'], '/scripts/script.js"></script>
3831
	<script>
3832
		var smf_scripturl = \'', $upgradeurl, '\';
3833
		var smf_charset = \'', (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'UTF-8' : $txt['lang_character_set']) : $modSettings['global_character_set']), '\';
3834
		var startPercent = ', $upcontext['overall_percent'], ';
3835
3836
		// This function dynamically updates the step progress bar - and overall one as required.
3837
		function updateStepProgress(current, max, overall_weight)
3838
		{
3839
			// What out the actual percent.
3840
			var width = parseInt((current / max) * 100);
3841
			if (document.getElementById(\'step_progress\'))
3842
			{
3843
				document.getElementById(\'step_progress\').style.width = width + "%";
3844
				setInnerHTML(document.getElementById(\'step_text\'), width + "%");
3845
			}
3846
			if (overall_weight && document.getElementById(\'overall_progress\'))
3847
			{
3848
				overall_width = parseInt(startPercent + width * (overall_weight / 100));
3849
				document.getElementById(\'overall_progress\').style.width = overall_width + "%";
3850
				setInnerHTML(document.getElementById(\'overall_text\'), overall_width + "%");
3851
			}
3852
		}
3853
	</script>
3854
</head>
3855
<body>
3856
	<div id="footerfix">
3857
	<div id="header">
3858
		<h1 class="forumtitle">', $txt['upgrade_upgrade_utility'], '</h1>
3859
		<img id="smflogo" src="', $settings['default_theme_url'], '/images/smflogo.svg" alt="Simple Machines Forum" title="Simple Machines Forum">
3860
	</div>
3861
	<div id="wrapper">
3862
		<div id="content_section">
3863
			<div id="main_content_section">
3864
				<div id="main_steps">
3865
					<h2>', $txt['upgrade_progress'], '</h2>
3866
					<ul class="steps_list">';
3867
3868
	foreach ($upcontext['steps'] as $num => $step)
3869
		echo '
3870
						<li', $num == $upcontext['current_step'] ? ' class="stepcurrent"' : '', '>
3871
							', $txt['upgrade_step'], ' ', $step[0], ': ', $txt[$step[1]], '
3872
						</li>';
3873
3874
	echo '
3875
					</ul>
3876
				</div><!-- #main_steps -->
3877
3878
				<div id="install_progress">
3879
					<div id="progress_bar" class="progress_bar progress_green">
3880
						<h3>', $txt['upgrade_overall_progress'], '</h3>
3881
						<div id="overall_progress" class="bar" style="width: ', $upcontext['overall_percent'], '%;"></div>
3882
						<span id="overall_text">', $upcontext['overall_percent'], '%</span>
3883
					</div>';
3884
3885
	if (isset($upcontext['step_progress']))
3886
		echo '
3887
					<div id="progress_bar_step" class="progress_bar progress_yellow">
3888
						<h3>', $txt['upgrade_step_progress'], '</h3>
3889
						<div id="step_progress" class="bar" style="width: ', $upcontext['step_progress'], '%;"></div>
3890
						<span id="step_text">', $upcontext['step_progress'], '%</span>
3891
					</div>';
3892
3893
	echo '
3894
					<div id="substep_bar_div" class="progress_bar ', isset($upcontext['substep_progress']) ? '' : 'hidden', '">
3895
						<h3 id="substep_name">', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], array('.' => ''))) : '', '</h3>
3896
						<div id="substep_progress" class="bar" style="width: ', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%;"></div>
3897
						<span id="substep_text">', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%</span>
3898
					</div>';
3899
3900
	// How long have we been running this?
3901
	$elapsed = time() - $upcontext['started'];
3902
	$mins = (int) ($elapsed / 60);
3903
	$seconds = $elapsed - $mins * 60;
3904
	echo '
3905
					<div class="smalltext time_elapsed">
3906
						', $txt['upgrade_time_elapsed'], ':
3907
						<span id="mins_elapsed">', $mins, '</span> ', $txt['upgrade_time_mins'], ', <span id="secs_elapsed">', $seconds, '</span> ', $txt['upgrade_time_secs'], '.
3908
					</div>';
3909
	echo '
3910
				</div><!-- #install_progress -->
3911
				<div id="main_screen" class="clear">
3912
					<h2>', $upcontext['page_title'], '</h2>
3913
					<div class="panel">';
3914
}
3915
3916
function template_upgrade_below()
3917
{
3918
	global $upcontext, $txt;
3919
3920
	if (!empty($upcontext['pause']))
3921
		echo '
3922
							<em>', $txt['upgrade_incomplete'], '.</em><br>
3923
3924
							<h2 style="margin-top: 2ex;">', $txt['upgrade_not_quite_done'], '</h2>
3925
							<h3>
3926
								', $txt['upgrade_paused_overload'], '
3927
							</h3>';
3928
3929
	if (!empty($upcontext['custom_warning']))
3930
		echo '
3931
							<div class="errorbox">
3932
								<h3>', $txt['upgrade_note'], '</h3>
3933
								', $upcontext['custom_warning'], '
3934
							</div>';
3935
3936
	echo '
3937
							<div class="righttext buttons">';
3938
3939
	if (!empty($upcontext['continue']))
3940
		echo '
3941
								<input type="submit" id="contbutt" name="contbutt" value="', $txt['upgrade_continue'], '"', $upcontext['continue'] == 2 ? ' disabled' : '', ' class="button">';
3942
	if (!empty($upcontext['skip']))
3943
		echo '
3944
								<input type="submit" id="skip" name="skip" value="', $txt['upgrade_skip'], '" onclick="dontSubmit = true; document.getElementById(\'contbutt\').disabled = \'disabled\'; return true;" class="button">';
3945
3946
	echo '
3947
							</div>
3948
						</form>
3949
					</div><!-- .panel -->
3950
				</div><!-- #main_screen -->
3951
			</div><!-- #main_content_section -->
3952
		</div><!-- #content_section -->
3953
	</div><!-- #wrapper -->
3954
	</div><!-- #footerfix -->
3955
	<div id="footer">
3956
		<ul>
3957
			<li class="copyright"><a href="https://www.simplemachines.org/" title="Simple Machines Forum" target="_blank" rel="noopener">SMF &copy; ' . SMF_SOFTWARE_YEAR . ', Simple Machines</a></li>
3958
		</ul>
3959
	</div>';
3960
3961
	// Are we on a pause?
3962
	if (!empty($upcontext['pause']))
3963
	{
3964
		echo '
3965
	<script>
3966
		window.onload = doAutoSubmit;
3967
		var countdown = 3;
3968
		var dontSubmit = false;
3969
3970
		function doAutoSubmit()
3971
		{
3972
			if (countdown == 0 && !dontSubmit)
3973
				document.upform.submit();
3974
			else if (countdown == -1)
3975
				return;
3976
3977
			document.getElementById(\'contbutt\').value = "', $txt['upgrade_continue'], ' (" + countdown + ")";
3978
			countdown--;
3979
3980
			setTimeout("doAutoSubmit();", 1000);
3981
		}
3982
	</script>';
3983
	}
3984
3985
	echo '
3986
</body>
3987
</html>';
3988
}
3989
3990
function template_xml_above()
3991
{
3992
	global $upcontext;
3993
3994
	echo '<', '?xml version="1.0" encoding="UTF-8"?', '>
3995
	<smf>';
3996
3997
	if (!empty($upcontext['get_data']))
3998
		foreach ($upcontext['get_data'] as $k => $v)
3999
			echo '
4000
		<get key="', $k, '">', $v, '</get>';
4001
}
4002
4003
function template_xml_below()
4004
{
4005
	echo '
4006
	</smf>';
4007
}
4008
4009
function template_error_message()
4010
{
4011
	global $upcontext, $txt;
4012
4013
	echo '
4014
	<div class="error">
4015
		', $upcontext['error_msg'], '
4016
		<br>
4017
		<a href="', $_SERVER['PHP_SELF'], '">', $txt['upgrade_respondtime_clickhere'], '</a>
4018
	</div>';
4019
}
4020
4021
function template_welcome_message()
4022
{
4023
	global $upcontext, $disable_security, $settings, $txt;
4024
4025
	echo '
4026
				<script src="https://www.simplemachines.org/smf/current-version.js?version=' . SMF_VERSION . '"></script>
4027
4028
				<h3>', sprintf($txt['upgrade_ready_proceed'], SMF_VERSION), '</h3>
4029
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">
4030
					<input type="hidden" name="', $upcontext['login_token_var'], '" value="', $upcontext['login_token'], '">
4031
4032
					<div id="version_warning" class="noticebox hidden">
4033
						<h3>', $txt['upgrade_warning'], '</h3>
4034
						', sprintf($txt['upgrade_warning_out_of_date'], SMF_VERSION, 'https://www.simplemachines.org'), '
4035
					</div>';
4036
4037
	$upcontext['chmod_in_form'] = true;
4038
	template_chmod();
4039
4040
	// For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade!
4041
	if ($upcontext['is_large_forum'])
4042
		echo '
4043
					<div class="errorbox">
4044
						<h3>', $txt['upgrade_warning'], '</h3>
4045
						', $txt['upgrade_warning_lots_data'], '
4046
					</div>';
4047
4048
	// A warning message?
4049
	if (!empty($upcontext['warning']))
4050
		echo '
4051
					<div class="errorbox">
4052
						<h3>', $txt['upgrade_warning'], '</h3>
4053
						', $upcontext['warning'], '
4054
					</div>';
4055
4056
	// Paths are incorrect?
4057
	echo '
4058
					<div class="errorbox', (file_exists($settings['default_theme_dir'] . '/scripts/script.js') ? ' hidden' : ''), '" id="js_script_missing_error">
4059
						<h3>', $txt['upgrade_critical_error'], '</h3>
4060
						', sprintf($txt['upgrade_error_script_js'], 'https://download.simplemachines.org/?tools'), '
4061
					</div>';
4062
4063
	// Is there someone already doing this?
4064
	if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600))
4065
	{
4066
		$ago = time() - $upcontext['started'];
4067
		$ago_hours = floor($ago / 3600);
4068
		$ago_minutes = intval(($ago / 60) % 60);
4069
		$ago_seconds = intval($ago % 60);
4070
		$agoTxt = $ago < 60 ? 'upgrade_time_s' : ($ago < 3600 ? 'upgrade_time_ms' : 'upgrade_time_hms');
4071
4072
		$updated = time() - $upcontext['updated'];
4073
		$updated_hours = floor($updated / 3600);
4074
		$updated_minutes = intval(($updated / 60) % 60);
4075
		$updated_seconds = intval($updated % 60);
4076
		$updatedTxt = $updated < 60 ? 'upgrade_time_updated_s' : ($updated < 3600 ? 'upgrade_time_updated_hm' : 'upgrade_time_updated_hms');
4077
4078
		echo '
4079
					<div class="errorbox">
4080
						<h3>', $txt['upgrade_warning'], '</h3>
4081
						<p>', sprintf($txt['upgrade_time_user'], $upcontext['user']['name']), '</p>
4082
						<p>', sprintf($txt[$agoTxt], $ago_seconds, $ago_minutes, $ago_hours), '</p>
4083
						<p>', sprintf($txt[$updatedTxt], $updated_seconds, $updated_minutes, $updated_hours), '</p>';
4084
4085
		if ($updated < 600)
4086
			echo '
4087
						<p>', $txt['upgrade_run_script'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_run_script2'], '</p>';
4088
4089
		if ($updated > $upcontext['inactive_timeout'])
4090
			echo '
4091
						<p>', $txt['upgrade_run'], '</p>';
4092
		elseif ($upcontext['inactive_timeout'] > 120)
4093
			echo '
4094
						<p>', sprintf($txt['upgrade_script_timeout_minutes'], $upcontext['user']['name'], round($upcontext['inactive_timeout'] / 60, 1)), '</p>';
4095
		else
4096
			echo '
4097
						<p>', sprintf($txt['upgrade_script_timeout_seconds'], $upcontext['user']['name'], $upcontext['inactive_timeout']), '</p>';
4098
4099
		echo '
4100
					</div>';
4101
	}
4102
4103
	echo '
4104
					<strong>', $txt['upgrade_admin_login'], ' ', $disable_security ? $txt['upgrade_admin_disabled'] : '', '</strong>
4105
					<h3>', $txt['upgrade_sec_login'], '</h3>
4106
					<dl class="settings adminlogin">
4107
						<dt>
4108
							<label for="user"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_username'], '</label>
4109
						</dt>
4110
						<dd>
4111
							<input type="text" name="user" value="', !empty($upcontext['username']) ? $upcontext['username'] : '', '"', $disable_security ? ' disabled' : '', '>';
4112
4113
	if (!empty($upcontext['username_incorrect']))
4114
		echo '
4115
							<div class="smalltext red">', $txt['upgrade_wrong_username'], '</div>';
4116
4117
	echo '
4118
						</dd>
4119
						<dt>
4120
							<label for="passwrd"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_password'], '</label>
4121
						</dt>
4122
						<dd>
4123
							<input type="password" name="passwrd" value=""', $disable_security ? ' disabled' : '', '>';
4124
4125
	if (!empty($upcontext['password_failed']))
4126
		echo '
4127
							<div class="smalltext red">', $txt['upgrade_wrong_password'], '</div>';
4128
4129
	echo '
4130
						</dd>';
4131
4132
	// Can they continue?
4133
	if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1)
4134
	{
4135
		echo '
4136
						<dd>
4137
							<label for="cont"><input type="checkbox" id="cont" name="cont" checked>', $txt['upgrade_continue_step'], '</label>
4138
						</dd>';
4139
	}
4140
4141
	echo '
4142
					</dl>
4143
					<span class="smalltext">
4144
						', $txt['upgrade_bypass'], '
4145
					</span>
4146
					<input type="hidden" name="login_attempt" id="login_attempt" value="1">
4147
					<input type="hidden" name="js_works" id="js_works" value="0">';
4148
4149
	// Say we want the continue button!
4150
	$upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1;
4151
4152
	// This defines whether javascript is going to work elsewhere :D
4153
	echo '
4154
					<script>
4155
						if (\'XMLHttpRequest\' in window && document.getElementById(\'js_works\'))
4156
							document.getElementById(\'js_works\').value = 1;
4157
4158
						// Latest version?
4159
						function smfCurrentVersion()
4160
						{
4161
							var smfVer, yourVer;
4162
4163
							if (!(\'smfVersion\' in window))
4164
								return;
4165
4166
							window.smfVersion = window.smfVersion.replace(/SMF\s?/g, \'\');
4167
4168
							smfVer = document.getElementById(\'smfVersion\');
4169
							yourVer = document.getElementById(\'yourVersion\');
4170
4171
							setInnerHTML(smfVer, window.smfVersion);
4172
4173
							var currentVersion = getInnerHTML(yourVer);
4174
							if (currentVersion < window.smfVersion)
4175
								document.getElementById(\'version_warning\').classList.remove(\'hidden\');
4176
						}
4177
						addLoadEvent(smfCurrentVersion);
4178
4179
						// This checks that the script file even exists!
4180
						if (typeof(smfSelectText) == \'undefined\')
4181
							document.getElementById(\'js_script_missing_error\').classList.remove(\'hidden\');
4182
4183
					</script>';
4184
}
4185
4186
function template_upgrade_options()
4187
{
4188
	global $upcontext, $modSettings, $db_prefix, $mmessage, $mtitle, $txt;
4189
4190
	echo '
4191
				<h3>', $txt['upgrade_areyouready'], '</h3>
4192
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">';
4193
4194
	// Warning message?
4195
	if (!empty($upcontext['upgrade_options_warning']))
4196
		echo '
4197
				<div class="errorbox">
4198
					<h3>', $txt['upgrade_warning'], '</h3>
4199
					', $upcontext['upgrade_options_warning'], '
4200
				</div>';
4201
4202
	echo '
4203
				<ul class="upgrade_settings">
4204
					<li>
4205
						<input type="checkbox" name="backup" id="backup" value="1" checked>
4206
						<label for="backup">', $txt['upgrade_backup_table'], ' &quot;backup_' . $db_prefix . '&quot;.</label>
4207
						(', $txt['upgrade_recommended'], ')
4208
					</li>
4209
					<li>
4210
						<input type="checkbox" name="maint" id="maint" value="1" checked>
4211
						<label for="maint">', $txt['upgrade_maintenance'], '</label>
4212
						<span class="smalltext">(<a href="javascript:void(0)" onclick="document.getElementById(\'mainmess\').classList.toggle(\'hidden\')">', $txt['upgrade_customize'], '</a>)</span>
4213
						<div id="mainmess" class="hidden">
4214
							<strong class="smalltext">', $txt['upgrade_maintenance_title'], ' </strong><br>
4215
							<input type="text" name="maintitle" size="30" value="', htmlspecialchars($mtitle), '"><br>
4216
							<strong class="smalltext">', $txt['upgrade_maintenance_message'], ' </strong><br>
4217
							<textarea name="mainmessage" rows="3" cols="50">', htmlspecialchars($mmessage), '</textarea>
4218
						</div>
4219
					</li>
4220
					<li>
4221
						<input type="checkbox" name="debug" id="debug" value="1">
4222
						<label for="debug">'.$txt['upgrade_debug_info'], '</label>
4223
					</li>
4224
					<li>
4225
						<input type="checkbox" name="empty_error" id="empty_error" value="1">
4226
						<label for="empty_error">', $txt['upgrade_empty_errorlog'], '</label>
4227
					</li>';
4228
4229
	if (!empty($upcontext['karma_installed']['good']) || !empty($upcontext['karma_installed']['bad']))
4230
		echo '
4231
					<li>
4232
						<input type="checkbox" name="delete_karma" id="delete_karma" value="1">
4233
						<label for="delete_karma">', $txt['upgrade_delete_karma'], '</label>
4234
					</li>';
4235
4236
	echo '
4237
					<li>
4238
						<input type="checkbox" name="stats" id="stats" value="1"', empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']) ? '' : ' checked="checked"', '>
4239
						<label for="stat">
4240
							', $txt['upgrade_stats_collection'], '<br>
4241
							<span class="smalltext">', sprintf($txt['upgrade_stats_info'], 'https://www.simplemachines.org/about/stats.php'), '</a></span>
4242
						</label>
4243
					</li>
4244
					<li>
4245
						<input type="checkbox" name="migrateSettings" id="migrateSettings" value="1"', empty($upcontext['migrate_settings_recommended']) ? '' : ' checked="checked"', '>
4246
						<label for="migrateSettings">
4247
							', $txt['upgrade_migrate_settings_file'], '
4248
						</label>
4249
					</li>
4250
				</ul>
4251
				<input type="hidden" name="upcont" value="1">';
4252
4253
	// We need a normal continue button here!
4254
	$upcontext['continue'] = 1;
4255
}
4256
4257
// Template for the database backup tool/
4258
function template_backup_database()
4259
{
4260
	global $upcontext, $support_js, $is_debug, $txt;
4261
4262
	echo '
4263
				<h3>', $txt['upgrade_wait'], '</h3>';
4264
4265
	echo '
4266
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4267
					<input type="hidden" name="backup_done" id="backup_done" value="0">
4268
					<strong>', sprintf($txt['upgrade_completedtables_outof'], $upcontext['cur_table_num'], $upcontext['table_count']), '</strong>
4269
					<div id="debug_section">
4270
						<span id="debuginfo"></span>
4271
					</div>';
4272
4273
	// Dont any tables so far?
4274
	if (!empty($upcontext['previous_tables']))
4275
		foreach ($upcontext['previous_tables'] as $table)
4276
			echo '
4277
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4278
4279
	echo '
4280
					<h3 id="current_tab">
4281
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4282
					</h3>
4283
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_backup_complete'], '</p>';
4284
4285
	// Continue please!
4286
	$upcontext['continue'] = $support_js ? 2 : 1;
4287
4288
	// If javascript allows we want to do this using XML.
4289
	if ($support_js)
4290
	{
4291
		echo '
4292
					<script>
4293
						var lastTable = ', $upcontext['cur_table_num'], ';
4294
						function getNextTables()
4295
						{
4296
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4297
						}
4298
4299
						// Got an update!
4300
						function onBackupUpdate(oXMLDoc)
4301
						{
4302
							var sCurrentTableName = "";
4303
							var iTableNum = 0;
4304
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4305
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4306
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4307
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4308
4309
							// Update the page.
4310
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4311
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4312
							lastTable = iTableNum;
4313
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4314
4315
		// If debug flood the screen.
4316
		if ($is_debug)
4317
			echo '
4318
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4319
4320
							if (document.getElementById(\'debug_section\').scrollHeight)
4321
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4322
4323
		echo '
4324
							// Get the next update...
4325
							if (iTableNum == ', $upcontext['table_count'], ')
4326
							{
4327
								document.getElementById(\'commess\').classList.remove("hidden");
4328
								document.getElementById(\'current_tab\').classList.add("hidden");
4329
								document.getElementById(\'contbutt\').disabled = 0;
4330
								document.getElementById(\'backup_done\').value = 1;
4331
							}
4332
							else
4333
								getNextTables();
4334
						}
4335
						getNextTables();
4336
					//# sourceURL=dynamicScript-bkup.js
4337
					</script>';
4338
	}
4339
}
4340
4341
function template_backup_xml()
4342
{
4343
	global $upcontext;
4344
4345
	echo '
4346
		<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4347
}
4348
4349
// Here is the actual "make the changes" template!
4350
function template_database_changes()
4351
{
4352
	global $upcontext, $support_js, $is_debug, $timeLimitThreshold, $txt;
4353
4354
	if (empty($is_debug) && !empty($upcontext['upgrade_status']['debug']))
4355
		$is_debug = true;
4356
4357
	echo '
4358
				<h3>', $txt['upgrade_db_changes'], '</h3>
4359
				<h4><em>', $txt['upgrade_db_patient'], '</em></h4>';
4360
4361
	echo '
4362
				<form action="', $upcontext['form_url'], '&amp;filecount=', $upcontext['file_count'], '" name="upform" id="upform" method="post">
4363
					<input type="hidden" name="database_done" id="database_done" value="0">';
4364
4365
	// No javascript looks rubbish!
4366
	if (!$support_js)
4367
	{
4368
		foreach ($upcontext['actioned_items'] as $num => $item)
4369
		{
4370
			if ($num != 0)
4371
				echo ' Successful!';
4372
			echo '<br>' . $item;
4373
		}
4374
4375
		// Only tell deubbers how much time they wasted waiting for the upgrade because they don't have javascript.
4376
		if (!empty($upcontext['changes_complete']))
4377
		{
4378
			if ($is_debug)
4379
			{
4380
				$active = time() - $upcontext['started'];
4381
				$hours = floor($active / 3600);
4382
				$minutes = intval(($active / 60) % 60);
4383
				$seconds = intval($active % 60);
4384
4385
				echo '', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '<br>';
4386
			}
4387
			else
4388
				echo '', $txt['upgrade_success'], '<br>';
4389
4390
			echo '
4391
					<p id="commess">', $txt['upgrade_db_complete'], '</p>';
4392
		}
4393
	}
4394
	else
4395
	{
4396
		// Tell them how many files we have in total.
4397
		if ($upcontext['file_count'] > 1)
4398
			echo '
4399
					<strong id="info1">', $txt['upgrade_script'], ' <span id="file_done">', $upcontext['cur_file_num'], '</span> of ', $upcontext['file_count'], '.</strong>';
4400
4401
		echo '
4402
					<h3 id="info2">
4403
						<strong>', $txt['upgrade_executing'], '</strong> &quot;<span id="cur_item_name">', $upcontext['current_item_name'], '</span>&quot; (<span id="item_num">', $upcontext['current_item_num'], '</span> ', $txt['upgrade_of'], ' <span id="total_items"><span id="item_count">', $upcontext['total_items'], '</span>', $upcontext['file_count'] > 1 ? ' - of this script' : '', ')</span>
4404
					</h3>
4405
					<p id="commess" class="', !empty($upcontext['changes_complete']) || $upcontext['current_debug_item_num'] == $upcontext['debug_items'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_db_complete2'], '</p>';
4406
4407
		if ($is_debug)
4408
		{
4409
			// Let our debuggers know how much time was spent, but not wasted since JS handled refreshing the page!
4410
			if ($upcontext['current_debug_item_num'] == $upcontext['debug_items'])
4411
			{
4412
				$active = time() - $upcontext['started'];
4413
				$hours = floor($active / 3600);
4414
				$minutes = intval(($active / 60) % 60);
4415
				$seconds = intval($active % 60);
4416
4417
				echo '
4418
					<p id="upgradeCompleted">', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '</p>';
4419
			}
4420
			else
4421
				echo '
4422
					<p id="upgradeCompleted"></p>';
4423
4424
			echo '
4425
					<div id="debug_section">
4426
						<span id="debuginfo"></span>
4427
					</div>';
4428
		}
4429
	}
4430
4431
	// Place for the XML error message.
4432
	echo '
4433
					<div id="error_block" class="errorbox', empty($upcontext['error_message']) ? ' hidden' : '', '">
4434
						<h3>', $txt['upgrade_error'], '</h3>
4435
						<div id="error_message">', isset($upcontext['error_message']) ? $upcontext['error_message'] : $txt['upgrade_unknown_error'], '</div>
4436
					</div>';
4437
4438
	// We want to continue at some point!
4439
	$upcontext['continue'] = $support_js ? 2 : 1;
4440
4441
	// If javascript allows we want to do this using XML.
4442
	if ($support_js)
4443
	{
4444
		echo '
4445
					<script>
4446
						var lastItem = ', $upcontext['current_debug_item_num'], ';
4447
						var sLastString = "', strtr($upcontext['current_debug_item_name'], array('"' => '&quot;')), '";
4448
						var iLastSubStepProgress = -1;
4449
						var curFile = ', $upcontext['cur_file_num'], ';
4450
						var totalItems = 0;
4451
						var prevFile = 0;
4452
						var retryCount = 0;
4453
						var testvar = 0;
4454
						var timeOutID = 0;
4455
						var getData = "";
4456
						var debugItems = ', $upcontext['debug_items'], ';';
4457
4458
		if ($is_debug)
4459
			echo '
4460
						var upgradeStartTime = ' . $upcontext['started'] . ';';
4461
4462
		echo '
4463
						function getNextItem()
4464
						{
4465
							// We want to track this...
4466
							if (timeOutID)
4467
								clearTimeout(timeOutID);
4468
							timeOutID = window.setTimeout("retTimeout()", ', (10 * $timeLimitThreshold), '000);
4469
4470
							getXMLDocument(\'', $upcontext['form_url'], '&xml&filecount=', $upcontext['file_count'], '&substep=\' + lastItem + getData, onItemUpdate);
4471
						}
4472
4473
						// Got an update!
4474
						function onItemUpdate(oXMLDoc)
4475
						{
4476
							var sItemName = "";
4477
							var sDebugName = "";
4478
							var iItemNum = 0;
4479
							var iSubStepProgress = -1;
4480
							var iDebugNum = 0;
4481
							var bIsComplete = 0;
4482
							var bSkipped = 0;
4483
							getData = "";
4484
4485
							// We\'ve got something - so reset the timeout!
4486
							if (timeOutID)
4487
								clearTimeout(timeOutID);
4488
4489
							// Assume no error at this time...
4490
							document.getElementById("error_block").classList.add("hidden");
4491
4492
							// Are we getting some duff info?
4493
							if (!oXMLDoc.getElementsByTagName("item")[0])
4494
							{
4495
								// Too many errors?
4496
								if (retryCount > 15)
4497
								{
4498
									document.getElementById("error_block").classList.remove("hidden");
4499
									setInnerHTML(document.getElementById("error_message"), "Error retrieving information on step: " + (sDebugName == "" ? sLastString : sDebugName));';
4500
4501
		if ($is_debug)
4502
			echo '
4503
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4504
4505
		echo '
4506
								}
4507
								else
4508
								{
4509
									retryCount++;
4510
									getNextItem();
4511
								}
4512
								return false;
4513
							}
4514
4515
							// Never allow loops.
4516
							if (curFile == prevFile)
4517
							{
4518
								retryCount++;
4519
								if (retryCount > 10)
4520
								{
4521
									document.getElementById("error_block").classList.remove("hidden");
4522
									setInnerHTML(document.getElementById("error_message"), "', $txt['upgrade_loop'], '" + sDebugName);';
4523
4524
		if ($is_debug)
4525
			echo '
4526
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4527
4528
		echo '
4529
								}
4530
							}
4531
							retryCount = 0;
4532
4533
							for (var i = 0; i < oXMLDoc.getElementsByTagName("item")[0].childNodes.length; i++)
4534
								sItemName += oXMLDoc.getElementsByTagName("item")[0].childNodes[i].nodeValue;
4535
							for (var i = 0; i < oXMLDoc.getElementsByTagName("debug")[0].childNodes.length; i++)
4536
								sDebugName += oXMLDoc.getElementsByTagName("debug")[0].childNodes[i].nodeValue;
4537
							for (var i = 0; i < oXMLDoc.getElementsByTagName("get").length; i++)
4538
							{
4539
								getData += "&" + oXMLDoc.getElementsByTagName("get")[i].getAttribute("key") + "=";
4540
								for (var j = 0; j < oXMLDoc.getElementsByTagName("get")[i].childNodes.length; j++)
4541
								{
4542
									getData += oXMLDoc.getElementsByTagName("get")[i].childNodes[j].nodeValue;
4543
								}
4544
							}
4545
4546
							iItemNum = oXMLDoc.getElementsByTagName("item")[0].getAttribute("num");
4547
							iDebugNum = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("num"));
4548
							bIsComplete = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("complete"));
4549
							bSkipped = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("skipped"));
4550
							iSubStepProgress = parseFloat(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("percent"));
4551
							sLastString = sDebugName + " (Item: " + iDebugNum + ")";
4552
4553
							curFile = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("num"));
4554
							debugItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("debug_items"));
4555
							totalItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("items"));
4556
4557
							// If we have an error we haven\'t completed!
4558
							if (oXMLDoc.getElementsByTagName("error")[0] && bIsComplete)
4559
								iDebugNum = lastItem;
4560
4561
							// Do we have the additional progress bar?
4562
							if (iSubStepProgress != -1)
4563
							{
4564
								document.getElementById("substep_bar_div").classList.remove("hidden");
4565
								document.getElementById("substep_progress").style.width = iSubStepProgress + "%";
4566
								setInnerHTML(document.getElementById("substep_text"), iSubStepProgress + "%");
4567
								setInnerHTML(document.getElementById("substep_name"), sDebugName.replace(/\./g, ""));
4568
							}
4569
							else
4570
							{
4571
								document.getElementById("substep_bar_div").classList.add("hidden");
4572
							}
4573
4574
							// Move onto the next item?
4575
							if (bIsComplete)
4576
								lastItem = iDebugNum;
4577
							else
4578
								lastItem = iDebugNum - 1;
4579
4580
							// Are we finished?
4581
							if (bIsComplete && iDebugNum == -1 && curFile >= ', $upcontext['file_count'], ')
4582
							{';
4583
4584
		// Database Changes, tell us how much time we spen to do this.  If this gets updated via JS.
4585
		if ($is_debug)
4586
			echo '
4587
								document.getElementById(\'debug_section\').classList.add("hidden");
4588
4589
								var upgradeFinishedTime = parseInt(oXMLDoc.getElementsByTagName("curtime")[0].childNodes[0].nodeValue);
4590
								var diffTime = upgradeFinishedTime - upgradeStartTime;
4591
								var diffHours = Math.floor(diffTime / 3600);
4592
								var diffMinutes = parseInt((diffTime / 60) % 60);
4593
								var diffSeconds = parseInt(diffTime % 60);
4594
4595
								var completedTxt = "', $txt['upgrade_success_time_db'], '";
4596
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4597
4598
								completedTxt = completedTxt.replace("%1$d", diffSeconds).replace("%2$d", diffMinutes).replace("%3$d", diffHours);
4599
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4600
								setInnerHTML(document.getElementById("upgradeCompleted"), completedTxt);';
4601
4602
		echo '
4603
4604
								document.getElementById(\'commess\').classList.remove("hidden");
4605
								document.getElementById(\'contbutt\').disabled = 0;
4606
								document.getElementById(\'database_done\').value = 1;';
4607
4608
		if ($upcontext['file_count'] > 1)
4609
			echo '
4610
								document.getElementById(\'info1\').classList.add(\'hidden\');';
4611
4612
		echo '
4613
								document.getElementById(\'info2\').classList.add(\'hidden\');
4614
								updateStepProgress(100, 100, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4615
								return true;
4616
							}
4617
							// Was it the last step in the file?
4618
							else if (bIsComplete && iDebugNum == -1)
4619
							{
4620
								lastItem = 0;
4621
								prevFile = curFile;';
4622
4623
		if ($is_debug)
4624
			echo '
4625
								setOuterHTML(document.getElementById(\'debuginfo\'), \'Moving to next script file...done<br><span id="debuginfo"><\' + \'/span>\');';
4626
4627
		echo '
4628
								getNextItem();
4629
								return true;
4630
							}';
4631
4632
		// If debug scroll the screen.
4633
		if ($is_debug)
4634
			echo '
4635
							if (iLastSubStepProgress == -1)
4636
							{
4637
								// Give it consistent dots.
4638
								dots = sDebugName.match(/\./g);
4639
								numDots = dots ? dots.length : 0;
4640
								for (var i = numDots; i < 3; i++)
4641
									sDebugName += ".";
4642
								setOuterHTML(document.getElementById(\'debuginfo\'), sDebugName + \'<span id="debuginfo"><\' + \'/span>\');
4643
							}
4644
							iLastSubStepProgress = iSubStepProgress;
4645
4646
							if (bIsComplete && bSkipped)
4647
								setOuterHTML(document.getElementById(\'debuginfo\'), \'skipped<br><span id="debuginfo"><\' + \'/span>\');
4648
							else if (bIsComplete)
4649
								setOuterHTML(document.getElementById(\'debuginfo\'), \'done<br><span id="debuginfo"><\' + \'/span>\');
4650
							else
4651
								setOuterHTML(document.getElementById(\'debuginfo\'), \'...<span id="debuginfo"><\' + \'/span>\');
4652
4653
							if (document.getElementById(\'debug_section\').scrollHeight)
4654
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4655
4656
		echo '
4657
							// Update the page.
4658
							setInnerHTML(document.getElementById(\'item_num\'), iItemNum);
4659
							setInnerHTML(document.getElementById(\'cur_item_name\'), sItemName);';
4660
4661
		if ($upcontext['file_count'] > 1)
4662
		{
4663
			echo '
4664
							setInnerHTML(document.getElementById(\'file_done\'), curFile);
4665
							setInnerHTML(document.getElementById(\'item_count\'), totalItems);';
4666
		}
4667
4668
		echo '
4669
							// Is there an error?
4670
							if (oXMLDoc.getElementsByTagName("error")[0])
4671
							{
4672
								var sErrorMsg = "";
4673
								for (var i = 0; i < oXMLDoc.getElementsByTagName("error")[0].childNodes.length; i++)
4674
									sErrorMsg += oXMLDoc.getElementsByTagName("error")[0].childNodes[i].nodeValue;
4675
								document.getElementById("error_block").classList.remove("hidden");
4676
								setInnerHTML(document.getElementById("error_message"), sErrorMsg);
4677
								return false;
4678
							}
4679
4680
							// Get the progress bar right.
4681
							barTotal = debugItems * ', $upcontext['file_count'], ';
4682
							barDone = (debugItems * (curFile - 1)) + lastItem;
4683
4684
							updateStepProgress(barDone, barTotal, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4685
4686
							// Finally - update the time here as it shows the server is responding!
4687
							curTime = new Date();
4688
							iElapsed = (curTime.getTime() / 1000 - ', $upcontext['started'], ');
4689
							mins = parseInt(iElapsed / 60);
4690
							secs = parseInt(iElapsed - mins * 60);
4691
							setInnerHTML(document.getElementById("mins_elapsed"), mins);
4692
							setInnerHTML(document.getElementById("secs_elapsed"), secs);
4693
4694
							getNextItem();
4695
							return true;
4696
						}
4697
4698
						// What if we timeout?!
4699
						function retTimeout(attemptAgain)
4700
						{
4701
							// Oh noes...
4702
							if (!attemptAgain)
4703
							{
4704
								document.getElementById("error_block").classList.remove("hidden");
4705
								setInnerHTML(document.getElementById("error_message"), "', sprintf($txt['upgrade_respondtime'], ($timeLimitThreshold * 10)), '" + "<a href=\"#\" onclick=\"retTimeout(true); return false;\">', $txt['upgrade_respondtime_clickhere'], '</a>");
4706
							}
4707
							else
4708
							{
4709
								document.getElementById("error_block").classList.add("hidden");
4710
								getNextItem();
4711
							}
4712
						}';
4713
4714
		// Start things off assuming we've not errored.
4715
		if (empty($upcontext['error_message']))
4716
			echo '
4717
						getNextItem();';
4718
4719
		echo '
4720
					//# sourceURL=dynamicScript-dbch.js
4721
					</script>';
4722
	}
4723
	return;
4724
}
4725
4726
function template_database_xml()
4727
{
4728
	global $is_debug, $upcontext;
4729
4730
	echo '
4731
	<file num="', $upcontext['cur_file_num'], '" items="', $upcontext['total_items'], '" debug_items="', $upcontext['debug_items'], '">', $upcontext['cur_file_name'], '</file>
4732
	<item num="', $upcontext['current_item_num'], '">', $upcontext['current_item_name'], '</item>
4733
	<debug num="', $upcontext['current_debug_item_num'], '" percent="', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : '-1', '" complete="', empty($upcontext['completed_step']) ? 0 : 1, '" skipped="', empty($upcontext['skip_db_substeps']) ? 0 : 1, '">', $upcontext['current_debug_item_name'], '</debug>';
4734
4735
	if (!empty($upcontext['error_message']))
4736
		echo '
4737
	<error>', $upcontext['error_message'], '</error>';
4738
4739
	if (!empty($upcontext['error_string']))
4740
		echo '
4741
	<sql>', $upcontext['error_string'], '</sql>';
4742
4743
	if ($is_debug)
4744
		echo '
4745
	<curtime>', time(), '</curtime>';
4746
}
4747
4748
// Template for the UTF-8 conversion step. Basically a copy of the backup stuff with slight modifications....
4749
function template_convert_utf8()
4750
{
4751
	global $upcontext, $support_js, $is_debug, $txt;
4752
4753
	echo '
4754
				<h3>', $txt['upgrade_wait2'], '</h3>
4755
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4756
					<input type="hidden" name="utf8_done" id="utf8_done" value="0">
4757
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4758
					<div id="debug_section">
4759
						<span id="debuginfo"></span>
4760
					</div>';
4761
4762
	// Done any tables so far?
4763
	if (!empty($upcontext['previous_tables']))
4764
		foreach ($upcontext['previous_tables'] as $table)
4765
			echo '
4766
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4767
4768
	echo '
4769
					<h3 id="current_tab">
4770
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4771
					</h3>';
4772
4773
	// If we dropped their index, let's let them know
4774
	if ($upcontext['dropping_index'])
4775
		echo '
4776
					<p id="indexmsg" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '>', $txt['upgrade_fulltext'], '</p>';
4777
4778
	// Completion notification
4779
	echo '
4780
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_conversion_proceed'], '</p>';
4781
4782
	// Continue please!
4783
	$upcontext['continue'] = $support_js ? 2 : 1;
4784
4785
	// If javascript allows we want to do this using XML.
4786
	if ($support_js)
4787
	{
4788
		echo '
4789
					<script>
4790
						var lastTable = ', $upcontext['cur_table_num'], ';
4791
						function getNextTables()
4792
						{
4793
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onConversionUpdate);
4794
						}
4795
4796
						// Got an update!
4797
						function onConversionUpdate(oXMLDoc)
4798
						{
4799
							var sCurrentTableName = "";
4800
							var iTableNum = 0;
4801
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4802
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4803
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4804
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4805
4806
							// Update the page.
4807
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4808
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4809
							lastTable = iTableNum;
4810
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4811
4812
		// If debug flood the screen.
4813
		if ($is_debug)
4814
			echo '
4815
						setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4816
4817
						if (document.getElementById(\'debug_section\').scrollHeight)
4818
							document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4819
4820
		echo '
4821
						// Get the next update...
4822
						if (iTableNum == ', $upcontext['table_count'], ')
4823
						{
4824
							document.getElementById(\'commess\').classList.remove(\'hidden\');
4825
							if (document.getElementById(\'indexmsg\') != null) {
4826
								document.getElementById(\'indexmsg\').classList.remove(\'hidden\');
4827
							}
4828
							document.getElementById(\'current_tab\').classList.add(\'hidden\');
4829
							document.getElementById(\'contbutt\').disabled = 0;
4830
							document.getElementById(\'utf8_done\').value = 1;
4831
						}
4832
						else
4833
							getNextTables();
4834
					}
4835
					getNextTables();
4836
				//# sourceURL=dynamicScript-conv.js
4837
				</script>';
4838
	}
4839
}
4840
4841
function template_convert_xml()
4842
{
4843
	global $upcontext;
4844
4845
	echo '
4846
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4847
}
4848
4849
// Template for the database backup tool/
4850
function template_serialize_json()
4851
{
4852
	global $upcontext, $support_js, $is_debug, $txt;
4853
4854
	echo '
4855
				<h3>', $txt['upgrade_convert_datajson'], '</h3>
4856
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4857
					<input type="hidden" name="json_done" id="json_done" value="0">
4858
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4859
					<div id="debug_section">
4860
						<span id="debuginfo"></span>
4861
					</div>';
4862
4863
	// Dont any tables so far?
4864
	if (!empty($upcontext['previous_tables']))
4865
		foreach ($upcontext['previous_tables'] as $table)
4866
			echo '
4867
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4868
4869
	echo '
4870
					<h3 id="current_tab">
4871
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4872
					</h3>
4873
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_json_completed'], '</p>';
4874
4875
	// Try to make sure substep was reset.
4876
	if ($upcontext['cur_table_num'] == $upcontext['table_count'])
4877
		echo '
4878
					<input type="hidden" name="substep" id="substep" value="0">';
4879
4880
	// Continue please!
4881
	$upcontext['continue'] = $support_js ? 2 : 1;
4882
4883
	// If javascript allows we want to do this using XML.
4884
	if ($support_js)
4885
	{
4886
		echo '
4887
					<script>
4888
						var lastTable = ', $upcontext['cur_table_num'], ';
4889
						function getNextTables()
4890
						{
4891
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4892
						}
4893
4894
						// Got an update!
4895
						function onBackupUpdate(oXMLDoc)
4896
						{
4897
							var sCurrentTableName = "";
4898
							var iTableNum = 0;
4899
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4900
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4901
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4902
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4903
4904
							// Update the page.
4905
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4906
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4907
							lastTable = iTableNum;
4908
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4909
4910
		// If debug flood the screen.
4911
		if ($is_debug)
4912
			echo '
4913
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4914
4915
							if (document.getElementById(\'debug_section\').scrollHeight)
4916
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4917
4918
		echo '
4919
							// Get the next update...
4920
							if (iTableNum == ', $upcontext['table_count'], ')
4921
							{
4922
								document.getElementById(\'commess\').classList.remove("hidden");
4923
								document.getElementById(\'current_tab\').classList.add("hidden");
4924
								document.getElementById(\'contbutt\').disabled = 0;
4925
								document.getElementById(\'json_done\').value = 1;
4926
							}
4927
							else
4928
								getNextTables();
4929
						}
4930
						getNextTables();
4931
					//# sourceURL=dynamicScript-json.js
4932
					</script>';
4933
	}
4934
}
4935
4936
function template_serialize_json_xml()
4937
{
4938
	global $upcontext;
4939
4940
	echo '
4941
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4942
}
4943
4944
function template_upgrade_complete()
4945
{
4946
	global $upcontext, $upgradeurl, $settings, $boardurl, $is_debug, $txt;
4947
4948
	echo '
4949
				<h3>', sprintf($txt['upgrade_done'], $boardurl), '</h3>
4950
				<form action="', $boardurl, '/index.php">';
4951
4952
	if (!empty($upcontext['can_delete_script']))
4953
		echo '
4954
					<label>
4955
						<input type="checkbox" id="delete_self" onclick="doTheDelete(this);"> ', $txt['upgrade_delete_now'], '
4956
					</label>
4957
					<em>', $txt['upgrade_delete_server'], '</em>
4958
					<script>
4959
						function doTheDelete(theCheck)
4960
						{
4961
							var theImage = document.getElementById ? document.getElementById("delete_upgrader") : document.all.delete_upgrader;
4962
							theImage.src = "', $upgradeurl, '?delete=1&ts_" + (new Date().getTime());
4963
							theCheck.disabled = true;
4964
						}
4965
					</script>
4966
					<img src="', $settings['default_theme_url'], '/images/blank.png" alt="" id="delete_upgrader"><br>';
4967
4968
	// Show Upgrade time in debug mode when we completed the upgrade process totally
4969
	if ($is_debug)
4970
	{
4971
		$active = time() - $upcontext['started'];
4972
		$hours = floor($active / 3600);
4973
		$minutes = intval(($active / 60) % 60);
4974
		$seconds = intval($active % 60);
4975
4976
		if ($hours > 0)
4977
			echo '', sprintf($txt['upgrade_completed_time_hms'], $seconds, $minutes, $hours), '';
4978
		elseif ($minutes > 0)
4979
			echo '', sprintf($txt['upgrade_completed_time_ms'], $seconds, $minutes), '';
4980
		elseif ($seconds > 0)
4981
			echo '', sprintf($txt['upgrade_completed_time_s'], $seconds), '';
4982
	}
4983
4984
	echo '
4985
					<p>
4986
						', sprintf($txt['upgrade_problems'], 'https://www.simplemachines.org'), '
4987
						<br>
4988
						', $txt['upgrade_luck'], '<br>
4989
						Simple Machines
4990
					</p>';
4991
}
4992
4993
/**
4994
 * Convert MySQL (var)char ip col to binary
4995
 *
4996
 * newCol needs to be a varbinary(16) null able field
4997
 *
4998
 * @param string $targetTable The table to perform the operation on
4999
 * @param string $oldCol The old column to gather data from
5000
 * @param string $newCol The new column to put data in
5001
 * @param int $limit The amount of entries to handle at once.
5002
 * @param int $setSize The amount of entries after which to update the database.
5003
 * @return bool
5004
 */
5005
function MySQLConvertOldIp($targetTable, $oldCol, $newCol, $limit = 50000, $setSize = 100)
5006
{
5007
	global $smcFunc, $step_progress;
5008
5009
	$current_substep = !isset($_GET['substep']) ? 0 : (int) $_GET['substep'];
5010
5011
	if (empty($_GET['a']))
5012
		$_GET['a'] = 0;
5013
	$step_progress['name'] = 'Converting ips';
5014
	$step_progress['current'] = $_GET['a'];
5015
5016
	// Skip this if we don't have the column
5017
	$request = $smcFunc['db_query']('', '
5018
		SHOW FIELDS
5019
		FROM {db_prefix}{raw:table}
5020
		WHERE Field = {string:name}',
5021
		array(
5022
			'table' => $targetTable,
5023
			'name' => $oldCol,
5024
		)
5025
	);
5026
	if ($smcFunc['db_num_rows']($request) !== 1)
5027
	{
5028
		$smcFunc['db_free_result']($request);
5029
		return;
5030
	}
5031
	$smcFunc['db_free_result']($request);
5032
5033
	$is_done = false;
5034
	while (!$is_done)
5035
	{
5036
		// Keep looping at the current step.
5037
		nextSubstep($current_substep);
5038
5039
		// mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html
5040
		$arIp = array();
5041
5042
		$request = $smcFunc['db_query']('', '
5043
			SELECT DISTINCT {raw:old_col}
5044
			FROM {db_prefix}{raw:table_name}
5045
			WHERE {raw:new_col} IS NULL AND
5046
				{raw:old_col} != {string:unknown} AND
5047
				{raw:old_col} != {string:empty}
5048
			LIMIT {int:limit}',
5049
			array(
5050
				'old_col' => $oldCol,
5051
				'new_col' => $newCol,
5052
				'table_name' => $targetTable,
5053
				'empty' => '',
5054
				'limit' => $limit,
5055
				'unknown' => 'unknown',
5056
			)
5057
		);
5058
		while ($row = $smcFunc['db_fetch_assoc']($request))
5059
			$arIp[] = $row[$oldCol];
5060
5061
		$smcFunc['db_free_result']($request);
5062
5063
		// Special case, null ip could keep us in a loop.
5064
		if (!isset($arIp[0]))
5065
			unset($arIp[0]);
5066
5067
		if (empty($arIp))
5068
			$is_done = true;
5069
5070
		$updates = array();
5071
		$cases = array();
5072
		$count = count($arIp);
5073
		for ($i = 0; $i < $count; $i++)
5074
		{
5075
			$arIp[$i] = trim($arIp[$i]);
5076
5077
			if (!filter_var($arIp[$i], FILTER_VALIDATE_IP, FILTER_FLAG_IPV4 | FILTER_FLAG_IPV6))
5078
				continue;
5079
5080
			$updates['ip' . $i] = $arIp[$i];
5081
			$cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:ip' . $i . '}';
5082
5083
			if ($setSize > 0 && $i % $setSize === 0)
5084
			{
5085
				if (count($updates) == 1)
5086
					continue;
5087
5088
				$updates['whereSet'] = array_values($updates);
5089
				$smcFunc['db_query']('', '
5090
					UPDATE {db_prefix}' . $targetTable . '
5091
					SET ' . $newCol . ' = CASE ' .
5092
					implode('
5093
						', $cases) . '
5094
						ELSE NULL
5095
					END
5096
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
5097
					$updates
5098
				);
5099
5100
				$updates = array();
5101
				$cases = array();
5102
			}
5103
		}
5104
5105
		// Incase some extras made it through.
5106
		if (!empty($updates))
5107
		{
5108
			if (count($updates) == 1)
5109
			{
5110
				foreach ($updates as $key => $ip)
5111
				{
5112
					$smcFunc['db_query']('', '
5113
						UPDATE {db_prefix}' . $targetTable . '
5114
						SET ' . $newCol . ' = {inet:ip}
5115
						WHERE ' . $oldCol . ' = {string:ip}',
5116
						array(
5117
							'ip' => $ip
5118
						)
5119
					);
5120
				}
5121
			}
5122
			else
5123
			{
5124
				$updates['whereSet'] = array_values($updates);
5125
				$smcFunc['db_query']('', '
5126
					UPDATE {db_prefix}' . $targetTable . '
5127
					SET ' . $newCol . ' = CASE ' .
5128
					implode('
5129
						', $cases) . '
5130
						ELSE NULL
5131
					END
5132
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
5133
					$updates
5134
				);
5135
			}
5136
		}
5137
		else
5138
			$is_done = true;
5139
5140
		$_GET['a'] += $limit;
5141
		$step_progress['current'] = $_GET['a'];
5142
	}
5143
5144
	unset($_GET['a']);
5145
}
5146
5147
/**
5148
 * Get the column info. This is basically the same as smf_db_list_columns but we get 1 column, force detail and other checks.
5149
 *
5150
 * @param string $targetTable The table to perform the operation on
5151
 * @param string $column The column we are looking for.
5152
 *
5153
 * @return array Info on the table.
5154
 */
5155
function upgradeGetColumnInfo($targetTable, $column)
5156
{
5157
	global $smcFunc;
5158
5159
	// This should already be here, but be safe.
5160
	db_extend('packages');
5161
5162
	$columns = $smcFunc['db_list_columns']($targetTable, true);
5163
5164
	if (isset($columns[$column]))
5165
		return $columns[$column];
5166
	else
5167
		return null;
5168
}
5169
5170
?>