Failed Conditions
Push — release-2.1 ( 8d1977...8da17b )
by Rick
06:19
created

template_backup_xml()   A

Complexity

Conditions 1
Paths 1

Size

Total Lines 6
Code Lines 3

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 1
eloc 3
nop 0
dl 0
loc 6
rs 10
c 0
b 0
f 0
nc 1
1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2020 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC2
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1 RC2');
16
define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION);
17
define('SMF_SOFTWARE_YEAR', '2020');
18
define('SMF_LANG_VERSION', '2.1 RC2');
19
define('SMF_INSTALLING', 1);
20
21
define('JQUERY_VERSION', '3.4.1');
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.4.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.4',
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
		if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0)
120
			$upgrade_path = substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1];
121
	}
122
123
// Are we from the client?
124
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
125
{
126
	$command_line = true;
127
	$disable_security = true;
128
}
129
else
130
	$command_line = false;
131
132
// We can't do anything without these files.
133
foreach (array('upgrade-helper.php', 'Settings.php') as $required_file)
134
{
135
	if (!file_exists($upgrade_path . '/' . $required_file))
136
		die($required_file . ' was not found where it was expected: ' . $upgrade_path . '/' . $required_file . '! Make sure you have uploaded ALL files from the upgrade package to your forum\'s root directory. The upgrader cannot continue.');
137
138
	require_once($upgrade_path . '/' . $required_file);
139
}
140
141
// We don't use "-utf8" anymore...  Tweak the entry that may have been loaded by Settings.php
142
if (isset($language))
143
	$language = str_ireplace('-utf8', '', basename($language, '.lng'));
144
145
// Figure out a valid language request (if any)
146
// 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.
147
if (isset($_SERVER['QUERY_STRING']) && preg_match('~\blang=(\w+)~', $_SERVER['QUERY_STRING'], $matches))
148
	$upcontext['lang'] = $matches[1];
149
150
// Are we logged in?
151
if (isset($upgradeData))
152
{
153
	$upcontext['user'] = json_decode(base64_decode($upgradeData), true);
154
155
	// Check for sensible values.
156
	if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400)
157
		$upcontext['user']['started'] = time();
158
	if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400)
159
		$upcontext['user']['updated'] = 0;
160
161
	$upcontext['started'] = $upcontext['user']['started'];
162
	$upcontext['updated'] = $upcontext['user']['updated'];
163
164
	$is_debug = !empty($upcontext['user']['debug']) ? true : false;
165
166
	$upcontext['skip_db_substeps'] = !empty($upcontext['user']['skip_db_substeps']);
167
}
168
169
// Nothing sensible?
170
if (empty($upcontext['updated']))
171
{
172
	$upcontext['started'] = time();
173
	$upcontext['updated'] = 0;
174
	$upcontext['skip_db_substeps'] = false;
175
	$upcontext['user'] = array(
176
		'id' => 0,
177
		'name' => 'Guest',
178
		'pass' => 0,
179
		'started' => $upcontext['started'],
180
		'updated' => $upcontext['updated'],
181
	);
182
}
183
184
// Try to load the language file... or at least define a few necessary strings for now.
185
load_lang_file();
186
187
// Load up some essential data...
188
loadEssentialData();
189
190
// Are we going to be mimic'ing SSI at this point?
191
if (isset($_GET['ssi']))
192
{
193
	require_once($sourcedir . '/Errors.php');
194
	require_once($sourcedir . '/Logging.php');
195
	require_once($sourcedir . '/Load.php');
196
	require_once($sourcedir . '/Security.php');
197
	require_once($sourcedir . '/Subs-Package.php');
198
199
	// SMF isn't started up properly, but loadUserSettings calls our cookies.
200
	if (!isset($smcFunc['json_encode']))
201
	{
202
		$smcFunc['json_encode'] = 'json_encode';
203
		$smcFunc['json_decode'] = 'smf_json_decode';
204
	}
205
206
	loadUserSettings();
207
	loadPermissions();
208
}
209
210
// Include our helper functions.
211
require_once($sourcedir . '/Subs.php');
212
require_once($sourcedir . '/LogInOut.php');
213
require_once($sourcedir . '/Subs-Editor.php');
214
215
// Don't do security check if on Yabbse
216
if (!isset($modSettings['smfVersion']))
217
	$disable_security = true;
218
219
// This only exists if we're on SMF ;)
220
if (isset($modSettings['smfVersion']))
221
{
222
	$request = $smcFunc['db_query']('', '
223
		SELECT variable, value
224
		FROM {db_prefix}themes
225
		WHERE id_theme = {int:id_theme}
226
			AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
227
		array(
228
			'id_theme' => 1,
229
			'theme_url' => 'theme_url',
230
			'theme_dir' => 'theme_dir',
231
			'images_url' => 'images_url',
232
			'db_error_skip' => true,
233
		)
234
	);
235
	while ($row = $smcFunc['db_fetch_assoc']($request))
236
		$modSettings[$row['variable']] = $row['value'];
237
	$smcFunc['db_free_result']($request);
238
}
239
240
if (!isset($modSettings['theme_url']))
241
{
242
	$modSettings['theme_dir'] = $boarddir . '/Themes/default';
243
	$modSettings['theme_url'] = 'Themes/default';
244
	$modSettings['images_url'] = 'Themes/default/images';
245
}
246
if (!isset($settings['default_theme_url']))
247
	$settings['default_theme_url'] = $modSettings['theme_url'];
248
if (!isset($settings['default_theme_dir']))
249
	$settings['default_theme_dir'] = $modSettings['theme_dir'];
250
251
// Old DBs won't have this
252
if (!isset($modSettings['rand_seed']))
253
{
254
	if (!function_exists('cache_put_data'))
255
		require_once($sourcedir . '/Load.php');
256
	smf_seed_generator();
257
}
258
259
// This is needed in case someone invokes the upgrader using https when upgrading an http forum
260
if (httpsOn())
261
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
262
263
$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000;
264
265
// Have we got tracking data - if so use it (It will be clean!)
266
if (isset($_GET['data']))
267
{
268
	global $is_debug;
269
270
	$upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true);
271
	$upcontext['current_step'] = $upcontext['upgrade_status']['curstep'];
272
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
273
	$upcontext['rid'] = $upcontext['upgrade_status']['rid'];
274
	$support_js = $upcontext['upgrade_status']['js'];
275
276
	// Only set this if the upgrader status says so.
277
	if (empty($is_debug))
278
		$is_debug = $upcontext['upgrade_status']['debug'];
279
}
280
// Set the defaults.
281
else
282
{
283
	$upcontext['current_step'] = 0;
284
	$upcontext['rid'] = mt_rand(0, 5000);
285
	$upcontext['upgrade_status'] = array(
286
		'curstep' => 0,
287
		'lang' => isset($upcontext['lang']) ? $upcontext['lang'] : basename($language, '.lng'),
288
		'rid' => $upcontext['rid'],
289
		'pass' => 0,
290
		'debug' => 0,
291
		'js' => 0,
292
	);
293
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
294
}
295
296
// Now that we have the necessary info, make sure we loaded the right language file.
297
load_lang_file();
298
299
// Default title...
300
$upcontext['page_title'] = $txt['updating_smf_installation'];
301
302
// If this isn't the first stage see whether they are logging in and resuming.
303
if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step']))
304
	checkLogin();
305
306
if ($command_line)
307
	cmdStep0();
308
309
// Don't error if we're using xml.
310
if (isset($_GET['xml']))
311
	$upcontext['return_error'] = true;
312
313
// Loop through all the steps doing each one as required.
314
$upcontext['overall_percent'] = 0;
315
foreach ($upcontext['steps'] as $num => $step)
316
{
317
	if ($num >= $upcontext['current_step'])
318
	{
319
		// The current weight of this step in terms of overall progress.
320
		$upcontext['step_weight'] = $step[3];
321
		// Make sure we reset the skip button.
322
		$upcontext['skip'] = false;
323
324
		// We cannot proceed if we're not logged in.
325
		if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass'])
326
		{
327
			$upcontext['steps'][0][2]();
328
			break;
329
		}
330
331
		// Call the step and if it returns false that means pause!
332
		if (function_exists($step[2]) && $step[2]() === false)
333
			break;
334
		elseif (function_exists($step[2]))
335
		{
336
			//Start each new step with this unset, so the 'normal' template is called first
337
			unset($_GET['xml']);
338
			//Clear out warnings at the start of each step
339
			unset($upcontext['custom_warning']);
340
			$_GET['substep'] = 0;
341
			$upcontext['current_step']++;
342
		}
343
	}
344
	$upcontext['overall_percent'] += $step[3];
345
}
346
347
upgradeExit();
348
349
// Exit the upgrade script.
350
function upgradeExit($fallThrough = false)
351
{
352
	global $upcontext, $upgradeurl, $sourcedir, $command_line, $is_debug, $txt;
353
354
	// Save where we are...
355
	if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id']))
356
	{
357
		$upcontext['user']['step'] = $upcontext['current_step'];
358
		$upcontext['user']['substep'] = $_GET['substep'];
359
		$upcontext['user']['updated'] = time();
360
		$upcontext['user']['skip_db_substeps'] = !empty($upcontext['skip_db_substeps']);
361
		$upcontext['debug'] = $is_debug;
362
		$upgradeData = base64_encode(json_encode($upcontext['user']));
363
		require_once($sourcedir . '/Subs.php');
364
		require_once($sourcedir . '/Subs-Admin.php');
365
		updateSettingsFile(array('upgradeData' => $upgradeData));
366
		updateDbLastError(0);
367
	}
368
369
	// Handle the progress of the step, if any.
370
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
371
	{
372
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
373
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
374
	}
375
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
376
377
	// We usually dump our templates out.
378
	if (!$fallThrough)
379
	{
380
		// This should not happen my dear... HELP ME DEVELOPERS!!
381
		if (!empty($command_line))
382
		{
383
			if (function_exists('debug_print_backtrace'))
384
				debug_print_backtrace();
385
386
			printf($txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
387
			flush();
388
			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...
389
		}
390
391
		if (!isset($_GET['xml']))
392
			template_upgrade_above();
393
		else
394
		{
395
			header('content-type: text/xml; charset=UTF-8');
396
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
397
			$upcontext['get_data'] = array();
398
			foreach ($_GET as $k => $v)
399
			{
400
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
401
				{
402
					$upcontext['get_data'][$k] = $v;
403
				}
404
			}
405
			template_xml_above();
406
		}
407
408
		// Call the template.
409
		if (isset($upcontext['sub_template']))
410
		{
411
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
412
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
413
414
			// Custom stuff to pass back?
415
			if (!empty($upcontext['query_string']))
416
				$upcontext['form_url'] .= $upcontext['query_string'];
417
418
			// Call the appropriate subtemplate
419
			if (is_callable('template_' . $upcontext['sub_template']))
420
				call_user_func('template_' . $upcontext['sub_template']);
421
			else
422
				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...
423
		}
424
425
		// Was there an error?
426
		if (!empty($upcontext['forced_error_message']))
427
			echo $upcontext['forced_error_message'];
428
429
		// Show the footer.
430
		if (!isset($_GET['xml']))
431
			template_upgrade_below();
432
		else
433
			template_xml_below();
434
	}
435
436
	// Show the upgrade time for CLI when we are completely done, if in debug mode.
437
	if (!empty($command_line) && $is_debug)
438
	{
439
		$active = time() - $upcontext['started'];
440
		$hours = floor($active / 3600);
441
		$minutes = intval(($active / 60) % 60);
442
		$seconds = intval($active % 60);
443
444
		if ($hours > 0)
445
			echo "\n" . '', sprintf($txt['upgrade_completed_time_hms'], $hours, $minutes, $seconds), '' . "\n";
446
		elseif ($minutes > 0)
447
			echo "\n" . '', sprintf($txt['upgrade_completed_time_ms'], $minutes, $seconds), '' . "\n";
448
		elseif ($seconds > 0)
449
			echo "\n" . '', sprintf($txt['upgrade_completed_time_s'], $seconds), '' . "\n";
450
	}
451
452
	// Bang - gone!
453
	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...
454
}
455
456
// Load the list of language files, and the current language file.
457
function load_lang_file()
458
{
459
	global $txt, $upcontext, $language, $modSettings;
460
461
	static $lang_dir = '', $detected_languages = array(), $loaded_langfile = '';
462
463
	// Do we know where to look for the language files, or shall we just guess for now?
464
	$temp = isset($modSettings['theme_dir']) ? $modSettings['theme_dir'] . '/languages' : dirname(__FILE__) . '/Themes/default/languages';
465
466
	if ($lang_dir != $temp)
467
	{
468
		$lang_dir = $temp;
469
		$detected_languages = array();
470
	}
471
472
	// Override the language file?
473
	if (isset($upcontext['language']))
474
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['language'] . '.php';
475
	elseif (isset($upcontext['lang']))
476
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['lang'] . '.php';
477
	elseif (isset($language))
478
		$_SESSION['upgrader_langfile'] = 'Install.' . $language . '.php';
479
480
	// Avoid pointless repetition
481
	if (isset($_SESSION['upgrader_langfile']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_langfile'])
482
		return;
483
484
	// Now try to find the language files
485
	if (empty($detected_languages))
486
	{
487
		// Make sure the languages directory actually exists.
488
		if (file_exists($lang_dir))
489
		{
490
			// Find all the "Install" language files in the directory.
491
			$dir = dir($lang_dir);
492
			while ($entry = $dir->read())
493
			{
494
				// Skip any old '-utf8' language files that might be lying around
495
				if (strpos($entry, '-utf8') !== false)
496
					continue;
497
498
				if (substr($entry, 0, 8) == 'Install.' && substr($entry, -4) == '.php')
499
					$detected_languages[$entry] = ucfirst(substr($entry, 8, strlen($entry) - 12));
500
			}
501
			$dir->close();
502
		}
503
		// Our guess was wrong, but that's fine. We'll try again after $modSettings['theme_dir'] is defined.
504
		elseif (!isset($modSettings['theme_dir']))
505
		{
506
			// Define a few essential strings for now.
507
			$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.';
508
			$txt['error_sourcefile_missing'] = 'Unable to find the Sources/%1$s file. Please make sure it was uploaded properly, and then try again.';
509
510
			$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.';
511
			$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.';
512
513
			return;
514
		}
515
	}
516
517
	// Didn't find any, show an error message!
518
	if (empty($detected_languages))
519
	{
520
		$from = explode('/', $_SERVER['PHP_SELF']);
521
		$to = explode('/', $lang_dir);
522
		$relPath = $to;
523
524
		foreach($from as $depth => $dir)
525
		{
526
			if ($dir === $to[$depth])
527
				array_shift($relPath);
528
			else
529
			{
530
				$remaining = count($from) - $depth;
531
				if ($remaining > 1)
532
				{
533
					$padLength = (count($relPath) + $remaining - 1) * -1;
534
					$relPath = array_pad($relPath, $padLength, '..');
535
					break;
536
				}
537
				else
538
					$relPath[0] = './' . $relPath[0];
539
			}
540
		}
541
		$relPath = implode(DIRECTORY_SEPARATOR, $relPath);
542
543
		// Let's not cache this message, eh?
544
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
545
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
546
		header('Cache-Control: no-cache');
547
548
		echo '<!DOCTYPE html>
549
			<html>
550
				<head>
551
					<title>SMF Upgrader: Error!</title>
552
						<style>
553
							body {
554
								font-family: sans-serif;
555
								max-width: 700px; }
556
557
								h1 {
558
									font-size: 14pt; }
559
560
								.directory {
561
									margin: 0.3em;
562
									font-family: monospace;
563
									font-weight: bold; }
564
						</style>
565
				</head>
566
				<body>
567
					<h1>A critical error has occurred.</h1>
568
						<p>This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:</p>
569
						<div class="directory">', $relPath, '</div>
570
						<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>
571
						<p>If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.</p>
572
						<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>
573
				</body>
574
			</html>';
575
		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...
576
	}
577
578
	// Make sure it exists. If it doesn't, reset it.
579
	if (!isset($_SESSION['upgrader_langfile']) || preg_match('~[^\w.-]~', $_SESSION['upgrader_langfile']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_langfile']))
580
	{
581
		// Use the first one...
582
		list ($_SESSION['upgrader_langfile']) = array_keys($detected_languages);
583
584
		// If we have English and some other language, use the other language.
585
		if ($_SESSION['upgrader_langfile'] == 'Install.english.php' && count($detected_languages) > 1)
586
			list (, $_SESSION['upgrader_langfile']) = array_keys($detected_languages);
587
	}
588
589
	// For backup we load English at first, then the second language will overwrite it.
590
	if ($_SESSION['upgrader_langfile'] != 'Install.english.php')
591
		require_once($lang_dir . '/Install.english.php');
592
593
	// And now include the actual language file itself.
594
	require_once($lang_dir . '/' . $_SESSION['upgrader_langfile']);
595
596
	// Remember what we've done
597
	$loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_langfile'];
598
}
599
600
// Used to direct the user to another location.
601
function redirectLocation($location, $addForm = true)
602
{
603
	global $upgradeurl, $upcontext, $command_line;
604
605
	// Command line users can't be redirected.
606
	if ($command_line)
607
		upgradeExit(true);
608
609
	// Are we providing the core info?
610
	if ($addForm)
611
	{
612
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
613
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
614
	}
615
616
	while (@ob_end_clean())
617
		header('location: ' . strtr($location, array('&amp;' => '&')));
618
619
	// Exit - saving status as we go.
620
	upgradeExit(true);
621
}
622
623
// Load all essential data and connect to the DB as this is pre SSI.php
624
function loadEssentialData()
625
{
626
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection;
627
	global $db_prefix, $db_character_set, $db_type, $db_port, $db_show_debug;
628
	global $db_mb4, $modSettings, $sourcedir, $smcFunc, $txt, $utf8;
629
630
	// Report all errors if admin wants them or this is a pre-release version.
631
	if (!empty($db_show_debug) || strspn(SMF_VERSION, '1234567890.') !== strlen(SMF_VERSION))
632
		error_reporting(E_ALL);
633
	// Otherwise, report all errors except for deprecation notices.
634
	else
635
		error_reporting(E_ALL & ~E_DEPRECATED);
636
637
638
	define('SMF', 1);
639
	header('X-Frame-Options: SAMEORIGIN');
640
	header('X-XSS-Protection: 1');
641
	header('X-Content-Type-Options: nosniff');
642
643
	// Start the session.
644
	if (@ini_get('session.save_handler') == 'user')
645
		@ini_set('session.save_handler', 'files');
646
	@session_start();
647
648
	if (empty($smcFunc))
649
		$smcFunc = array();
650
651
	require_once($sourcedir . '/Subs.php');
652
653
	$smcFunc['random_int'] = function($min = 0, $max = PHP_INT_MAX)
654
	{
655
		global $sourcedir;
656
657
		// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
658
		if (!is_callable('random_int'))
659
			require_once($sourcedir . '/random_compat/random.php');
660
661
		return random_int($min, $max);
662
	};
663
664
	// We need this for authentication and some upgrade code
665
	require_once($sourcedir . '/Subs-Auth.php');
666
	require_once($sourcedir . '/Class-Package.php');
667
668
	$smcFunc['strtolower'] = 'smf_strtolower';
669
670
	// Initialize everything...
671
	initialize_inputs();
672
673
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
674
675
	// Get the database going!
676
	if (empty($db_type) || $db_type == 'mysqli')
677
	{
678
		$db_type = 'mysql';
679
		// If overriding $db_type, need to set its settings.php entry too
680
		$changes = array();
681
		$changes['db_type'] = 'mysql';
682
		require_once($sourcedir . '/Subs-Admin.php');
683
		updateSettingsFile($changes);
684
	}
685
686
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
687
	{
688
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
689
690
		// Make the connection...
691
		if (empty($db_connection))
692
		{
693
			$options = array('non_fatal' => true);
694
			// Add in the port if needed
695
			if (!empty($db_port))
696
				$options['port'] = $db_port;
697
698
			if (!empty($db_mb4))
699
				$options['db_mb4'] = $db_mb4;
700
701
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
702
		}
703
		else
704
			// If we've returned here, ping/reconnect to be safe
705
			$smcFunc['db_ping']($db_connection);
706
707
		// Oh dear god!!
708
		if ($db_connection === null)
709
			die($txt['error_db_connect_settings']);
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...
710
711
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
712
			$smcFunc['db_query']('', '
713
				SET NAMES {string:db_character_set}',
714
				array(
715
					'db_error_skip' => true,
716
					'db_character_set' => $db_character_set,
717
				)
718
			);
719
720
		// Load the modSettings data...
721
		$request = $smcFunc['db_query']('', '
722
			SELECT variable, value
723
			FROM {db_prefix}settings',
724
			array(
725
				'db_error_skip' => true,
726
			)
727
		);
728
		$modSettings = array();
729
		while ($row = $smcFunc['db_fetch_assoc']($request))
730
			$modSettings[$row['variable']] = $row['value'];
731
		$smcFunc['db_free_result']($request);
732
	}
733
	else
734
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
735
736
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
737
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
738
	{
739
		require_once($sourcedir . '/QueryString.php');
740
		cleanRequest();
741
	}
742
743
	if (!isset($_GET['substep']))
744
		$_GET['substep'] = 0;
745
}
746
747
function initialize_inputs()
748
{
749
	global $start_time, $db_type;
750
751
	$start_time = time();
752
753
	umask(0);
754
755
	ob_start();
756
757
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
758
	ignore_user_abort(true);
759
760
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
761
	if (isset($_GET['delete']))
762
	{
763
		@unlink(__FILE__);
764
765
		// And the extra little files ;).
766
		@unlink(dirname(__FILE__) . '/upgrade_1-0.sql');
767
		@unlink(dirname(__FILE__) . '/upgrade_1-1.sql');
768
		@unlink(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
769
		@unlink(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
770
		@unlink(dirname(__FILE__) . '/upgrade-helper.php');
771
772
		$dh = opendir(dirname(__FILE__));
773
		while ($file = readdir($dh))
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, 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

773
		while ($file = readdir(/** @scrutinizer ignore-type */ $dh))
Loading history...
774
		{
775
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
776
				@unlink(dirname(__FILE__) . '/' . $file);
777
		}
778
		closedir($dh);
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of closedir() does only seem to accept resource, 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

778
		closedir(/** @scrutinizer ignore-type */ $dh);
Loading history...
779
780
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
781
		// 1.1 Sources files not in 2.0+
782
		@unlink(dirname(__FILE__) . '/Sources/ModSettings.php');
783
		// 1.1 Templates that don't exist any more (e.g. renamed)
784
		@unlink(dirname(__FILE__) . '/Themes/default/Combat.template.php');
785
		@unlink(dirname(__FILE__) . '/Themes/default/Modlog.template.php');
786
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
787
		@unlink(dirname(__FILE__) . '/Themes/default/fader.js');
788
		@unlink(dirname(__FILE__) . '/Themes/default/script.js');
789
		@unlink(dirname(__FILE__) . '/Themes/default/spellcheck.js');
790
		@unlink(dirname(__FILE__) . '/Themes/default/xml_board.js');
791
		@unlink(dirname(__FILE__) . '/Themes/default/xml_topic.js');
792
793
		// 2.0 Sources files not in 2.1+
794
		@unlink(dirname(__FILE__) . '/Sources/DumpDatabase.php');
795
		@unlink(dirname(__FILE__) . '/Sources/LockTopic.php');
796
797
		header('location: http://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
798
		exit;
799
	}
800
801
	// Something is causing this to happen, and it's annoying.  Stop it.
802
	$temp = 'upgrade_php?step';
803
	while (strlen($temp) > 4)
804
	{
805
		if (isset($_GET[$temp]))
806
			unset($_GET[$temp]);
807
		$temp = substr($temp, 1);
808
	}
809
810
	// Force a step, defaulting to 0.
811
	$_GET['step'] = (int) @$_GET['step'];
812
	$_GET['substep'] = (int) @$_GET['substep'];
813
}
814
815
// Step 0 - Let's welcome them in and ask them to login!
816
function WelcomeLogin()
817
{
818
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
819
	global $smcFunc, $db_type, $databases, $boardurl;
820
821
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
822
	global $txt;
823
824
	$upcontext['sub_template'] = 'welcome_message';
825
826
	// Check for some key files - one template, one language, and a new and an old source file.
827
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
828
		&& @file_exists($sourcedir . '/QueryString.php')
829
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
830
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
831
832
	// Need legacy scripts?
833
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
834
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
835
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
836
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
837
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
838
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
839
840
	// We don't need "-utf8" files anymore...
841
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
842
843
	if (!$check)
844
		// 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.
845
		return throw_error($txt['error_upgrade_files_missing']);
846
847
	// Do they meet the install requirements?
848
	if (!php_version_check())
849
		return throw_error($txt['error_php_too_low']);
850
851
	if (!db_version_check())
852
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
853
854
	// Do some checks to make sure they have proper privileges
855
	db_extend('packages');
856
857
	// CREATE
858
	$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');
859
860
	// ALTER
861
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
862
863
	// DROP
864
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
865
866
	// Sorry... we need CREATE, ALTER and DROP
867
	if (!$create || !$alter || !$drop)
868
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
869
870
	// Do a quick version spot check.
871
	$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

871
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
872
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
873
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
874
		return throw_error($txt['error_upgrade_old_files']);
875
876
	// What absolutely needs to be writable?
877
	$writable_files = array(
878
		$boarddir . '/Settings.php',
879
		$boarddir . '/Settings_bak.php',
880
	);
881
882
	// Only check for minified writable files if we have it enabled or not set.
883
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
884
		$writable_files += array(
885
			$modSettings['theme_dir'] . '/css/minified.css',
886
			$modSettings['theme_dir'] . '/scripts/minified.js',
887
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
888
		);
889
890
	// Do we need to add this setting?
891
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
892
893
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
894
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
895
896
	// This little fellow has to cooperate...
897
	quickFileWritable($custom_av_dir);
898
899
	// Are we good now?
900
	if (!is_writable($custom_av_dir))
901
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
902
	elseif ($need_settings_update)
903
	{
904
		if (!function_exists('cache_put_data'))
905
			require_once($sourcedir . '/Load.php');
906
907
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
908
		updateSettings(array('custom_avatar_url' => $custom_av_url));
909
	}
910
911
	require_once($sourcedir . '/Security.php');
912
913
	// Check the cache directory.
914
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
915
	if (!file_exists($cachedir_temp))
916
		@mkdir($cachedir_temp);
917
918
	if (!file_exists($cachedir_temp))
919
		return throw_error($txt['error_cache_not_found']);
920
921
	quickFileWritable($cachedir_temp . '/db_last_error.php');
922
923
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
924
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
925
	elseif (!isset($_GET['skiplang']))
926
	{
927
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
928
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
929
930
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
931
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
932
	}
933
934
	if (!makeFilesWritable($writable_files))
935
		return false;
936
937
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
938
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
939
		return throw_error($txt['error_agreement_not_writable']);
940
941
	// Upgrade the agreement.
942
	elseif (isset($modSettings['agreement']))
943
	{
944
		$fp = fopen($boarddir . '/agreement.txt', 'w');
945
		fwrite($fp, $modSettings['agreement']);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, 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

945
		fwrite(/** @scrutinizer ignore-type */ $fp, $modSettings['agreement']);
Loading history...
946
		fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

946
		fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
947
	}
948
949
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
950
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr(dirname(__FILE__), array('/' => '', '\\' => '')))
951
		$upcontext['warning'] = '
952
			' . sprintf($txt['upgrade_boarddir_settings'], $boarddir, dirname(__FILE__)) . '<br>
953
			<ul>
954
				<li>' . $txt['upgrade_boarddir'] . '  ' . $boarddir . '</li>
955
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
956
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
957
			</ul>
958
			' . $txt['upgrade_incorrect_settings'] . '';
959
960
	// Confirm mbstring is loaded...
961
	if (!extension_loaded('mbstring'))
962
		return throw_error($txt['install_no_mbstring']);
963
964
	// Check for https stream support.
965
	$supported_streams = stream_get_wrappers();
966
	if (!in_array('https', $supported_streams))
967
		$upcontext['custom_warning'] = $txt['install_no_https'];
968
969
	// Either we're logged in or we're going to present the login.
970
	if (checkLogin())
971
		return true;
972
973
	$upcontext += createToken('login');
974
975
	return false;
976
}
977
978
// Step 0.5: Does the login work?
979
function checkLogin()
980
{
981
	global $modSettings, $upcontext, $disable_security;
982
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
983
984
	// Are we trying to login?
985
	if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security))
986
	{
987
		// If we've disabled security pick a suitable name!
988
		if (empty($_POST['user']))
989
			$_POST['user'] = 'Administrator';
990
991
		// Before 2.0 these column names were different!
992
		$oldDB = false;
993
		if (empty($db_type) || $db_type == 'mysql')
994
		{
995
			$request = $smcFunc['db_query']('', '
996
				SHOW COLUMNS
997
				FROM {db_prefix}members
998
				LIKE {string:member_name}',
999
				array(
1000
					'member_name' => 'memberName',
1001
					'db_error_skip' => true,
1002
				)
1003
			);
1004
			if ($smcFunc['db_num_rows']($request) != 0)
1005
				$oldDB = true;
1006
			$smcFunc['db_free_result']($request);
1007
		}
1008
1009
		// Get what we believe to be their details.
1010
		if (!$disable_security)
1011
		{
1012
			if ($oldDB)
1013
				$request = $smcFunc['db_query']('', '
1014
					SELECT id_member, memberName AS member_name, passwd, id_group,
1015
						additionalGroups AS additional_groups, lngfile
1016
					FROM {db_prefix}members
1017
					WHERE memberName = {string:member_name}',
1018
					array(
1019
						'member_name' => $_POST['user'],
1020
						'db_error_skip' => true,
1021
					)
1022
				);
1023
			else
1024
				$request = $smcFunc['db_query']('', '
1025
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
1026
					FROM {db_prefix}members
1027
					WHERE member_name = {string:member_name}',
1028
					array(
1029
						'member_name' => $_POST['user'],
1030
						'db_error_skip' => true,
1031
					)
1032
				);
1033
			if ($smcFunc['db_num_rows']($request) != 0)
1034
			{
1035
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1036
1037
				$groups = explode(',', $addGroups);
1038
				$groups[] = $id_group;
1039
1040
				foreach ($groups as $k => $v)
1041
					$groups[$k] = (int) $v;
1042
1043
				$sha_passwd = sha1(strtolower($name) . un_htmlspecialchars($_REQUEST['passwrd']));
1044
1045
				// We don't use "-utf8" anymore...
1046
				$user_language = str_ireplace('-utf8', '', $user_language);
1047
			}
1048
			else
1049
				$upcontext['username_incorrect'] = true;
1050
1051
			$smcFunc['db_free_result']($request);
1052
		}
1053
		$upcontext['username'] = $_POST['user'];
1054
1055
		// Track whether javascript works!
1056
		if (isset($_POST['js_works']))
1057
		{
1058
			if (!empty($_POST['js_works']))
1059
			{
1060
				$upcontext['upgrade_status']['js'] = 1;
1061
				$support_js = 1;
1062
			}
1063
			else
1064
				$support_js = 0;
1065
		}
1066
1067
		// Note down the version we are coming from.
1068
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1069
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1070
1071
		// Didn't get anywhere?
1072
		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']))
1073
		{
1074
			// MD5?
1075
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1076
			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...
1077
			{
1078
				$upcontext['password_failed'] = true;
1079
				// Disable the hashing this time.
1080
				$upcontext['disable_login_hashing'] = true;
1081
			}
1082
		}
1083
1084
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1085
		{
1086
			// Set the password.
1087
			if (!$disable_security)
1088
			{
1089
				// Do we actually have permission?
1090
				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...
1091
				{
1092
					$request = $smcFunc['db_query']('', '
1093
						SELECT permission
1094
						FROM {db_prefix}permissions
1095
						WHERE id_group IN ({array_int:groups})
1096
							AND permission = {string:admin_forum}',
1097
						array(
1098
							'groups' => $groups,
1099
							'admin_forum' => 'admin_forum',
1100
							'db_error_skip' => true,
1101
						)
1102
					);
1103
					if ($smcFunc['db_num_rows']($request) == 0)
1104
						return throw_error($txt['error_not_admin']);
1105
					$smcFunc['db_free_result']($request);
1106
				}
1107
1108
				$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...
1109
				$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...
1110
			}
1111
			else
1112
			{
1113
				$upcontext['user']['id'] = 1;
1114
				$upcontext['user']['name'] = 'Administrator';
1115
			}
1116
1117
			if (!is_callable('random_int'))
1118
				require_once('Sources/random_compat/random.php');
1119
1120
			$upcontext['user']['pass'] = random_int(0, 60000);
1121
			// This basically is used to match the GET variables to Settings.php.
1122
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1123
1124
			// Set the language to that of the user?
1125
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1126
			{
1127
				$user_language = basename($user_language, '.lng');
1128
				$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

1128
				$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
Loading history...
1129
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1130
1131
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1132
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1133
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1134
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1135
				else
1136
				{
1137
					// Set this as the new language.
1138
					$upcontext['language'] = $user_language;
1139
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1140
1141
					// Include the file.
1142
					load_lang_file();
1143
				}
1144
			}
1145
1146
			// If we're resuming set the step and substep to be correct.
1147
			if (isset($_POST['cont']))
1148
			{
1149
				$upcontext['current_step'] = $upcontext['user']['step'];
1150
				$_GET['substep'] = $upcontext['user']['substep'];
1151
			}
1152
1153
			return true;
1154
		}
1155
	}
1156
1157
	return false;
1158
}
1159
1160
// Step 1: Do the maintenance and backup.
1161
function UpgradeOptions()
1162
{
1163
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1164
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1165
1166
	$upcontext['sub_template'] = 'upgrade_options';
1167
	$upcontext['page_title'] = $txt['upgrade_options'];
1168
1169
	db_extend('packages');
1170
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1171
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1172
1173
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1174
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1175
1176
	$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', '<');
1177
1178
	unset($member_columns);
1179
1180
	// If we've not submitted then we're done.
1181
	if (empty($_POST['upcont']))
1182
		return false;
1183
1184
	// Firstly, if they're enabling SM stat collection just do it.
1185
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1186
	{
1187
		$upcontext['allow_sm_stats'] = true;
1188
1189
		// Don't register if we still have a key.
1190
		if (empty($modSettings['sm_stats_key']))
1191
		{
1192
			// Attempt to register the site etc.
1193
			$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1194
			if ($fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1195
			{
1196
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1197
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1198
				$out .= 'Connection: Close' . "\r\n\r\n";
1199
				fwrite($fp, $out);
1200
1201
				$return_data = '';
1202
				while (!feof($fp))
1203
					$return_data .= fgets($fp, 128);
1204
1205
				fclose($fp);
1206
1207
				// Get the unique site ID.
1208
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1209
1210
				if (!empty($ID[1]))
1211
					$smcFunc['db_insert']('replace',
1212
						$db_prefix . 'settings',
1213
						array('variable' => 'string', 'value' => 'string'),
1214
						array(
1215
							array('sm_stats_key', $ID[1]),
1216
							array('enable_sm_stats', 1),
1217
						),
1218
						array('variable')
1219
					);
1220
			}
1221
		}
1222
		else
1223
		{
1224
			$smcFunc['db_insert']('replace',
1225
				$db_prefix . 'settings',
1226
				array('variable' => 'string', 'value' => 'string'),
1227
				array('enable_sm_stats', 1),
1228
				array('variable')
1229
			);
1230
		}
1231
	}
1232
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1233
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1234
		$smcFunc['db_query']('', '
1235
			DELETE FROM {db_prefix}settings
1236
			WHERE variable = {string:enable_sm_stats}',
1237
			array(
1238
				'enable_sm_stats' => 'enable_sm_stats',
1239
				'db_error_skip' => true,
1240
			)
1241
		);
1242
1243
	// Deleting old karma stuff?
1244
	if (!empty($_POST['delete_karma']))
1245
	{
1246
		// Delete old settings vars.
1247
		$smcFunc['db_query']('', '
1248
			DELETE FROM {db_prefix}settings
1249
			WHERE variable IN ({array_string:karma_vars})',
1250
			array(
1251
				'karma_vars' => array('karmaMode', 'karmaTimeRestrictAdmins', 'karmaWaitTime', 'karmaMinPosts', 'karmaLabel', 'karmaSmiteLabel', 'karmaApplaudLabel'),
1252
			)
1253
		);
1254
1255
		// Cleaning up old karma member settings.
1256
		if ($upcontext['karma_installed']['good'])
1257
			$smcFunc['db_query']('', '
1258
				ALTER TABLE {db_prefix}members
1259
				DROP karma_good',
1260
				array()
1261
			);
1262
1263
		// Does karma bad was enable?
1264
		if ($upcontext['karma_installed']['bad'])
1265
			$smcFunc['db_query']('', '
1266
				ALTER TABLE {db_prefix}members
1267
				DROP karma_bad',
1268
				array()
1269
			);
1270
1271
		// Cleaning up old karma permissions.
1272
		$smcFunc['db_query']('', '
1273
			DELETE FROM {db_prefix}permissions
1274
			WHERE permission = {string:karma_vars}',
1275
			array(
1276
				'karma_vars' => 'karma_edit',
1277
			)
1278
		);
1279
		// Cleaning up old log_karma table
1280
		$smcFunc['db_query']('', '
1281
			DROP TABLE IF EXISTS {db_prefix}log_karma',
1282
			array()
1283
		);
1284
	}
1285
1286
	// Emptying the error log?
1287
	if (!empty($_POST['empty_error']))
1288
		$smcFunc['db_query']('truncate_table', '
1289
			TRUNCATE {db_prefix}log_errors',
1290
			array(
1291
			)
1292
		);
1293
1294
	$changes = array();
1295
1296
	// Add proxy settings.
1297
	if (!isset($GLOBALS['image_proxy_secret']) || $GLOBALS['image_proxy_secret'] == 'smfisawesome')
1298
		$changes['image_proxy_secret'] = substr(sha1(mt_rand()), 0, 20);
1299
	if (!isset($GLOBALS['image_proxy_maxsize']))
1300
		$changes['image_proxy_maxsize'] = 5190;
1301
	if (!isset($GLOBALS['image_proxy_enabled']))
1302
		$changes['image_proxy_enabled'] = false;
1303
1304
	// If $boardurl reflects https, set force_ssl
1305
	if (!function_exists('cache_put_data'))
1306
		require_once($sourcedir . '/Load.php');
1307
	if (stripos($boardurl, 'https://') !== false)
1308
		updateSettings(array('force_ssl' => '1'));
1309
1310
	// If we're overriding the language follow it through.
1311
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1312
		$changes['language'] = $upcontext['lang'];
1313
1314
	if (!empty($_POST['maint']))
1315
	{
1316
		$changes['maintenance'] = 2;
1317
		// Remember what it was...
1318
		$upcontext['user']['main'] = $maintenance;
1319
1320
		if (!empty($_POST['maintitle']))
1321
		{
1322
			$changes['mtitle'] = $_POST['maintitle'];
1323
			$changes['mmessage'] = $_POST['mainmessage'];
1324
		}
1325
		else
1326
		{
1327
			$changes['mtitle'] = $txt['mtitle'];
1328
			$changes['mmessage'] = $txt['mmessage'];
1329
		}
1330
	}
1331
1332
	if ($command_line)
1333
		echo ' * Updating Settings.php...';
1334
1335
	// Fix some old paths.
1336
	if (substr($boarddir, 0, 1) == '.')
1337
		$changes['boarddir'] = fixRelativePath($boarddir);
1338
1339
	if (substr($sourcedir, 0, 1) == '.')
1340
		$changes['sourcedir'] = fixRelativePath($sourcedir);
1341
1342
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1343
		$changes['cachedir'] = fixRelativePath($boarddir) . '/cache';
1344
1345
	// Migrate cache settings.
1346
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1347
	if (!isset($GLOBALS['cache_enable']))
1348
		$changes += array(
1349
			'cache_accelerator' => !empty($modSettings['cache_enable']) ? 'smf' : '',
1350
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1351
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? $modSettings['cache_memcached'] : '',
1352
		);
1353
1354
	// If they have a "host:port" setup for the host, split that into separate values
1355
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1356
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1357
	{
1358
		list ($db_server, $db_port) = explode(':', $db_server);
1359
1360
		$changes['db_server'] = $db_server;
1361
1362
		// Only set this if we're not using the default port
1363
		if ($db_port != ini_get('mysqli.default_port'))
1364
			$changes['db_port'] = (int) $db_port;
1365
	}
1366
1367
	// If db_port is set and is the same as the default, set it to 0.
1368
	if (!empty($db_port))
1369
	{
1370
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1371
			$changes['db_port'] = 0;
1372
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1373
			$changes['db_port'] = 0;
1374
	}
1375
1376
	// Maybe we haven't had this option yet?
1377
	if (empty($packagesdir))
1378
		$changes['packagesdir'] = fixRelativePath($boarddir) . '/Packages';
1379
1380
	// Add support for $tasksdir var.
1381
	if (empty($tasksdir))
1382
		$changes['tasksdir'] = fixRelativePath($sourcedir) . '/tasks';
1383
1384
	// Make sure we fix the language as well.
1385
	if (stristr($language, '-utf8'))
1386
		$changes['language'] = str_ireplace('-utf8', '', $language);
1387
1388
	// @todo Maybe change the cookie name if going to 1.1, too?
1389
1390
	// Ensure this doesn't get lost in translation.
1391
	$changes['upgradeData'] = base64_encode(json_encode($upcontext['user']));
1392
1393
	// Update Settings.php with the new settings, and rebuild if they selected that option.
1394
	require_once($sourcedir . '/Subs.php');
1395
	require_once($sourcedir . '/Subs-Admin.php');
1396
	updateSettingsFile($changes, false, !empty($_POST['migrateSettings']));
1397
1398
	if ($command_line)
1399
		echo ' Successful.' . "\n";
1400
1401
	// Are we doing debug?
1402
	if (isset($_POST['debug']))
1403
	{
1404
		$upcontext['upgrade_status']['debug'] = true;
1405
		$is_debug = true;
1406
	}
1407
1408
	// If we're not backing up then jump one.
1409
	if (empty($_POST['backup']))
1410
		$upcontext['current_step']++;
1411
1412
	// If we've got here then let's proceed to the next step!
1413
	return true;
1414
}
1415
1416
// Backup the database - why not...
1417
function BackupDatabase()
1418
{
1419
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1420
1421
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1422
	$upcontext['page_title'] = $txt['backup_database'];
1423
1424
	// Done it already - js wise?
1425
	if (!empty($_POST['backup_done']))
1426
		return true;
1427
1428
	// Some useful stuff here.
1429
	db_extend();
1430
1431
	// Might need this as well
1432
	db_extend('packages');
1433
1434
	// Get all the table names.
1435
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1436
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1437
	$tables = $smcFunc['db_list_tables']($db, $filter);
1438
1439
	$table_names = array();
1440
	foreach ($tables as $table)
1441
		if (substr($table, 0, 7) !== 'backup_')
1442
			$table_names[] = $table;
1443
1444
	$upcontext['table_count'] = count($table_names);
1445
	$upcontext['cur_table_num'] = $_GET['substep'];
1446
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1447
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1448
	// For non-java auto submit...
1449
	$file_steps = $upcontext['table_count'];
1450
1451
	// What ones have we already done?
1452
	foreach ($table_names as $id => $table)
1453
		if ($id < $_GET['substep'])
1454
			$upcontext['previous_tables'][] = $table;
1455
1456
	if ($command_line)
1457
		echo 'Backing Up Tables.';
1458
1459
	// If we don't support javascript we backup here.
1460
	if (!$support_js || isset($_GET['xml']))
1461
	{
1462
		// Backup each table!
1463
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1464
		{
1465
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1466
			$upcontext['cur_table_num'] = $substep + 1;
1467
1468
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1469
1470
			// Do we need to pause?
1471
			nextSubstep($substep);
1472
1473
			backupTable($table_names[$substep]);
1474
1475
			// If this is XML to keep it nice for the user do one table at a time anyway!
1476
			if (isset($_GET['xml']))
1477
				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...
1478
		}
1479
1480
		if ($command_line)
1481
		{
1482
			echo "\n" . ' Successful.\'' . "\n";
1483
			flush();
1484
		}
1485
		$upcontext['step_progress'] = 100;
1486
1487
		$_GET['substep'] = 0;
1488
		// Make sure we move on!
1489
		return true;
1490
	}
1491
1492
	// Either way next place to post will be database changes!
1493
	$_GET['substep'] = 0;
1494
	return false;
1495
}
1496
1497
// Backup one table...
1498
function backupTable($table)
1499
{
1500
	global $command_line, $db_prefix, $smcFunc;
1501
1502
	if ($command_line)
1503
	{
1504
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1505
		flush();
1506
	}
1507
1508
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1509
1510
	if ($command_line)
1511
		echo ' done.';
1512
}
1513
1514
// Step 2: Everything.
1515
function DatabaseChanges()
1516
{
1517
	global $db_prefix, $modSettings, $smcFunc, $txt;
1518
	global $upcontext, $support_js, $db_type;
1519
1520
	// Have we just completed this?
1521
	if (!empty($_POST['database_done']))
1522
		return true;
1523
1524
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1525
	$upcontext['page_title'] = $txt['database_changes'];
1526
1527
	// All possible files.
1528
	// Name, < version, insert_on_complete
1529
	// Last entry in array indicates whether to use sql_mode of STRICT or not.
1530
	$files = array(
1531
		array('upgrade_1-0.sql', '1.1', '1.1 RC0', false),
1532
		array('upgrade_1-1.sql', '2.0', '2.0 a', false),
1533
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0', false),
1534
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION, true),
1535
	);
1536
1537
	// How many files are there in total?
1538
	if (isset($_GET['filecount']))
1539
		$upcontext['file_count'] = (int) $_GET['filecount'];
1540
	else
1541
	{
1542
		$upcontext['file_count'] = 0;
1543
		foreach ($files as $file)
1544
		{
1545
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1546
				$upcontext['file_count']++;
1547
		}
1548
	}
1549
1550
	// Do each file!
1551
	$did_not_do = count($files) - $upcontext['file_count'];
1552
	$upcontext['step_progress'] = 0;
1553
	$upcontext['cur_file_num'] = 0;
1554
	foreach ($files as $file)
1555
	{
1556
		if ($did_not_do)
1557
			$did_not_do--;
1558
		else
1559
		{
1560
			$upcontext['cur_file_num']++;
1561
			$upcontext['cur_file_name'] = $file[0];
1562
			// Do we actually need to do this still?
1563
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1564
			{
1565
				setSqlMode($file[3]);
1566
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1567
				if ($nextFile)
1568
				{
1569
					// Only update the version of this if complete.
1570
					$smcFunc['db_insert']('replace',
1571
						$db_prefix . 'settings',
1572
						array('variable' => 'string', 'value' => 'string'),
1573
						array('smfVersion', $file[2]),
1574
						array('variable')
1575
					);
1576
1577
					$modSettings['smfVersion'] = $file[2];
1578
				}
1579
1580
				// If this is XML we only do this stuff once.
1581
				if (isset($_GET['xml']))
1582
				{
1583
					// Flag to move on to the next.
1584
					$upcontext['completed_step'] = true;
1585
					// Did we complete the whole file?
1586
					if ($nextFile)
1587
						$upcontext['current_debug_item_num'] = -1;
1588
					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...
1589
				}
1590
				elseif ($support_js)
1591
					break;
1592
			}
1593
			// Set the progress bar to be right as if we had - even if we hadn't...
1594
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1595
		}
1596
	}
1597
1598
	$_GET['substep'] = 0;
1599
	// So the template knows we're done.
1600
	if (!$support_js)
1601
	{
1602
		$upcontext['changes_complete'] = true;
1603
1604
		return true;
1605
	}
1606
	return false;
1607
}
1608
1609
// Different versions of the files use different sql_modes
1610
function setSqlMode($strict = true)
1611
{
1612
	global $db_type, $db_connection;
1613
1614
	if ($db_type != 'mysql')
1615
		return;
1616
1617
	if ($strict)
1618
		$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';
1619
	else
1620
		$mode = '';
1621
1622
	mysqli_query($db_connection, 'SET SESSION sql_mode = \'' . $mode . '\'');
1623
1624
	return;
1625
}
1626
1627
// Delete the damn thing!
1628
function DeleteUpgrade()
1629
{
1630
	global $command_line, $language, $upcontext, $sourcedir;
1631
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1632
1633
	// Now it's nice to have some of the basic SMF source files.
1634
	if (!isset($_GET['ssi']) && !$command_line)
1635
		redirectLocation('&ssi=1');
1636
1637
	$upcontext['sub_template'] = 'upgrade_complete';
1638
	$upcontext['page_title'] = $txt['upgrade_complete'];
1639
1640
	$endl = $command_line ? "\n" : '<br>' . "\n";
1641
1642
	$changes = array(
1643
		'language' => (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language),
1644
		'db_error_send' => true,
1645
		'upgradeData' => null,
1646
	);
1647
1648
	// Are we in maintenance mode?
1649
	if (isset($upcontext['user']['main']))
1650
	{
1651
		if ($command_line)
1652
			echo ' * ';
1653
		$upcontext['removed_maintenance'] = true;
1654
		$changes['maintenance'] = $upcontext['user']['main'];
1655
	}
1656
	// Otherwise if somehow we are in 2 let's go to 1.
1657
	elseif (!empty($maintenance) && $maintenance == 2)
1658
		$changes['maintenance'] = 1;
1659
1660
	// Wipe this out...
1661
	$upcontext['user'] = array();
1662
1663
	require_once($sourcedir . '/Subs.php');
1664
	require_once($sourcedir . '/Subs-Admin.php');
1665
	updateSettingsFile($changes);
1666
1667
	// Clean any old cache files away.
1668
	upgrade_clean_cache();
1669
1670
	// Can we delete the file?
1671
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1672
1673
	// Now is the perfect time to fetch the SM files.
1674
	if ($command_line)
1675
		cli_scheduled_fetchSMfiles();
1676
	else
1677
	{
1678
		require_once($sourcedir . '/ScheduledTasks.php');
1679
		scheduled_fetchSMfiles(); // Now go get those files!
1680
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1681
		if (httpsOn())
1682
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1683
	}
1684
1685
	// Log what we've done.
1686
	if (empty($user_info['id']))
1687
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1688
1689
	// Log the action manually, so CLI still works.
1690
	$smcFunc['db_insert']('',
1691
		'{db_prefix}log_actions',
1692
		array(
1693
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1694
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1695
		),
1696
		array(
1697
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1698
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1699
		),
1700
		array('id_action')
1701
	);
1702
	$user_info['id'] = 0;
1703
1704
	if ($command_line)
1705
	{
1706
		echo $endl;
1707
		echo 'Upgrade Complete!', $endl;
1708
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1709
		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...
1710
	}
1711
1712
	// Make sure it says we're done.
1713
	$upcontext['overall_percent'] = 100;
1714
	if (isset($upcontext['step_progress']))
1715
		unset($upcontext['step_progress']);
1716
1717
	$_GET['substep'] = 0;
1718
	return false;
1719
}
1720
1721
// Just like the built in one, but setup for CLI to not use themes.
1722
function cli_scheduled_fetchSMfiles()
1723
{
1724
	global $sourcedir, $language, $modSettings, $smcFunc;
1725
1726
	if (empty($modSettings['time_format']))
1727
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1728
1729
	// What files do we want to get
1730
	$request = $smcFunc['db_query']('', '
1731
		SELECT id_file, filename, path, parameters
1732
		FROM {db_prefix}admin_info_files',
1733
		array(
1734
		)
1735
	);
1736
1737
	$js_files = array();
1738
	while ($row = $smcFunc['db_fetch_assoc']($request))
1739
	{
1740
		$js_files[$row['id_file']] = array(
1741
			'filename' => $row['filename'],
1742
			'path' => $row['path'],
1743
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1744
		);
1745
	}
1746
	$smcFunc['db_free_result']($request);
1747
1748
	// We're gonna need fetch_web_data() to pull this off.
1749
	require_once($sourcedir . '/Subs.php');
1750
1751
	foreach ($js_files as $ID_FILE => $file)
1752
	{
1753
		// Create the url
1754
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
1755
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1756
1757
		// Get the file
1758
		$file_data = fetch_web_data($url);
1759
1760
		// If we got an error - give up - the site might be down.
1761
		if ($file_data === false)
1762
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
1763
1764
		// Save the file to the database.
1765
		$smcFunc['db_query']('substring', '
1766
			UPDATE {db_prefix}admin_info_files
1767
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1768
			WHERE id_file = {int:id_file}',
1769
			array(
1770
				'id_file' => $ID_FILE,
1771
				'file_data' => $file_data,
1772
			)
1773
		);
1774
	}
1775
	return true;
1776
}
1777
1778
function convertSettingsToTheme()
1779
{
1780
	global $db_prefix, $modSettings, $smcFunc;
1781
1782
	$values = array(
1783
		'show_latest_member' => @$GLOBALS['showlatestmember'],
1784
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
1785
		'show_modify' => @$GLOBALS['showmodify'],
1786
		'show_user_images' => @$GLOBALS['showuserpic'],
1787
		'show_blurb' => @$GLOBALS['showusertext'],
1788
		'show_gender' => @$GLOBALS['showgenderimage'],
1789
		'show_newsfader' => @$GLOBALS['shownewsfader'],
1790
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
1791
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
1792
		'linktree_link' => @$GLOBALS['curposlinks'],
1793
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
1794
		'show_mark_read' => @$GLOBALS['showmarkread'],
1795
		'newsfader_time' => @$GLOBALS['fadertime'],
1796
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
1797
		'enable_news' => @$GLOBALS['enable_news'],
1798
		'return_to_post' => @$modSettings['returnToPost'],
1799
	);
1800
1801
	$themeData = array();
1802
	foreach ($values as $variable => $value)
1803
	{
1804
		if (!isset($value) || $value === null)
1805
			$value = 0;
1806
1807
		$themeData[] = array(0, 1, $variable, $value);
1808
	}
1809
	if (!empty($themeData))
1810
	{
1811
		$smcFunc['db_insert']('ignore',
1812
			$db_prefix . 'themes',
1813
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
1814
			$themeData,
1815
			array('id_member', 'id_theme', 'variable')
1816
		);
1817
	}
1818
}
1819
1820
// This function only works with MySQL but that's fine as it is only used for v1.0.
1821
function convertSettingstoOptions()
1822
{
1823
	global $modSettings, $smcFunc;
1824
1825
	// Format: new_setting -> old_setting_name.
1826
	$values = array(
1827
		'calendar_start_day' => 'cal_startmonday',
1828
		'view_newest_first' => 'viewNewestFirst',
1829
		'view_newest_pm_first' => 'viewNewestFirst',
1830
	);
1831
1832
	foreach ($values as $variable => $value)
1833
	{
1834
		if (empty($modSettings[$value[0]]))
1835
			continue;
1836
1837
		$smcFunc['db_query']('', '
1838
			INSERT IGNORE INTO {db_prefix}themes
1839
				(id_member, id_theme, variable, value)
1840
			SELECT id_member, 1, {string:variable}, {string:value}
1841
			FROM {db_prefix}members',
1842
			array(
1843
				'variable' => $variable,
1844
				'value' => $modSettings[$value[0]],
1845
				'db_error_skip' => true,
1846
			)
1847
		);
1848
1849
		$smcFunc['db_query']('', '
1850
			INSERT IGNORE INTO {db_prefix}themes
1851
				(id_member, id_theme, variable, value)
1852
			VALUES (-1, 1, {string:variable}, {string:value})',
1853
			array(
1854
				'variable' => $variable,
1855
				'value' => $modSettings[$value[0]],
1856
				'db_error_skip' => true,
1857
			)
1858
		);
1859
	}
1860
}
1861
1862
function php_version_check()
1863
{
1864
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
1865
}
1866
1867
function db_version_check()
1868
{
1869
	global $db_type, $databases;
1870
1871
	$curver = eval($databases[$db_type]['version_check']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1872
	$curver = preg_replace('~\-.+?$~', '', $curver);
1873
1874
	return version_compare($databases[$db_type]['version'], $curver, '<=');
1875
}
1876
1877
function fixRelativePath($path)
1878
{
1879
	global $install_path;
1880
1881
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
1882
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
1883
}
1884
1885
function parse_sql($filename)
1886
{
1887
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
1888
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set;
1889
1890
/*
1891
	Failure allowed on:
1892
		- INSERT INTO but not INSERT IGNORE INTO.
1893
		- UPDATE IGNORE but not UPDATE.
1894
		- ALTER TABLE and ALTER IGNORE TABLE.
1895
		- DROP TABLE.
1896
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
1897
1898
	If a comment...
1899
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
1900
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
1901
		- is only ---#, it is "done." and then a break - only shown in debug.
1902
		- begins with ---{ it is a code block terminating at ---}.
1903
1904
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
1905
1906
	Replaces the following variables:
1907
		- {$boarddir}
1908
		- {$boardurl}
1909
		- {$db_prefix}
1910
		- {$db_collation}
1911
*/
1912
1913
	// May want to use extended functionality.
1914
	db_extend();
1915
	db_extend('packages');
1916
1917
	// Our custom error handler - does nothing but does stop public errors from XML!
1918
	set_error_handler(
1919
		function($errno, $errstr, $errfile, $errline) use ($support_js)
1920
		{
1921
			if ($support_js)
1922
				return true;
1923
			else
1924
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
1925
		}
1926
	);
1927
1928
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
1929
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
1930
	if ($db_type == 'mysql')
1931
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
1932
	else
1933
		$db_collation = '';
1934
1935
	$endl = $command_line ? "\n" : '<br>' . "\n";
1936
1937
	$lines = file($filename);
1938
1939
	$current_type = 'sql';
1940
	$current_data = '';
1941
	$substep = 0;
1942
	$last_step = '';
1943
1944
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
1945
	if (isset($db_character_set) && $db_character_set === 'utf8')
1946
		$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
1947
1948
	// Count the total number of steps within this file - for progress.
1949
	$file_steps = substr_count(implode('', $lines), '---#');
0 ignored issues
show
Bug introduced by
It seems like $lines 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

1949
	$file_steps = substr_count(implode('', /** @scrutinizer ignore-type */ $lines), '---#');
Loading history...
1950
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
1951
	$upcontext['debug_items'] = $file_steps;
1952
	$upcontext['current_item_num'] = 0;
1953
	$upcontext['current_item_name'] = '';
1954
	$upcontext['current_debug_item_num'] = 0;
1955
	$upcontext['current_debug_item_name'] = '';
1956
	// This array keeps a record of what we've done in case java is dead...
1957
	$upcontext['actioned_items'] = array();
1958
1959
	$done_something = false;
1960
1961
	foreach ($lines as $line_number => $line)
1962
	{
1963
		$do_current = $substep >= $_GET['substep'];
1964
1965
		// Get rid of any comments in the beginning of the line...
1966
		if (substr(trim($line), 0, 2) === '/*')
1967
			$line = preg_replace('~/\*.+?\*/~', '', $line);
1968
1969
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
1970
		if ($is_debug && !$support_js && $command_line)
1971
			flush();
1972
1973
		if (trim($line) === '')
1974
			continue;
1975
1976
		if (trim(substr($line, 0, 3)) === '---')
1977
		{
1978
			$type = substr($line, 3, 1);
1979
1980
			// An error??
1981
			if (trim($current_data) != '' && $type !== '}')
1982
			{
1983
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
1984
				if ($command_line)
1985
					echo $upcontext['error_message'];
1986
			}
1987
1988
			if ($type == ' ')
1989
			{
1990
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
1991
				{
1992
					echo ' Successful.', $endl;
1993
					flush();
1994
				}
1995
1996
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
1997
				$upcontext['current_item_num']++;
1998
				$upcontext['current_item_name'] = $last_step;
1999
2000
				if ($do_current)
2001
				{
2002
					$upcontext['actioned_items'][] = $last_step;
2003
					if ($command_line)
2004
						echo ' * ';
2005
2006
					// Starting a new main step in our DB changes, so it's time to reset this.
2007
					$upcontext['skip_db_substeps'] = false;
2008
				}
2009
			}
2010
			elseif ($type == '#')
2011
			{
2012
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
2013
2014
				$upcontext['current_debug_item_num']++;
2015
				if (trim($line) != '---#')
2016
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
2017
2018
				// Have we already done something?
2019
				if (isset($_GET['xml']) && $done_something)
2020
				{
2021
					restore_error_handler();
2022
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
2023
				}
2024
2025
				if ($do_current)
2026
				{
2027
					if (trim($line) == '---#' && $command_line)
2028
						echo ' done.', $endl;
2029
					elseif ($command_line)
2030
						echo ' +++ ', rtrim(substr($line, 4));
2031
					elseif (trim($line) != '---#')
2032
					{
2033
						if ($is_debug)
2034
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
2035
					}
2036
				}
2037
2038
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
2039
				{
2040
					if ($command_line)
2041
						echo ' * ';
2042
					else
2043
						$upcontext['actioned_items'][] = $last_step;
2044
				}
2045
2046
				// Small step - only if we're actually doing stuff.
2047
				if ($do_current)
2048
					nextSubstep(++$substep);
2049
				else
2050
					$substep++;
2051
			}
2052
			elseif ($type == '{')
2053
				$current_type = 'code';
2054
			elseif ($type == '}')
2055
			{
2056
				$current_type = 'sql';
2057
2058
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2059
				{
2060
					$current_data = '';
2061
2062
					// Avoid confusion when skipping something we normally would have done
2063
					if ($do_current)
2064
						$done_something = true;
2065
2066
					continue;
2067
				}
2068
2069
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2070
				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...
2071
				{
2072
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2073
					if ($command_line)
2074
						echo $upcontext['error_message'];
2075
				}
2076
2077
				// Done with code!
2078
				$current_data = '';
2079
				$done_something = true;
2080
			}
2081
2082
			continue;
2083
		}
2084
2085
		$current_data .= $line;
2086
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2087
		{
2088
			if ((!$support_js || isset($_GET['xml'])))
2089
			{
2090
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2091
				{
2092
					$current_data = '';
2093
2094
					if ($do_current)
2095
						$done_something = true;
2096
2097
					continue;
2098
				}
2099
2100
				$current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $boarddir, '{$sboarddir}' => addslashes($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation));
2101
2102
				upgrade_query($current_data);
2103
2104
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2105
				/*
2106
				$result = $smcFunc['db_query']('', $current_data, false, false);
2107
				// Went wrong?
2108
				if (!$result)
2109
				{
2110
					// Bit of a bodge - do we want the error?
2111
					if (!empty($upcontext['return_error']))
2112
					{
2113
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2114
						return false;
2115
					}
2116
				}*/
2117
				$done_something = true;
2118
			}
2119
			$current_data = '';
2120
		}
2121
		// If this is xml based and we're just getting the item name then that's grand.
2122
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2123
		{
2124
			restore_error_handler();
2125
			return false;
2126
		}
2127
2128
		// Clean up by cleaning any step info.
2129
		$step_progress = array();
2130
		$custom_warning = '';
2131
	}
2132
2133
	// Put back the error handler.
2134
	restore_error_handler();
2135
2136
	if ($command_line)
2137
	{
2138
		echo ' Successful.' . "\n";
2139
		flush();
2140
	}
2141
2142
	$_GET['substep'] = 0;
2143
	return true;
2144
}
2145
2146
function upgrade_query($string, $unbuffered = false)
2147
{
2148
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2149
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2150
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2151
2152
	// Get the query result - working around some SMF specific security - just this once!
2153
	$modSettings['disableQueryCheck'] = true;
2154
	$db_unbuffered = $unbuffered;
2155
	$ignore_insert_error = false;
2156
2157
	// If we got an old pg version and use a insert ignore query
2158
	if ($db_type == 'postgresql' && !$smcFunc['db_native_replace']() && strpos($string, 'ON CONFLICT DO NOTHING') !== false)
2159
	{
2160
		$ignore_insert_error = true;
2161
		$string = str_replace('ON CONFLICT DO NOTHING', '', $string);
2162
	}
2163
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2164
	$db_unbuffered = false;
2165
2166
	// Failure?!
2167
	if ($result !== false)
2168
		return $result;
2169
2170
	$db_error_message = $smcFunc['db_error']($db_connection);
2171
	// If MySQL we do something more clever.
2172
	if ($db_type == 'mysql')
2173
	{
2174
		$mysqli_errno = mysqli_errno($db_connection);
2175
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR', 'INSERT IGNO'));
2176
2177
		// Error numbers:
2178
		//    1016: Can't open file '....MYI'
2179
		//    1050: Table already exists.
2180
		//    1054: Unknown column name.
2181
		//    1060: Duplicate column name.
2182
		//    1061: Duplicate key name.
2183
		//    1062: Duplicate entry for unique key.
2184
		//    1068: Multiple primary keys.
2185
		//    1072: Key column '%s' doesn't exist in table.
2186
		//    1091: Can't drop key, doesn't exist.
2187
		//    1146: Table doesn't exist.
2188
		//    2013: Lost connection to server during query.
2189
2190
		if ($mysqli_errno == 1016)
2191
		{
2192
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2193
			{
2194
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2195
				$result = mysqli_query($db_connection, $string);
2196
				if ($result !== false)
2197
					return $result;
2198
			}
2199
		}
2200
		elseif ($mysqli_errno == 2013)
2201
		{
2202
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
2203
			mysqli_select_db($db_connection, $db_name);
0 ignored issues
show
Bug introduced by
It seems like $db_connection can also be of type false; however, parameter $link of mysqli_select_db() does only seem to accept mysqli, 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

2203
			mysqli_select_db(/** @scrutinizer ignore-type */ $db_connection, $db_name);
Loading history...
2204
			if ($db_connection)
2205
			{
2206
				$result = mysqli_query($db_connection, $string);
2207
				if ($result !== false)
2208
					return $result;
2209
			}
2210
		}
2211
		// Duplicate column name... should be okay ;).
2212
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2213
			return false;
2214
		// Duplicate insert... make sure it's the proper type of query ;).
2215
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2216
			return false;
2217
		// Creating an index on a non-existent column.
2218
		elseif ($mysqli_errno == 1072)
2219
			return false;
2220
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2221
			return false;
2222
		// Testing for legacy tables or columns? Needed for 1.0 & 1.1 scripts.
2223
		elseif (in_array($mysqli_errno, array(1054, 1146)) && in_array(substr(trim($string), 0, 7), array('SELECT ', 'SHOW CO')))
2224
			return false;
2225
	}
2226
	// If a table already exists don't go potty.
2227
	else
2228
	{
2229
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2230
		{
2231
			if (strpos($db_error_message, 'exist') !== false)
2232
				return true;
2233
		}
2234
		elseif (strpos(trim($string), 'INSERT ') !== false)
2235
		{
2236
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2237
				return true;
2238
		}
2239
	}
2240
2241
	// Get the query string so we pass everything.
2242
	$query_string = '';
2243
	foreach ($_GET as $k => $v)
2244
		$query_string .= ';' . $k . '=' . $v;
2245
	if (strlen($query_string) != 0)
2246
		$query_string = '?' . substr($query_string, 1);
2247
2248
	if ($command_line)
2249
	{
2250
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2251
		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...
2252
	}
2253
2254
	// Bit of a bodge - do we want the error?
2255
	if (!empty($upcontext['return_error']))
2256
	{
2257
		$upcontext['error_message'] = $db_error_message;
2258
		$upcontext['error_string'] = $string;
2259
		return false;
2260
	}
2261
2262
	// Otherwise we have to display this somewhere appropriate if possible.
2263
	$upcontext['forced_error_message'] = '
2264
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2265
2266
			<div style="margin: 2ex;">
2267
				' . $txt['upgrade_thisquery'] . '
2268
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2269
2270
				' . $txt['upgrade_causerror'] . '
2271
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2272
			</div>
2273
2274
			<form action="' . $upgradeurl . $query_string . '" method="post">
2275
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2276
			</form>
2277
		</div>';
2278
2279
	upgradeExit();
2280
}
2281
2282
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2283
function protected_alter($change, $substep, $is_test = false)
2284
{
2285
	global $db_prefix, $smcFunc;
2286
2287
	db_extend('packages');
2288
2289
	// Firstly, check whether the current index/column exists.
2290
	$found = false;
2291
	if ($change['type'] === 'column')
2292
	{
2293
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2294
		foreach ($columns as $column)
2295
		{
2296
			// Found it?
2297
			if ($column['name'] === $change['name'])
2298
			{
2299
				$found |= true;
2300
				// Do some checks on the data if we have it set.
2301
				if (isset($change['col_type']))
2302
					$found &= $change['col_type'] === $column['type'];
2303
				if (isset($change['null_allowed']))
2304
					$found &= $column['null'] == $change['null_allowed'];
2305
				if (isset($change['default']))
2306
					$found &= $change['default'] === $column['default'];
2307
			}
2308
		}
2309
	}
2310
	elseif ($change['type'] === 'index')
2311
	{
2312
		$request = upgrade_query('
2313
			SHOW INDEX
2314
			FROM ' . $db_prefix . $change['table']);
2315
		if ($request !== false)
2316
		{
2317
			$cur_index = array();
2318
2319
			while ($row = $smcFunc['db_fetch_assoc']($request))
2320
				if ($row['Key_name'] === $change['name'])
2321
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2322
2323
			ksort($cur_index, SORT_NUMERIC);
2324
			$found = array_values($cur_index) === $change['target_columns'];
2325
2326
			$smcFunc['db_free_result']($request);
2327
		}
2328
	}
2329
2330
	// If we're trying to add and it's added, we're done.
2331
	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...
2332
		return true;
2333
	// Otherwise if we're removing and it wasn't found we're also done.
2334
	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...
2335
		return true;
2336
	// Otherwise is it just a test?
2337
	elseif ($is_test)
2338
		return false;
2339
2340
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2341
	$running = false;
2342
	$found = false;
2343
	while (1 == 1)
2344
	{
2345
		$request = upgrade_query('
2346
			SHOW FULL PROCESSLIST');
2347
		while ($row = $smcFunc['db_fetch_assoc']($request))
2348
		{
2349
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2350
				$found = true;
2351
		}
2352
2353
		// Can't find it? Then we need to run it fools!
2354
		if (!$found && !$running)
2355
		{
2356
			$smcFunc['db_free_result']($request);
2357
2358
			$success = upgrade_query('
2359
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2360
				' . $change['text'], true) !== false;
2361
2362
			if (!$success)
2363
				return false;
2364
2365
			// Return
2366
			$running = true;
2367
		}
2368
		// What if we've not found it, but we'd ran it already? Must of completed.
2369
		elseif (!$found)
2370
		{
2371
			$smcFunc['db_free_result']($request);
2372
			return true;
2373
		}
2374
2375
		// Pause execution for a sec or three.
2376
		sleep(3);
2377
2378
		// Can never be too well protected.
2379
		nextSubstep($substep);
2380
	}
2381
2382
	// Protect it.
2383
	nextSubstep($substep);
2384
}
2385
2386
/**
2387
 * Alter a text column definition preserving its character set.
2388
 *
2389
 * @param array $change
2390
 * @param int $substep
2391
 */
2392
function textfield_alter($change, $substep)
2393
{
2394
	global $db_prefix, $smcFunc;
2395
2396
	$request = $smcFunc['db_query']('', '
2397
		SHOW FULL COLUMNS
2398
		FROM {db_prefix}' . $change['table'] . '
2399
		LIKE {string:column}',
2400
		array(
2401
			'column' => $change['column'],
2402
			'db_error_skip' => true,
2403
		)
2404
	);
2405
	if ($smcFunc['db_num_rows']($request) === 0)
2406
		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...
2407
	$table_row = $smcFunc['db_fetch_assoc']($request);
2408
	$smcFunc['db_free_result']($request);
2409
2410
	// If something of the current column definition is different, fix it.
2411
	$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']);
2412
2413
	// Columns that previously allowed null, need to be converted first.
2414
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2415
2416
	// Get the character set that goes with the collation of the column.
2417
	if ($column_fix && !empty($table_row['Collation']))
2418
	{
2419
		$request = $smcFunc['db_query']('', '
2420
			SHOW COLLATION
2421
			LIKE {string:collation}',
2422
			array(
2423
				'collation' => $table_row['Collation'],
2424
				'db_error_skip' => true,
2425
			)
2426
		);
2427
		// No results? Just forget it all together.
2428
		if ($smcFunc['db_num_rows']($request) === 0)
2429
			unset($table_row['Collation']);
2430
		else
2431
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2432
		$smcFunc['db_free_result']($request);
2433
	}
2434
2435
	if ($column_fix)
2436
	{
2437
		// Make sure there are no NULL's left.
2438
		if ($null_fix)
2439
			$smcFunc['db_query']('', '
2440
				UPDATE {db_prefix}' . $change['table'] . '
2441
				SET ' . $change['column'] . ' = {string:default}
2442
				WHERE ' . $change['column'] . ' IS NULL',
2443
				array(
2444
					'default' => isset($change['default']) ? $change['default'] : '',
2445
					'db_error_skip' => true,
2446
				)
2447
			);
2448
2449
		// Do the actual alteration.
2450
		$smcFunc['db_query']('', '
2451
			ALTER TABLE {db_prefix}' . $change['table'] . '
2452
			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}' : ''),
2453
			array(
2454
				'default' => isset($change['default']) ? $change['default'] : '',
2455
				'db_error_skip' => true,
2456
			)
2457
		);
2458
	}
2459
	nextSubstep($substep);
2460
}
2461
2462
// Check if we need to alter this query.
2463
function checkChange(&$change)
2464
{
2465
	global $smcFunc, $db_type, $databases;
2466
	static $database_version, $where_field_support;
2467
2468
	// Attempt to find a database_version.
2469
	if (empty($database_version))
2470
	{
2471
		$database_version = $databases[$db_type]['version_check'];
2472
		$where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version, '<=');
2473
	}
2474
2475
	// Not a column we need to check on?
2476
	if (!in_array($change['name'], array('memberGroups', 'passwordSalt')))
2477
		return;
2478
2479
	// Break it up you (six|seven).
2480
	$temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text']));
2481
2482
	// Can we support a shortcut method?
2483
	if ($where_field_support)
2484
	{
2485
		// Get the details about this change.
2486
		$request = $smcFunc['db_query']('', '
2487
			SHOW FIELDS
2488
			FROM {db_prefix}{raw:table}
2489
			WHERE Field = {string:old_name} OR Field = {string:new_name}',
2490
			array(
2491
				'table' => $change['table'],
2492
				'old_name' => $temp[1],
2493
				'new_name' => $temp[2],
2494
			)
2495
		);
2496
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2497
		if ($smcFunc['db_num_rows'] != 1)
2498
			return;
2499
2500
		list (, $current_type) = $smcFunc['db_fetch_assoc']($request);
2501
		$smcFunc['db_free_result']($request);
2502
	}
2503
	else
2504
	{
2505
		// Do this the old fashion, sure method way.
2506
		$request = $smcFunc['db_query']('', '
2507
			SHOW FIELDS
2508
			FROM {db_prefix}{raw:table}',
2509
			array(
2510
				'table' => $change['table'],
2511
			)
2512
		);
2513
		// Mayday!
2514
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2515
		if ($smcFunc['db_num_rows'] == 0)
2516
			return;
2517
2518
		// Oh where, oh where has my little field gone. Oh where can it be...
2519
		while ($row = $smcFunc['db_query']($request))
2520
			if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2])
2521
			{
2522
				$current_type = $row['Type'];
2523
				break;
2524
			}
2525
	}
2526
2527
	// If this doesn't match, the column may of been altered for a reason.
2528
	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...
2529
		$temp[3] = $current_type;
2530
2531
	// Piece this back together.
2532
	$change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp));
2533
}
2534
2535
// The next substep.
2536
function nextSubstep($substep)
2537
{
2538
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2539
	global $step_progress, $is_debug, $upcontext;
2540
2541
	if ($_GET['substep'] < $substep)
2542
		$_GET['substep'] = $substep;
2543
2544
	if ($command_line)
2545
	{
2546
		if (time() - $start_time > 1 && empty($is_debug))
2547
		{
2548
			echo '.';
2549
			$start_time = time();
2550
		}
2551
		return;
2552
	}
2553
2554
	@set_time_limit(300);
2555
	if (function_exists('apache_reset_timeout'))
2556
		@apache_reset_timeout();
2557
2558
	if (time() - $start_time <= $timeLimitThreshold)
2559
		return;
2560
2561
	// Do we have some custom step progress stuff?
2562
	if (!empty($step_progress))
2563
	{
2564
		$upcontext['substep_progress'] = 0;
2565
		$upcontext['substep_progress_name'] = $step_progress['name'];
2566
		if ($step_progress['current'] > $step_progress['total'])
2567
			$upcontext['substep_progress'] = 99.9;
2568
		else
2569
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2570
2571
		// Make it nicely rounded.
2572
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2573
	}
2574
2575
	// If this is XML we just exit right away!
2576
	if (isset($_GET['xml']))
2577
		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...
2578
2579
	// We're going to pause after this!
2580
	$upcontext['pause'] = true;
2581
2582
	$upcontext['query_string'] = '';
2583
	foreach ($_GET as $k => $v)
2584
	{
2585
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2586
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2587
	}
2588
2589
	// Custom warning?
2590
	if (!empty($custom_warning))
2591
		$upcontext['custom_warning'] = $custom_warning;
2592
2593
	upgradeExit();
2594
}
2595
2596
function cmdStep0()
2597
{
2598
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2599
	global $is_debug;
2600
	$start_time = time();
2601
2602
	ob_end_clean();
2603
	ob_implicit_flush(1);
2604
	@set_time_limit(600);
2605
2606
	if (!isset($_SERVER['argv']))
2607
		$_SERVER['argv'] = array();
2608
	$_GET['maint'] = 1;
2609
2610
	foreach ($_SERVER['argv'] as $i => $arg)
2611
	{
2612
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2613
			$upcontext['lang'] = $match[1];
2614
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2615
			continue;
2616
		elseif ($arg == '--no-maintenance')
2617
			$_GET['maint'] = 0;
2618
		elseif ($arg == '--debug')
2619
			$is_debug = true;
2620
		elseif ($arg == '--backup')
2621
			$_POST['backup'] = 1;
2622
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2623
			$_GET['conv'] = 1;
2624
		elseif ($i != 0)
2625
		{
2626
			echo 'SMF Command-line Upgrader
2627
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2628
2629
	--language=LANG         Reset the forum\'s language to LANG.
2630
	--no-maintenance        Don\'t put the forum into maintenance mode.
2631
	--debug                 Output debugging information.
2632
	--backup                Create backups of tables with "backup_" prefix.';
2633
			echo "\n";
2634
			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...
2635
		}
2636
	}
2637
2638
	if (!php_version_check())
2639
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
2640
	if (!db_version_check())
2641
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2642
2643
	// Do some checks to make sure they have proper privileges
2644
	db_extend('packages');
2645
2646
	// CREATE
2647
	$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');
2648
2649
	// ALTER
2650
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2651
2652
	// DROP
2653
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2654
2655
	// Sorry... we need CREATE, ALTER and DROP
2656
	if (!$create || !$alter || !$drop)
2657
		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);
2658
2659
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2660
		&& @file_exists($sourcedir . '/QueryString.php')
2661
		&& @file_exists($sourcedir . '/ManageBoards.php');
2662
	if (!$check && !isset($modSettings['smfVersion']))
2663
		print_error('Error: Some files are missing or out-of-date.', true);
2664
2665
	// Do a quick version spot check.
2666
	$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

2666
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
2667
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2668
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2669
		print_error('Error: Some files have not yet been updated properly.');
2670
2671
	// Make sure Settings.php is writable.
2672
	quickFileWritable($boarddir . '/Settings.php');
2673
	if (!is_writable($boarddir . '/Settings.php'))
2674
		print_error('Error: Unable to obtain write access to "Settings.php".', true);
2675
2676
	// Make sure Settings_bak.php is writable.
2677
	quickFileWritable($boarddir . '/Settings_bak.php');
2678
	if (!is_writable($boarddir . '/Settings_bak.php'))
2679
		print_error('Error: Unable to obtain write access to "Settings_bak.php".');
2680
2681
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
2682
		print_error('Error: Unable to obtain write access to "agreement.txt".');
2683
	elseif (isset($modSettings['agreement']))
2684
	{
2685
		$fp = fopen($boarddir . '/agreement.txt', 'w');
2686
		fwrite($fp, $modSettings['agreement']);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, 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

2686
		fwrite(/** @scrutinizer ignore-type */ $fp, $modSettings['agreement']);
Loading history...
2687
		fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, 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

2687
		fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
2688
	}
2689
2690
	// Make sure Themes is writable.
2691
	quickFileWritable($modSettings['theme_dir']);
2692
2693
	if (!is_writable($modSettings['theme_dir']) && !isset($modSettings['smfVersion']))
2694
		print_error('Error: Unable to obtain write access to "Themes".');
2695
2696
	// Make sure cache directory exists and is writable!
2697
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
2698
	if (!file_exists($cachedir_temp))
2699
		@mkdir($cachedir_temp);
2700
2701
	// Make sure the cache temp dir is writable.
2702
	quickFileWritable($cachedir_temp);
2703
2704
	if (!is_writable($cachedir_temp))
2705
		print_error('Error: Unable to obtain write access to "cache".', true);
2706
2707
	// Make sure db_last_error.php is writable.
2708
	quickFileWritable($cachedir_temp . '/db_last_error.php');
2709
	if (!is_writable($cachedir_temp . '/db_last_error.php'))
2710
		print_error('Error: Unable to obtain write access to "db_last_error.php".');
2711
2712
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
2713
		print_error('Error: Unable to find language files!', true);
2714
	else
2715
	{
2716
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
2717
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
2718
2719
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
2720
			print_error('Error: Language files out of date.', true);
2721
		if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'))
2722
			print_error('Error: Install language is missing for selected language.', true);
2723
2724
		// Otherwise include it!
2725
		require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php');
2726
	}
2727
2728
	// Make sure we skip the HTML for login.
2729
	$_POST['upcont'] = true;
2730
	$upcontext['current_step'] = 1;
2731
}
2732
2733
/**
2734
 * Handles converting your database to UTF-8
2735
 */
2736
function ConvertUtf8()
2737
{
2738
	global $upcontext, $db_character_set, $sourcedir, $smcFunc, $modSettings, $language;
2739
	global $db_prefix, $db_type, $command_line, $support_js, $txt;
2740
2741
	// Done it already?
2742
	if (!empty($_POST['utf8_done']))
2743
		return true;
2744
2745
	// First make sure they aren't already on UTF-8 before we go anywhere...
2746
	if ($db_type == 'postgresql' || ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8'))
2747
	{
2748
		$smcFunc['db_insert']('replace',
2749
			'{db_prefix}settings',
2750
			array('variable' => 'string', 'value' => 'string'),
2751
			array(array('global_character_set', 'UTF-8')),
2752
			array('variable')
2753
		);
2754
2755
		return true;
2756
	}
2757
	else
2758
	{
2759
		$upcontext['page_title'] = $txt['converting_utf8'];
2760
		$upcontext['sub_template'] = isset($_GET['xml']) ? 'convert_xml' : 'convert_utf8';
2761
2762
		// The character sets used in SMF's language files with their db equivalent.
2763
		$charsets = array(
2764
			// Armenian
2765
			'armscii8' => 'armscii8',
2766
			// Chinese-traditional.
2767
			'big5' => 'big5',
2768
			// Chinese-simplified.
2769
			'gbk' => 'gbk',
2770
			// West European.
2771
			'ISO-8859-1' => 'latin1',
2772
			// Romanian.
2773
			'ISO-8859-2' => 'latin2',
2774
			// Turkish.
2775
			'ISO-8859-9' => 'latin5',
2776
			// Latvian
2777
			'ISO-8859-13' => 'latin7',
2778
			// West European with Euro sign.
2779
			'ISO-8859-15' => 'latin9',
2780
			// Thai.
2781
			'tis-620' => 'tis620',
2782
			// Persian, Chinese, etc.
2783
			'UTF-8' => 'utf8',
2784
			// Russian.
2785
			'windows-1251' => 'cp1251',
2786
			// Greek.
2787
			'windows-1253' => 'utf8',
2788
			// Hebrew.
2789
			'windows-1255' => 'utf8',
2790
			// Arabic.
2791
			'windows-1256' => 'cp1256',
2792
		);
2793
2794
		// Get a list of character sets supported by your MySQL server.
2795
		$request = $smcFunc['db_query']('', '
2796
			SHOW CHARACTER SET',
2797
			array(
2798
			)
2799
		);
2800
		$db_charsets = array();
2801
		while ($row = $smcFunc['db_fetch_assoc']($request))
2802
			$db_charsets[] = $row['Charset'];
2803
2804
		$smcFunc['db_free_result']($request);
2805
2806
		// Character sets supported by both MySQL and SMF's language files.
2807
		$charsets = array_intersect($charsets, $db_charsets);
2808
2809
		// Use the messages.body column as indicator for the database charset.
2810
		$request = $smcFunc['db_query']('', '
2811
			SHOW FULL COLUMNS
2812
			FROM {db_prefix}messages
2813
			LIKE {string:body_like}',
2814
			array(
2815
				'body_like' => 'body',
2816
			)
2817
		);
2818
		$column_info = $smcFunc['db_fetch_assoc']($request);
2819
		$smcFunc['db_free_result']($request);
2820
2821
		// A collation looks like latin1_swedish. We only need the character set.
2822
		list($upcontext['database_charset']) = explode('_', $column_info['Collation']);
2823
		$upcontext['database_charset'] = in_array($upcontext['database_charset'], $charsets) ? array_search($upcontext['database_charset'], $charsets) : $upcontext['database_charset'];
2824
2825
		// Detect whether a fulltext index is set.
2826
		$request = $smcFunc['db_query']('', '
2827
			SHOW INDEX
2828
			FROM {db_prefix}messages',
2829
			array(
2830
			)
2831
		);
2832
2833
		$upcontext['dropping_index'] = false;
2834
2835
		// If there's a fulltext index, we need to drop it first...
2836
		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
2837
		{
2838
			while ($row = $smcFunc['db_fetch_assoc']($request))
2839
				if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
2840
					$upcontext['fulltext_index'][] = $row['Key_name'];
2841
			$smcFunc['db_free_result']($request);
2842
2843
			if (isset($upcontext['fulltext_index']))
2844
				$upcontext['fulltext_index'] = array_unique($upcontext['fulltext_index']);
2845
		}
2846
2847
		// Drop it and make a note...
2848
		if (!empty($upcontext['fulltext_index']))
2849
		{
2850
			$upcontext['dropping_index'] = true;
2851
2852
			$smcFunc['db_query']('', '
2853
				ALTER TABLE {db_prefix}messages
2854
				DROP INDEX ' . implode(',
2855
				DROP INDEX ', $upcontext['fulltext_index']),
2856
				array(
2857
					'db_error_skip' => true,
2858
				)
2859
			);
2860
2861
			// Update the settings table
2862
			$smcFunc['db_insert']('replace',
2863
				'{db_prefix}settings',
2864
				array('variable' => 'string', 'value' => 'string'),
2865
				array('db_search_index', ''),
2866
				array('variable')
2867
			);
2868
		}
2869
2870
		// Figure out what charset we should be converting from...
2871
		$lang_charsets = array(
2872
			'arabic' => 'windows-1256',
2873
			'armenian_east' => 'armscii-8',
2874
			'armenian_west' => 'armscii-8',
2875
			'azerbaijani_latin' => 'ISO-8859-9',
2876
			'bangla' => 'UTF-8',
2877
			'belarusian' => 'ISO-8859-5',
2878
			'bulgarian' => 'windows-1251',
2879
			'cambodian' => 'UTF-8',
2880
			'chinese_simplified' => 'gbk',
2881
			'chinese_traditional' => 'big5',
2882
			'croation' => 'ISO-8859-2',
2883
			'czech' => 'ISO-8859-2',
2884
			'czech_informal' => 'ISO-8859-2',
2885
			'english_pirate' => 'UTF-8',
2886
			'esperanto' => 'ISO-8859-3',
2887
			'estonian' => 'ISO-8859-15',
2888
			'filipino_tagalog' => 'UTF-8',
2889
			'filipino_vasayan' => 'UTF-8',
2890
			'georgian' => 'UTF-8',
2891
			'greek' => 'ISO-8859-3',
2892
			'hebrew' => 'windows-1255',
2893
			'hungarian' => 'ISO-8859-2',
2894
			'irish' => 'UTF-8',
2895
			'japanese' => 'UTF-8',
2896
			'khmer' => 'UTF-8',
2897
			'korean' => 'UTF-8',
2898
			'kurdish_kurmanji' => 'ISO-8859-9',
2899
			'kurdish_sorani' => 'windows-1256',
2900
			'lao' => 'tis-620',
2901
			'latvian' => 'ISO-8859-13',
2902
			'lithuanian' => 'ISO-8859-4',
2903
			'macedonian' => 'UTF-8',
2904
			'malayalam' => 'UTF-8',
2905
			'mongolian' => 'UTF-8',
2906
			'nepali' => 'UTF-8',
2907
			'persian' => 'UTF-8',
2908
			'polish' => 'ISO-8859-2',
2909
			'romanian' => 'ISO-8859-2',
2910
			'russian' => 'windows-1252',
2911
			'sakha' => 'UTF-8',
2912
			'serbian_cyrillic' => 'ISO-8859-5',
2913
			'serbian_latin' => 'ISO-8859-2',
2914
			'sinhala' => 'UTF-8',
2915
			'slovak' => 'ISO-8859-2',
2916
			'slovenian' => 'ISO-8859-2',
2917
			'telugu' => 'UTF-8',
2918
			'thai' => 'tis-620',
2919
			'turkish' => 'ISO-8859-9',
2920
			'turkmen' => 'ISO-8859-9',
2921
			'ukranian' => 'windows-1251',
2922
			'urdu' => 'UTF-8',
2923
			'uzbek_cyrillic' => 'ISO-8859-5',
2924
			'uzbek_latin' => 'ISO-8859-5',
2925
			'vietnamese' => 'UTF-8',
2926
			'yoruba' => 'UTF-8'
2927
		);
2928
2929
		// Default to ISO-8859-1 unless we detected another supported charset
2930
		$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';
2931
2932
		$upcontext['charset_list'] = array_keys($charsets);
2933
2934
		// Translation table for the character sets not native for MySQL.
2935
		$translation_tables = array(
2936
			'windows-1255' => array(
2937
				'0x81' => '\'\'',		'0x8A' => '\'\'',		'0x8C' => '\'\'',
2938
				'0x8D' => '\'\'',		'0x8E' => '\'\'',		'0x8F' => '\'\'',
2939
				'0x90' => '\'\'',		'0x9A' => '\'\'',		'0x9C' => '\'\'',
2940
				'0x9D' => '\'\'',		'0x9E' => '\'\'',		'0x9F' => '\'\'',
2941
				'0xCA' => '\'\'',		'0xD9' => '\'\'',		'0xDA' => '\'\'',
2942
				'0xDB' => '\'\'',		'0xDC' => '\'\'',		'0xDD' => '\'\'',
2943
				'0xDE' => '\'\'',		'0xDF' => '\'\'',		'0xFB' => '0xD792',
2944
				'0xFC' => '0xE282AC',		'0xFF' => '0xD6B2',		'0xC2' => '0xFF',
2945
				'0x80' => '0xFC',		'0xE2' => '0xFB',		'0xA0' => '0xC2A0',
2946
				'0xA1' => '0xC2A1',		'0xA2' => '0xC2A2',		'0xA3' => '0xC2A3',
2947
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
2948
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
2949
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
2950
				'0xAF' => '0xC2AF',		'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',
2951
				'0xB2' => '0xC2B2',		'0xB3' => '0xC2B3',		'0xB4' => '0xC2B4',
2952
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
2953
				'0xB8' => '0xC2B8',		'0xB9' => '0xC2B9',		'0xBB' => '0xC2BB',
2954
				'0xBC' => '0xC2BC',		'0xBD' => '0xC2BD',		'0xBE' => '0xC2BE',
2955
				'0xBF' => '0xC2BF',		'0xD7' => '0xD7B3',		'0xD1' => '0xD781',
2956
				'0xD4' => '0xD7B0',		'0xD5' => '0xD7B1',		'0xD6' => '0xD7B2',
2957
				'0xE0' => '0xD790',		'0xEA' => '0xD79A',		'0xEC' => '0xD79C',
2958
				'0xED' => '0xD79D',		'0xEE' => '0xD79E',		'0xEF' => '0xD79F',
2959
				'0xF0' => '0xD7A0',		'0xF1' => '0xD7A1',		'0xF2' => '0xD7A2',
2960
				'0xF3' => '0xD7A3',		'0xF5' => '0xD7A5',		'0xF6' => '0xD7A6',
2961
				'0xF7' => '0xD7A7',		'0xF8' => '0xD7A8',		'0xF9' => '0xD7A9',
2962
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
2963
				'0x86' => '0xE280A0',	'0x87' => '0xE280A1',	'0x89' => '0xE280B0',
2964
				'0x8B' => '0xE280B9',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
2965
				'0x95' => '0xE280A2',	'0x97' => '0xE28094',	'0x99' => '0xE284A2',
2966
				'0xC0' => '0xD6B0',		'0xC1' => '0xD6B1',		'0xC3' => '0xD6B3',
2967
				'0xC4' => '0xD6B4',		'0xC5' => '0xD6B5',		'0xC6' => '0xD6B6',
2968
				'0xC7' => '0xD6B7',		'0xC8' => '0xD6B8',		'0xC9' => '0xD6B9',
2969
				'0xCB' => '0xD6BB',		'0xCC' => '0xD6BC',		'0xCD' => '0xD6BD',
2970
				'0xCE' => '0xD6BE',		'0xCF' => '0xD6BF',		'0xD0' => '0xD780',
2971
				'0xD2' => '0xD782',		'0xE3' => '0xD793',		'0xE4' => '0xD794',
2972
				'0xE5' => '0xD795',		'0xE7' => '0xD797',		'0xE9' => '0xD799',
2973
				'0xFD' => '0xE2808E',	'0xFE' => '0xE2808F',	'0x92' => '0xE28099',
2974
				'0x83' => '0xC692',		'0xD3' => '0xD783',		'0x88' => '0xCB86',
2975
				'0x98' => '0xCB9C',		'0x91' => '0xE28098',	'0x96' => '0xE28093',
2976
				'0xBA' => '0xC3B7',		'0x9B' => '0xE280BA',	'0xAA' => '0xC397',
2977
				'0xA4' => '0xE282AA',	'0xE1' => '0xD791',		'0xE6' => '0xD796',
2978
				'0xE8' => '0xD798',		'0xEB' => '0xD79B',		'0xF4' => '0xD7A4',
2979
				'0xFA' => '0xD7AA',
2980
			),
2981
			'windows-1253' => array(
2982
				'0x81' => '\'\'',			'0x88' => '\'\'',			'0x8A' => '\'\'',
2983
				'0x8C' => '\'\'',			'0x8D' => '\'\'',			'0x8E' => '\'\'',
2984
				'0x8F' => '\'\'',			'0x90' => '\'\'',			'0x98' => '\'\'',
2985
				'0x9A' => '\'\'',			'0x9C' => '\'\'',			'0x9D' => '\'\'',
2986
				'0x9E' => '\'\'',			'0x9F' => '\'\'',			'0xAA' => '\'\'',
2987
				'0xD2' => '0xE282AC',			'0xFF' => '0xCE92',			'0xCE' => '0xCE9E',
2988
				'0xB8' => '0xCE88',		'0xBA' => '0xCE8A',		'0xBC' => '0xCE8C',
2989
				'0xBE' => '0xCE8E',		'0xBF' => '0xCE8F',		'0xC0' => '0xCE90',
2990
				'0xC8' => '0xCE98',		'0xCA' => '0xCE9A',		'0xCC' => '0xCE9C',
2991
				'0xCD' => '0xCE9D',		'0xCF' => '0xCE9F',		'0xDA' => '0xCEAA',
2992
				'0xE8' => '0xCEB8',		'0xEA' => '0xCEBA',		'0xEC' => '0xCEBC',
2993
				'0xEE' => '0xCEBE',		'0xEF' => '0xCEBF',		'0xC2' => '0xFF',
2994
				'0xBD' => '0xC2BD',		'0xED' => '0xCEBD',		'0xB2' => '0xC2B2',
2995
				'0xA0' => '0xC2A0',		'0xA3' => '0xC2A3',		'0xA4' => '0xC2A4',
2996
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
2997
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
2998
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
2999
				'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',		'0xB3' => '0xC2B3',
3000
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
3001
				'0xBB' => '0xC2BB',		'0xE2' => '0xCEB2',		'0x80' => '0xD2',
3002
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
3003
				'0x86' => '0xE280A0',	'0xA1' => '0xCE85',		'0xA2' => '0xCE86',
3004
				'0x87' => '0xE280A1',	'0x89' => '0xE280B0',	'0xB9' => '0xCE89',
3005
				'0x8B' => '0xE280B9',	'0x91' => '0xE28098',	'0x99' => '0xE284A2',
3006
				'0x92' => '0xE28099',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
3007
				'0x95' => '0xE280A2',	'0x96' => '0xE28093',	'0x97' => '0xE28094',
3008
				'0x9B' => '0xE280BA',	'0xAF' => '0xE28095',	'0xB4' => '0xCE84',
3009
				'0xC1' => '0xCE91',		'0xC3' => '0xCE93',		'0xC4' => '0xCE94',
3010
				'0xC5' => '0xCE95',		'0xC6' => '0xCE96',		'0x83' => '0xC692',
3011
				'0xC7' => '0xCE97',		'0xC9' => '0xCE99',		'0xCB' => '0xCE9B',
3012
				'0xD0' => '0xCEA0',		'0xD1' => '0xCEA1',		'0xD3' => '0xCEA3',
3013
				'0xD4' => '0xCEA4',		'0xD5' => '0xCEA5',		'0xD6' => '0xCEA6',
3014
				'0xD7' => '0xCEA7',		'0xD8' => '0xCEA8',		'0xD9' => '0xCEA9',
3015
				'0xDB' => '0xCEAB',		'0xDC' => '0xCEAC',		'0xDD' => '0xCEAD',
3016
				'0xDE' => '0xCEAE',		'0xDF' => '0xCEAF',		'0xE0' => '0xCEB0',
3017
				'0xE1' => '0xCEB1',		'0xE3' => '0xCEB3',		'0xE4' => '0xCEB4',
3018
				'0xE5' => '0xCEB5',		'0xE6' => '0xCEB6',		'0xE7' => '0xCEB7',
3019
				'0xE9' => '0xCEB9',		'0xEB' => '0xCEBB',		'0xF0' => '0xCF80',
3020
				'0xF1' => '0xCF81',		'0xF2' => '0xCF82',		'0xF3' => '0xCF83',
3021
				'0xF4' => '0xCF84',		'0xF5' => '0xCF85',		'0xF6' => '0xCF86',
3022
				'0xF7' => '0xCF87',		'0xF8' => '0xCF88',		'0xF9' => '0xCF89',
3023
				'0xFA' => '0xCF8A',		'0xFB' => '0xCF8B',		'0xFC' => '0xCF8C',
3024
				'0xFD' => '0xCF8D',		'0xFE' => '0xCF8E',
3025
			),
3026
		);
3027
3028
		// Make some preparations.
3029
		if (isset($translation_tables[$upcontext['charset_detected']]))
3030
		{
3031
			$replace = '%field%';
3032
3033
			// Build a huge REPLACE statement...
3034
			foreach ($translation_tables[$upcontext['charset_detected']] as $from => $to)
3035
				$replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')';
3036
		}
3037
3038
		// Get a list of table names ahead of time... This makes it easier to set our substep and such
3039
		db_extend();
3040
		$queryTables = $smcFunc['db_list_tables'](false, $db_prefix . '%');
3041
3042
		$upcontext['table_count'] = count($queryTables);
3043
3044
		// What ones have we already done?
3045
		foreach ($queryTables as $id => $table)
3046
			if ($id < $_GET['substep'])
3047
				$upcontext['previous_tables'][] = $table;
3048
3049
		$upcontext['cur_table_num'] = $_GET['substep'];
3050
		$upcontext['cur_table_name'] = str_replace($db_prefix, '', $queryTables[$_GET['substep']]);
3051
		$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3052
3053
		// Make sure we're ready & have painted the template before proceeding
3054
		if ($support_js && !isset($_GET['xml']))
3055
		{
3056
			$_GET['substep'] = 0;
3057
			return false;
3058
		}
3059
3060
		// We want to start at the first table.
3061
		for ($substep = $_GET['substep'], $n = count($queryTables); $substep < $n; $substep++)
3062
		{
3063
			$table = $queryTables[$substep];
3064
3065
			$getTableStatus = $smcFunc['db_query']('', '
3066
				SHOW TABLE STATUS
3067
				LIKE {string:table_name}',
3068
				array(
3069
					'table_name' => str_replace('_', '\_', $table)
3070
				)
3071
			);
3072
3073
			// Only one row so we can just fetch_assoc and free the result...
3074
			$table_info = $smcFunc['db_fetch_assoc']($getTableStatus);
3075
			$smcFunc['db_free_result']($getTableStatus);
3076
3077
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($queryTables[$substep + 1]) ? $queryTables[$substep + 1] : $queryTables[$substep]));
3078
			$upcontext['cur_table_num'] = $substep + 1;
3079
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3080
3081
			// Do we need to pause?
3082
			nextSubstep($substep);
3083
3084
			// Just to make sure it doesn't time out.
3085
			if (function_exists('apache_reset_timeout'))
3086
				@apache_reset_timeout();
3087
3088
			$table_charsets = array();
3089
3090
			// Loop through each column.
3091
			$queryColumns = $smcFunc['db_query']('', '
3092
				SHOW FULL COLUMNS
3093
				FROM ' . $table_info['Name'],
3094
				array(
3095
				)
3096
			);
3097
			while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns))
3098
			{
3099
				// Only text'ish columns have a character set and need converting.
3100
				if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
3101
				{
3102
					$collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation'];
3103
					if (!empty($collation) && $collation !== 'NULL')
3104
					{
3105
						list($charset) = explode('_', $collation);
3106
3107
						// Build structure of columns to operate on organized by charset; only operate on columns not yet utf8
3108
						if ($charset != 'utf8')
3109
						{
3110
							if (!isset($table_charsets[$charset]))
3111
								$table_charsets[$charset] = array();
3112
3113
							$table_charsets[$charset][] = $column_info;
3114
						}
3115
					}
3116
				}
3117
			}
3118
			$smcFunc['db_free_result']($queryColumns);
3119
3120
			// Only change the non-utf8 columns identified above
3121
			if (count($table_charsets) > 0)
3122
			{
3123
				$updates_blob = '';
3124
				$updates_text = '';
3125
				foreach ($table_charsets as $charset => $columns)
3126
				{
3127
					if ($charset !== $charsets[$upcontext['charset_detected']])
3128
					{
3129
						foreach ($columns as $column)
3130
						{
3131
							$updates_blob .= '
3132
								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'] . '\'') . ',';
3133
							$updates_text .= '
3134
								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'] . '\'') . ',';
3135
						}
3136
					}
3137
				}
3138
3139
				// Change the columns to binary form.
3140
				$smcFunc['db_query']('', '
3141
					ALTER TABLE {raw:table_name}{raw:updates_blob}',
3142
					array(
3143
						'table_name' => $table_info['Name'],
3144
						'updates_blob' => substr($updates_blob, 0, -1),
3145
					)
3146
				);
3147
3148
				// Convert the character set if MySQL has no native support for it.
3149
				if (isset($translation_tables[$upcontext['charset_detected']]))
3150
				{
3151
					$update = '';
3152
					foreach ($table_charsets as $charset => $columns)
3153
						foreach ($columns as $column)
3154
							$update .= '
3155
								' . $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...
3156
3157
					$smcFunc['db_query']('', '
3158
						UPDATE {raw:table_name}
3159
						SET {raw:updates}',
3160
						array(
3161
							'table_name' => $table_info['Name'],
3162
							'updates' => substr($update, 0, -1),
3163
						)
3164
					);
3165
				}
3166
3167
				// Change the columns back, but with the proper character set.
3168
				$smcFunc['db_query']('', '
3169
					ALTER TABLE {raw:table_name}{raw:updates_text}',
3170
					array(
3171
						'table_name' => $table_info['Name'],
3172
						'updates_text' => substr($updates_text, 0, -1),
3173
					)
3174
				);
3175
			}
3176
3177
			// Now do the actual conversion (if still needed).
3178
			if ($charsets[$upcontext['charset_detected']] !== 'utf8')
3179
			{
3180
				if ($command_line)
3181
					echo 'Converting table ' . $table_info['Name'] . ' to UTF-8...';
3182
3183
				$smcFunc['db_query']('', '
3184
					ALTER TABLE {raw:table_name}
3185
					CONVERT TO CHARACTER SET utf8',
3186
					array(
3187
						'table_name' => $table_info['Name'],
3188
					)
3189
				);
3190
3191
				if ($command_line)
3192
					echo " done.\n";
3193
			}
3194
			// If this is XML to keep it nice for the user do one table at a time anyway!
3195
			if (isset($_GET['xml']) && $upcontext['cur_table_num'] < $upcontext['table_count'])
3196
				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...
3197
		}
3198
3199
		$prev_charset = empty($translation_tables[$upcontext['charset_detected']]) ? $charsets[$upcontext['charset_detected']] : $translation_tables[$upcontext['charset_detected']];
3200
3201
		$smcFunc['db_insert']('replace',
3202
			'{db_prefix}settings',
3203
			array('variable' => 'string', 'value' => 'string'),
3204
			array(array('global_character_set', 'UTF-8'), array('previousCharacterSet', $prev_charset)),
3205
			array('variable')
3206
		);
3207
3208
		// Store it in Settings.php too because it's needed before db connection.
3209
		// Hopefully this works...
3210
		require_once($sourcedir . '/Subs.php');
3211
		require_once($sourcedir . '/Subs-Admin.php');
3212
		updateSettingsFile(array('db_character_set' => 'utf8'));
3213
3214
		// The conversion might have messed up some serialized strings. Fix them!
3215
		$request = $smcFunc['db_query']('', '
3216
			SELECT id_action, extra
3217
			FROM {db_prefix}log_actions
3218
			WHERE action IN ({string:remove}, {string:delete})',
3219
			array(
3220
				'remove' => 'remove',
3221
				'delete' => 'delete',
3222
			)
3223
		);
3224
		while ($row = $smcFunc['db_fetch_assoc']($request))
3225
		{
3226
			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)
3227
				$smcFunc['db_query']('', '
3228
					UPDATE {db_prefix}log_actions
3229
					SET extra = {string:extra}
3230
					WHERE id_action = {int:current_action}',
3231
					array(
3232
						'current_action' => $row['id_action'],
3233
						'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4],
3234
					)
3235
				);
3236
		}
3237
		$smcFunc['db_free_result']($request);
3238
3239
		if ($upcontext['dropping_index'] && $command_line)
3240
		{
3241
			echo "\n" . '', $txt['upgrade_fulltext_error'], '';
3242
			flush();
3243
		}
3244
	}
3245
	$_GET['substep'] = 0;
3246
	return false;
3247
}
3248
3249
/**
3250
 * Attempts to repair corrupted serialized data strings
3251
 *
3252
 * @param string $string Serialized data that has been corrupted
3253
 * @return string|bool A working version of the serialized data, or the original if the repair failed
3254
 */
3255
function fix_serialized_data($string)
3256
{
3257
	// If its not broken, don't fix it.
3258
	if (!is_string($string) || !preg_match('/^[bidsa]:/', $string) || @safe_unserialize($string) !== false)
0 ignored issues
show
introduced by
The condition is_string($string) is always true.
Loading history...
3259
		return $string;
3260
3261
	// This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8)
3262
	$new_string = preg_replace_callback('~\bs:(\d+):"(.*?)";(?=$|[bidsa]:|[{}]|N;)~s', function ($matches) {return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';}, $string);
3263
3264
	// @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc.
3265
3266
	// Did it work?
3267
	if (@safe_unserialize($new_string) !== false)
0 ignored issues
show
introduced by
The condition @safe_unserialize($new_string) !== false is always false.
Loading history...
3268
		return $new_string;
3269
	else
3270
		return $string;
3271
}
3272
3273
function serialize_to_json()
3274
{
3275
	global $command_line, $smcFunc, $modSettings, $sourcedir, $upcontext, $support_js, $txt;
3276
3277
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json';
3278
	// First thing's first - did we already do this?
3279
	if (!empty($modSettings['json_done']))
3280
	{
3281
		if ($command_line)
3282
			return ConvertUtf8();
3283
		else
3284
			return true;
3285
	}
3286
3287
	// Done it already - js wise?
3288
	if (!empty($_POST['json_done']))
3289
		return true;
3290
3291
	// List of tables affected by this function
3292
	// name => array('key', col1[,col2|true[,col3]])
3293
	// If 3rd item in array is true, it indicates that col1 could be empty...
3294
	$tables = array(
3295
		'background_tasks' => array('id_task', 'task_data'),
3296
		'log_actions' => array('id_action', 'extra'),
3297
		'log_online' => array('session', 'url'),
3298
		'log_packages' => array('id_install', 'db_changes', 'failed_steps', 'credits'),
3299
		'log_spider_hits' => array('id_hit', 'url'),
3300
		'log_subscribed' => array('id_sublog', 'pending_details'),
3301
		'pm_rules' => array('id_rule', 'criteria', 'actions'),
3302
		'qanda' => array('id_question', 'answers'),
3303
		'subscriptions' => array('id_subscribe', 'cost'),
3304
		'user_alerts' => array('id_alert', 'extra', true),
3305
		'user_drafts' => array('id_draft', 'to_list', true),
3306
		// These last two are a bit different - we'll handle those separately
3307
		'settings' => array(),
3308
		'themes' => array()
3309
	);
3310
3311
	// Set up some context stuff...
3312
	// Because we're not using numeric indices, we need this to figure out the current table name...
3313
	$keys = array_keys($tables);
3314
3315
	$upcontext['page_title'] = $txt['converting_json'];
3316
	$upcontext['table_count'] = count($keys);
3317
	$upcontext['cur_table_num'] = $_GET['substep'];
3318
	$upcontext['cur_table_name'] = isset($keys[$_GET['substep']]) ? $keys[$_GET['substep']] : $keys[0];
3319
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3320
3321
	foreach ($keys as $id => $table)
3322
		if ($id < $_GET['substep'])
3323
			$upcontext['previous_tables'][] = $table;
3324
3325
	if ($command_line)
3326
		echo 'Converting data from serialize() to json_encode().';
3327
3328
	if (!$support_js || isset($_GET['xml']))
3329
	{
3330
		// Fix the data in each table
3331
		for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++)
3332
		{
3333
			$upcontext['cur_table_name'] = isset($keys[$substep + 1]) ? $keys[$substep + 1] : $keys[$substep];
3334
			$upcontext['cur_table_num'] = $substep + 1;
3335
3336
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3337
3338
			// Do we need to pause?
3339
			nextSubstep($substep);
3340
3341
			// Initialize a few things...
3342
			$where = '';
3343
			$vars = array();
3344
			$table = $keys[$substep];
3345
			$info = $tables[$table];
3346
3347
			// Now the fun - build our queries and all that fun stuff
3348
			if ($table == 'settings')
3349
			{
3350
				// Now a few settings...
3351
				$serialized_settings = array(
3352
					'attachment_basedirectories',
3353
					'attachmentUploadDir',
3354
					'cal_today_birthday',
3355
					'cal_today_event',
3356
					'cal_today_holiday',
3357
					'displayFields',
3358
					'last_attachments_directory',
3359
					'memberlist_cache',
3360
					'search_custom_index_config',
3361
					'spider_name_cache'
3362
				);
3363
3364
				// Loop through and fix these...
3365
				$new_settings = array();
3366
				if ($command_line)
3367
					echo "\n" . 'Fixing some settings...';
3368
3369
				foreach ($serialized_settings as $var)
3370
				{
3371
					if (isset($modSettings[$var]))
3372
					{
3373
						// Attempt to unserialize the setting
3374
						$temp = @safe_unserialize($modSettings[$var]);
3375
						// Maybe conversion to UTF-8 corrupted it
3376
						if ($temp === false)
3377
							$temp = @safe_unserialize(fix_serialized_data($modSettings[$var]));
3378
3379
						if (!$temp && $command_line)
3380
							echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping.";
3381
						elseif ($temp !== false)
3382
							$new_settings[$var] = json_encode($temp);
3383
					}
3384
				}
3385
3386
				// Update everything at once
3387
				if (!function_exists('cache_put_data'))
3388
					require_once($sourcedir . '/Load.php');
3389
				updateSettings($new_settings, true);
3390
3391
				if ($command_line)
3392
					echo ' done.';
3393
			}
3394
			elseif ($table == 'themes')
3395
			{
3396
				// Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point...
3397
				$query = $smcFunc['db_query']('', '
3398
					SELECT id_member, id_theme, value FROM {db_prefix}themes
3399
					WHERE variable = {string:admin_prefs}',
3400
					array(
3401
						'admin_prefs' => 'admin_preferences'
3402
					)
3403
				);
3404
3405
				if ($smcFunc['db_num_rows']($query) != 0)
3406
				{
3407
					while ($row = $smcFunc['db_fetch_assoc']($query))
3408
					{
3409
						$temp = @safe_unserialize($row['value']);
3410
						if ($temp === false)
3411
							$temp = @safe_unserialize(fix_serialized_data($row['value']));
3412
3413
						if ($command_line)
3414
						{
3415
							if ($temp === false)
3416
								echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.';
3417
							else
3418
								echo "\n" . 'Fixing admin preferences...';
3419
						}
3420
3421
						if ($temp !== false)
3422
						{
3423
							$row['value'] = json_encode($temp);
3424
3425
							// Even though we have all values from the table, UPDATE is still faster than REPLACE
3426
							$smcFunc['db_query']('', '
3427
								UPDATE {db_prefix}themes
3428
								SET value = {string:prefs}
3429
								WHERE id_theme = {int:theme}
3430
									AND id_member = {int:member}
3431
									AND variable = {string:admin_prefs}',
3432
								array(
3433
									'prefs' => $row['value'],
3434
									'theme' => $row['id_theme'],
3435
									'member' => $row['id_member'],
3436
									'admin_prefs' => 'admin_preferences'
3437
								)
3438
							);
3439
3440
							if ($command_line)
3441
								echo ' done.';
3442
						}
3443
					}
3444
3445
					$smcFunc['db_free_result']($query);
3446
				}
3447
			}
3448
			else
3449
			{
3450
				// First item is always the key...
3451
				$key = $info[0];
3452
				unset($info[0]);
3453
3454
				// Now we know what columns we have and such...
3455
				if (count($info) == 2 && $info[2] === true)
3456
				{
3457
					$col_select = $info[1];
3458
					$where = ' WHERE ' . $info[1] . ' != {empty}';
3459
				}
3460
				else
3461
				{
3462
					$col_select = implode(', ', $info);
3463
				}
3464
3465
				$query = $smcFunc['db_query']('', '
3466
					SELECT ' . $key . ', ' . $col_select . '
3467
					FROM {db_prefix}' . $table . $where,
3468
					array()
3469
				);
3470
3471
				if ($smcFunc['db_num_rows']($query) != 0)
3472
				{
3473
					if ($command_line)
3474
					{
3475
						echo "\n" . ' +++ Fixing the "' . $table . '" table...';
3476
						flush();
3477
					}
3478
3479
					while ($row = $smcFunc['db_fetch_assoc']($query))
3480
					{
3481
						$update = '';
3482
3483
						// We already know what our key is...
3484
						foreach ($info as $col)
3485
						{
3486
							if ($col !== true && $row[$col] != '')
3487
							{
3488
								$temp = @safe_unserialize($row[$col]);
3489
3490
								// Maybe we can fix the data?
3491
								if ($temp === false)
3492
									$temp = @safe_unserialize(fix_serialized_data($row[$col]));
3493
3494
								// Maybe the data is already JSON?
3495
								if ($temp === false)
3496
									$temp = smf_json_decode($row[$col], true, false);
3497
3498
								// Oh well...
3499
								if ($temp === null)
3500
								{
3501
									$temp = array();
3502
3503
									if ($command_line)
3504
										echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n";
3505
								}
3506
3507
								$row[$col] = json_encode($temp);
3508
3509
								// Build our SET string and variables array
3510
								$update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}';
3511
								$vars[$col] = $row[$col];
3512
							}
3513
						}
3514
3515
						$vars[$key] = $row[$key];
3516
3517
						// In a few cases, we might have empty data, so don't try to update in those situations...
3518
						if (!empty($update))
3519
						{
3520
							$smcFunc['db_query']('', '
3521
								UPDATE {db_prefix}' . $table . '
3522
								SET ' . $update . '
3523
								WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}',
3524
								$vars
3525
							);
3526
						}
3527
					}
3528
3529
					if ($command_line)
3530
						echo ' done.';
3531
3532
					// Free up some memory...
3533
					$smcFunc['db_free_result']($query);
3534
				}
3535
			}
3536
			// If this is XML to keep it nice for the user do one table at a time anyway!
3537
			if (isset($_GET['xml']))
3538
				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...
3539
		}
3540
3541
		if ($command_line)
3542
		{
3543
			echo "\n" . 'Successful.' . "\n";
3544
			flush();
3545
		}
3546
		$upcontext['step_progress'] = 100;
3547
3548
		// Last but not least, insert a dummy setting so we don't have to do this again in the future...
3549
		updateSettings(array('json_done' => true));
3550
3551
		$_GET['substep'] = 0;
3552
		// Make sure we move on!
3553
		if ($command_line)
3554
			return ConvertUtf8();
3555
3556
		return true;
3557
	}
3558
3559
	// If this fails we just move on to deleting the upgrade anyway...
3560
	$_GET['substep'] = 0;
3561
	return false;
3562
}
3563
3564
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3565
						Templates are below this point
3566
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
3567
3568
// This is what is displayed if there's any chmod to be done. If not it returns nothing...
3569
function template_chmod()
3570
{
3571
	global $upcontext, $txt, $settings;
3572
3573
	// Don't call me twice!
3574
	if (!empty($upcontext['chmod_called']))
3575
		return;
3576
3577
	$upcontext['chmod_called'] = true;
3578
3579
	// Nothing?
3580
	if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error']))
3581
		return;
3582
3583
	// Was it a problem with Windows?
3584
	if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess')
3585
	{
3586
		echo '
3587
		<div class="error">
3588
			<p>', $txt['upgrade_writable_files'], '</p>
3589
			<ul class="error_content">
3590
				<li>' . implode('</li>
3591
				<li>', $upcontext['chmod']['files']) . '</li>
3592
			</ul>
3593
		</div>';
3594
3595
		return false;
3596
	}
3597
3598
	echo '
3599
		<div class="panel">
3600
			<h2>', $txt['upgrade_ftp_login'], '</h2>
3601
			<h3>', $txt['upgrade_ftp_perms'], '</h3>
3602
			<script>
3603
				function warning_popup()
3604
				{
3605
					popup = window.open(\'\',\'popup\',\'height=150,width=400,scrollbars=yes\');
3606
					var content = popup.document;
3607
					content.write(\'<!DOCTYPE html>\n\');
3608
					content.write(\'<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>\n\t<head>\n\t\t<meta name="robots" content="noindex">\n\t\t\');
3609
					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\');
3610
					content.write(\'<div class="windowbg description">\n\t\t\t<h4>', $txt['upgrade_ftp_files'], '</h4>\n\t\t\t\');
3611
					content.write(\'<p>', implode('<br>\n\t\t\t', $upcontext['chmod']['files']), '</p>\n\t\t\t\');';
3612
3613
	if (isset($upcontext['systemos']) && $upcontext['systemos'] == 'linux')
3614
		echo '
3615
					content.write(\'<hr>\n\t\t\t\');
3616
					content.write(\'<p>', $txt['upgrade_ftp_shell'], '</p>\n\t\t\t\');
3617
					content.write(\'<tt># chmod a+w ', implode(' ', $upcontext['chmod']['files']), '</tt>\n\t\t\t\');';
3618
3619
	echo '
3620
					content.write(\'<a href="javascript:self.close();">close</a>\n\t\t</div>\n\t</body>\n</html>\');
3621
					content.close();
3622
				}
3623
			</script>';
3624
3625
	if (!empty($upcontext['chmod']['ftp_error']))
3626
		echo '
3627
			<div class="error">
3628
				<p>', $txt['upgrade_ftp_error'], '<p>
3629
				<code>', $upcontext['chmod']['ftp_error'], '</code>
3630
			</div>';
3631
3632
	if (empty($upcontext['chmod_in_form']))
3633
		echo '
3634
			<form action="', $upcontext['form_url'], '" method="post">';
3635
3636
	echo '
3637
				<dl class="settings">
3638
					<dt>
3639
						<label for="ftp_server">', $txt['ftp_server'], ':</label>
3640
					</dt>
3641
					<dd>
3642
						<div class="floatright">
3643
							<label for="ftp_port" class="textbox"><strong>', $txt['ftp_port'], ':</strong></label>
3644
							<input type="text" size="3" name="ftp_port" id="ftp_port" value="', isset($upcontext['chmod']['port']) ? $upcontext['chmod']['port'] : '21', '">
3645
						</div>
3646
						<input type="text" size="30" name="ftp_server" id="ftp_server" value="', isset($upcontext['chmod']['server']) ? $upcontext['chmod']['server'] : 'localhost', '">
3647
						<div class="smalltext">', $txt['ftp_server_info'], '</div>
3648
					</dd>
3649
					<dt>
3650
						<label for="ftp_username">', $txt['ftp_username'], ':</label>
3651
					</dt>
3652
					<dd>
3653
						<input type="text" size="30" name="ftp_username" id="ftp_username" value="', isset($upcontext['chmod']['username']) ? $upcontext['chmod']['username'] : '', '">
3654
						<div class="smalltext">', $txt['ftp_username_info'], '</div>
3655
					</dd>
3656
					<dt>
3657
						<label for="ftp_password">', $txt['ftp_password'], ':</label>
3658
					</dt>
3659
					<dd>
3660
						<input type="password" size="30" name="ftp_password" id="ftp_password">
3661
						<div class="smalltext">', $txt['ftp_password_info'], '</div>
3662
					</dd>
3663
					<dt>
3664
						<label for="ftp_path">', $txt['ftp_path'], ':</label>
3665
					</dt>
3666
					<dd>
3667
						<input type="text" size="30" name="ftp_path" id="ftp_path" value="', isset($upcontext['chmod']['path']) ? $upcontext['chmod']['path'] : '', '">
3668
						<div class="smalltext">', !empty($upcontext['chmod']['path']) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info'], '</div>
3669
					</dd>
3670
				</dl>
3671
3672
				<div class="righttext buttons">
3673
					<input type="submit" value="', $txt['ftp_connect'], '" class="button">
3674
				</div>';
3675
3676
	if (empty($upcontext['chmod_in_form']))
3677
		echo '
3678
			</form>';
3679
3680
	echo '
3681
		</div><!-- .panel -->';
3682
}
3683
3684
function template_upgrade_above()
3685
{
3686
	global $modSettings, $txt, $settings, $upcontext, $upgradeurl;
3687
3688
	echo '<!DOCTYPE html>
3689
<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>
3690
<head>
3691
	<meta charset="', isset($txt['lang_character_set']) ? $txt['lang_character_set'] : 'UTF-8', '">
3692
	<meta name="robots" content="noindex">
3693
	<title>', $txt['upgrade_upgrade_utility'], '</title>
3694
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">
3695
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/install.css">
3696
	', $txt['lang_rtl'] == true ? '<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/rtl.css">' : '', '
3697
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
3698
	<script src="', $settings['default_theme_url'], '/scripts/script.js"></script>
3699
	<script>
3700
		var smf_scripturl = \'', $upgradeurl, '\';
3701
		var smf_charset = \'', (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'UTF-8' : $txt['lang_character_set']) : $modSettings['global_character_set']), '\';
3702
		var startPercent = ', $upcontext['overall_percent'], ';
3703
3704
		// This function dynamically updates the step progress bar - and overall one as required.
3705
		function updateStepProgress(current, max, overall_weight)
3706
		{
3707
			// What out the actual percent.
3708
			var width = parseInt((current / max) * 100);
3709
			if (document.getElementById(\'step_progress\'))
3710
			{
3711
				document.getElementById(\'step_progress\').style.width = width + "%";
3712
				setInnerHTML(document.getElementById(\'step_text\'), width + "%");
3713
			}
3714
			if (overall_weight && document.getElementById(\'overall_progress\'))
3715
			{
3716
				overall_width = parseInt(startPercent + width * (overall_weight / 100));
3717
				document.getElementById(\'overall_progress\').style.width = overall_width + "%";
3718
				setInnerHTML(document.getElementById(\'overall_text\'), overall_width + "%");
3719
			}
3720
		}
3721
	</script>
3722
</head>
3723
<body>
3724
	<div id="footerfix">
3725
	<div id="header">
3726
		<h1 class="forumtitle">', $txt['upgrade_upgrade_utility'], '</h1>
3727
		<img id="smflogo" src="', $settings['default_theme_url'], '/images/smflogo.svg" alt="Simple Machines Forum" title="Simple Machines Forum">
3728
	</div>
3729
	<div id="wrapper">
3730
		<div id="content_section">
3731
			<div id="main_content_section">
3732
				<div id="main_steps">
3733
					<h2>', $txt['upgrade_progress'], '</h2>
3734
					<ul class="steps_list">';
3735
3736
	foreach ($upcontext['steps'] as $num => $step)
3737
		echo '
3738
						<li', $num == $upcontext['current_step'] ? ' class="stepcurrent"' : '', '>
3739
							', $txt['upgrade_step'], ' ', $step[0], ': ', $txt[$step[1]], '
3740
						</li>';
3741
3742
	echo '
3743
					</ul>
3744
				</div><!-- #main_steps -->
3745
3746
				<div id="install_progress">
3747
					<div id="progress_bar" class="progress_bar progress_green">
3748
						<h3>', $txt['upgrade_overall_progress'], '</h3>
3749
						<div id="overall_progress" class="bar" style="width: ', $upcontext['overall_percent'], '%;"></div>
3750
						<span id="overall_text">', $upcontext['overall_percent'], '%</span>
3751
					</div>';
3752
3753
	if (isset($upcontext['step_progress']))
3754
		echo '
3755
					<div id="progress_bar_step" class="progress_bar progress_yellow">
3756
						<h3>', $txt['upgrade_step_progress'], '</h3>
3757
						<div id="step_progress" class="bar" style="width: ', $upcontext['step_progress'], '%;"></div>
3758
						<span id="step_text">', $upcontext['step_progress'], '%</span>
3759
					</div>';
3760
3761
	echo '
3762
					<div id="substep_bar_div" class="progress_bar ', isset($upcontext['substep_progress']) ? '' : 'hidden', '">
3763
						<h3 id="substep_name">', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], array('.' => ''))) : '', '</h3>
3764
						<div id="substep_progress" class="bar" style="width: ', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%;"></div>
3765
						<span id="substep_text">', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%</span>
3766
					</div>';
3767
3768
	// How long have we been running this?
3769
	$elapsed = time() - $upcontext['started'];
3770
	$mins = (int) ($elapsed / 60);
3771
	$seconds = $elapsed - $mins * 60;
3772
	echo '
3773
					<div class="smalltext time_elapsed">
3774
						', $txt['upgrade_time_elapsed'], ':
3775
						<span id="mins_elapsed">', $mins, '</span> ', $txt['upgrade_time_mins'], ', <span id="secs_elapsed">', $seconds, '</span> ', $txt['upgrade_time_secs'], '.
3776
					</div>';
3777
	echo '
3778
				</div><!-- #install_progress -->
3779
				<div id="main_screen" class="clear">
3780
					<h2>', $upcontext['page_title'], '</h2>
3781
					<div class="panel">';
3782
}
3783
3784
function template_upgrade_below()
3785
{
3786
	global $upcontext, $txt;
3787
3788
	if (!empty($upcontext['pause']))
3789
		echo '
3790
							<em>', $txt['upgrade_incomplete'], '.</em><br>
3791
3792
							<h2 style="margin-top: 2ex;">', $txt['upgrade_not_quite_done'], '</h2>
3793
							<h3>
3794
								', $txt['upgrade_paused_overload'], '
3795
							</h3>';
3796
3797
	if (!empty($upcontext['custom_warning']))
3798
		echo '
3799
							<div class="errorbox">
3800
								<h3>', $txt['upgrade_note'], '</h3>
3801
								', $upcontext['custom_warning'], '
3802
							</div>';
3803
3804
	echo '
3805
							<div class="righttext buttons">';
3806
3807
	if (!empty($upcontext['continue']))
3808
		echo '
3809
								<input type="submit" id="contbutt" name="contbutt" value="', $txt['upgrade_continue'], '"', $upcontext['continue'] == 2 ? ' disabled' : '', ' class="button">';
3810
	if (!empty($upcontext['skip']))
3811
		echo '
3812
								<input type="submit" id="skip" name="skip" value="', $txt['upgrade_skip'], '" onclick="dontSubmit = true; document.getElementById(\'contbutt\').disabled = \'disabled\'; return true;" class="button">';
3813
3814
	echo '
3815
							</div>
3816
						</form>
3817
					</div><!-- .panel -->
3818
				</div><!-- #main_screen -->
3819
			</div><!-- #main_content_section -->
3820
		</div><!-- #content_section -->
3821
	</div><!-- #wrapper -->
3822
	</div><!-- #footerfix -->
3823
	<div id="footer">
3824
		<ul>
3825
			<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>
3826
		</ul>
3827
	</div>';
3828
3829
	// Are we on a pause?
3830
	if (!empty($upcontext['pause']))
3831
	{
3832
		echo '
3833
	<script>
3834
		window.onload = doAutoSubmit;
3835
		var countdown = 3;
3836
		var dontSubmit = false;
3837
3838
		function doAutoSubmit()
3839
		{
3840
			if (countdown == 0 && !dontSubmit)
3841
				document.upform.submit();
3842
			else if (countdown == -1)
3843
				return;
3844
3845
			document.getElementById(\'contbutt\').value = "', $txt['upgrade_continue'], ' (" + countdown + ")";
3846
			countdown--;
3847
3848
			setTimeout("doAutoSubmit();", 1000);
3849
		}
3850
	</script>';
3851
	}
3852
3853
	echo '
3854
</body>
3855
</html>';
3856
}
3857
3858
function template_xml_above()
3859
{
3860
	global $upcontext;
3861
3862
	echo '<', '?xml version="1.0" encoding="UTF-8"?', '>
3863
	<smf>';
3864
3865
	if (!empty($upcontext['get_data']))
3866
		foreach ($upcontext['get_data'] as $k => $v)
3867
			echo '
3868
		<get key="', $k, '">', $v, '</get>';
3869
}
3870
3871
function template_xml_below()
3872
{
3873
	echo '
3874
	</smf>';
3875
}
3876
3877
function template_error_message()
3878
{
3879
	global $upcontext;
3880
3881
	echo '
3882
	<div class="error">
3883
		', $upcontext['error_msg'], '
3884
		<br>
3885
		<a href="', $_SERVER['PHP_SELF'], '">Click here to try again.</a>
3886
	</div>';
3887
}
3888
3889
function template_welcome_message()
3890
{
3891
	global $upcontext, $disable_security, $settings, $txt;
3892
3893
	echo '
3894
				<script src="https://www.simplemachines.org/smf/current-version.js?version=' . SMF_VERSION . '"></script>
3895
3896
				<h3>', sprintf($txt['upgrade_ready_proceed'], SMF_VERSION), '</h3>
3897
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">
3898
					<input type="hidden" name="', $upcontext['login_token_var'], '" value="', $upcontext['login_token'], '">
3899
3900
					<div id="version_warning" class="noticebox hidden">
3901
						<h3>', $txt['upgrade_warning'], '</h3>
3902
						', sprintf($txt['upgrade_warning_out_of_date'], SMF_VERSION, 'https://www.simplemachines.org'), '
3903
					</div>';
3904
3905
	$upcontext['chmod_in_form'] = true;
3906
	template_chmod();
3907
3908
	// For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade!
3909
	if ($upcontext['is_large_forum'])
3910
		echo '
3911
					<div class="errorbox">
3912
						<h3>', $txt['upgrade_warning'], '</h3>
3913
						', $txt['upgrade_warning_lots_data'], '
3914
					</div>';
3915
3916
	// A warning message?
3917
	if (!empty($upcontext['warning']))
3918
		echo '
3919
					<div class="errorbox">
3920
						<h3>', $txt['upgrade_warning'], '</h3>
3921
						', $upcontext['warning'], '
3922
					</div>';
3923
3924
	// Paths are incorrect?
3925
	echo '
3926
					<div class="errorbox', (file_exists($settings['default_theme_dir'] . '/scripts/script.js') ? ' hidden' : ''), '" id="js_script_missing_error">
3927
						<h3>', $txt['upgrade_critical_error'], '</h3>
3928
						', sprintf($txt['upgrade_error_script_js'], 'https://download.simplemachines.org/?tools'), '
3929
					</div>';
3930
3931
	// Is there someone already doing this?
3932
	if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600))
3933
	{
3934
		$ago = time() - $upcontext['started'];
3935
		$ago_hours = floor($ago / 3600);
3936
		$ago_minutes = intval(($ago / 60) % 60);
3937
		$ago_seconds = intval($ago % 60);
3938
		$agoTxt = $ago < 60 ? 'upgrade_time_s' : ($ago < 3600 ? 'upgrade_time_ms' : 'upgrade_time_hms');
3939
3940
		$updated = time() - $upcontext['updated'];
3941
		$updated_hours = floor($updated / 3600);
3942
		$updated_minutes = intval(($updated / 60) % 60);
3943
		$updated_seconds = intval($updated % 60);
3944
		$updatedTxt = $updated < 60 ? 'upgrade_time_updated_s' : ($updated < 3600 ? 'upgrade_time_updated_hm' : 'upgrade_time_updated_hms');
3945
3946
		echo '
3947
					<div class="errorbox">
3948
						<h3>', $txt['upgrade_warning'], '</h3>
3949
						<p>', sprintf($txt['upgrade_time_user'], $upcontext['user']['name']), '</p>
3950
						<p>', sprintf($txt[$agoTxt], $ago_seconds, $ago_minutes, $ago_hours), '</p>
3951
						<p>', sprintf($txt[$updatedTxt], $updated_seconds, $updated_minutes, $updated_hours), '</p>';
3952
3953
		if ($updated < 600)
3954
			echo '
3955
						<p>', $txt['upgrade_run_script'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_run_script2'], '</p>';
3956
3957
		if ($updated > $upcontext['inactive_timeout'])
3958
			echo '
3959
						<p>', $txt['upgrade_run'], '</p>';
3960
		elseif ($upcontext['inactive_timeout'] > 120)
3961
			echo '
3962
						<p>', sprintf($txt['upgrade_script_timeout_minutes'], $upcontext['user']['name'], round($upcontext['inactive_timeout'] / 60, 1)), '</p>';
3963
		else
3964
			echo '
3965
						<p>', sprintf($txt['upgrade_script_timeout_seconds'], $upcontext['user']['name'], $upcontext['inactive_timeout']), '</p>';
3966
3967
		echo '
3968
					</div>';
3969
	}
3970
3971
	echo '
3972
					<strong>', $txt['upgrade_admin_login'], ' ', $disable_security ? '(DISABLED)' : '', '</strong>
3973
					<h3>', $txt['upgrade_sec_login'], '</h3>
3974
					<dl class="settings adminlogin">
3975
						<dt>
3976
							<label for="user"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_username'], '</label>
3977
						</dt>
3978
						<dd>
3979
							<input type="text" name="user" value="', !empty($upcontext['username']) ? $upcontext['username'] : '', '"', $disable_security ? ' disabled' : '', '>';
3980
3981
	if (!empty($upcontext['username_incorrect']))
3982
		echo '
3983
							<div class="smalltext red">', $txt['upgrade_wrong_username'], '</div>';
3984
3985
	echo '
3986
						</dd>
3987
						<dt>
3988
							<label for="passwrd"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_password'], '</label>
3989
						</dt>
3990
						<dd>
3991
							<input type="password" name="passwrd" value=""', $disable_security ? ' disabled' : '', '>
3992
							<input type="hidden" name="hash_passwrd" value="">';
3993
3994
	if (!empty($upcontext['password_failed']))
3995
		echo '
3996
							<div class="smalltext red">', $txt['upgrade_wrong_password'], '</div>';
3997
3998
	echo '
3999
						</dd>';
4000
4001
	// Can they continue?
4002
	if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1)
4003
	{
4004
		echo '
4005
						<dd>
4006
							<label for="cont"><input type="checkbox" id="cont" name="cont" checked>', $txt['upgrade_continue_step'], '</label>
4007
						</dd>';
4008
	}
4009
4010
	echo '
4011
					</dl>
4012
					<span class="smalltext">
4013
						', $txt['upgrade_bypass'], '
4014
					</span>
4015
					<input type="hidden" name="login_attempt" id="login_attempt" value="1">
4016
					<input type="hidden" name="js_works" id="js_works" value="0">';
4017
4018
	// Say we want the continue button!
4019
	$upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1;
4020
4021
	// This defines whether javascript is going to work elsewhere :D
4022
	echo '
4023
					<script>
4024
						if (\'XMLHttpRequest\' in window && document.getElementById(\'js_works\'))
4025
							document.getElementById(\'js_works\').value = 1;
4026
4027
						// Latest version?
4028
						function smfCurrentVersion()
4029
						{
4030
							var smfVer, yourVer;
4031
4032
							if (!(\'smfVersion\' in window))
4033
								return;
4034
4035
							window.smfVersion = window.smfVersion.replace(/SMF\s?/g, \'\');
4036
4037
							smfVer = document.getElementById(\'smfVersion\');
4038
							yourVer = document.getElementById(\'yourVersion\');
4039
4040
							setInnerHTML(smfVer, window.smfVersion);
4041
4042
							var currentVersion = getInnerHTML(yourVer);
4043
							if (currentVersion < window.smfVersion)
4044
								document.getElementById(\'version_warning\').classList.remove(\'hidden\');
4045
						}
4046
						addLoadEvent(smfCurrentVersion);
4047
4048
						// This checks that the script file even exists!
4049
						if (typeof(smfSelectText) == \'undefined\')
4050
							document.getElementById(\'js_script_missing_error\').classList.remove(\'hidden\');
4051
4052
					</script>';
4053
}
4054
4055
function template_upgrade_options()
4056
{
4057
	global $upcontext, $modSettings, $db_prefix, $mmessage, $mtitle, $txt;
4058
4059
	echo '
4060
				<h3>', $txt['upgrade_areyouready'], '</h3>
4061
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">';
4062
4063
	// Warning message?
4064
	if (!empty($upcontext['upgrade_options_warning']))
4065
		echo '
4066
				<div class="errorbox">
4067
					<h3>', $txt['upgrade_warning'], '</h3>
4068
					', $upcontext['upgrade_options_warning'], '
4069
				</div>';
4070
4071
	echo '
4072
				<ul class="upgrade_settings">
4073
					<li>
4074
						<input type="checkbox" name="backup" id="backup" value="1">
4075
						<label for="backup">', $txt['upgrade_backup_table'], ' &quot;backup_' . $db_prefix . '&quot;.</label>
4076
						(', $txt['upgrade_recommended'], ')
4077
					</li>
4078
					<li>
4079
						<input type="checkbox" name="maint" id="maint" value="1" checked>
4080
						<label for="maint">', $txt['upgrade_maintenance'], '</label>
4081
						<span class="smalltext">(<a href="javascript:void(0)" onclick="document.getElementById(\'mainmess\').classList.toggle(\'hidden\')">', $txt['upgrade_customize'], '</a>)</span>
4082
						<div id="mainmess" class="hidden">
4083
							<strong class="smalltext">', $txt['upgrade_maintenance_title'], ' </strong><br>
4084
							<input type="text" name="maintitle" size="30" value="', htmlspecialchars($mtitle), '"><br>
4085
							<strong class="smalltext">', $txt['upgrade_maintenance_message'], ' </strong><br>
4086
							<textarea name="mainmessage" rows="3" cols="50">', htmlspecialchars($mmessage), '</textarea>
4087
						</div>
4088
					</li>
4089
					<li>
4090
						<input type="checkbox" name="debug" id="debug" value="1">
4091
						<label for="debug">'.$txt['upgrade_debug_info'], '</label>
4092
					</li>
4093
					<li>
4094
						<input type="checkbox" name="empty_error" id="empty_error" value="1">
4095
						<label for="empty_error">', $txt['upgrade_empty_errorlog'], '</label>
4096
					</li>';
4097
4098
	if (!empty($upcontext['karma_installed']['good']) || !empty($upcontext['karma_installed']['bad']))
4099
		echo '
4100
					<li>
4101
						<input type="checkbox" name="delete_karma" id="delete_karma" value="1">
4102
						<label for="delete_karma">', $txt['upgrade_delete_karma'], '</label>
4103
					</li>';
4104
4105
	echo '
4106
					<li>
4107
						<input type="checkbox" name="stats" id="stats" value="1"', empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']) ? '' : ' checked="checked"', '>
4108
						<label for="stat">
4109
							', $txt['upgrade_stats_collection'], '<br>
4110
							<span class="smalltext">', sprintf($txt['upgrade_stats_info'], 'https://www.simplemachines.org/about/stats.php'), '</a></span>
4111
						</label>
4112
					</li>
4113
					<li>
4114
						<input type="checkbox" name="migrateSettings" id="migrateSettings" value="1"', empty($upcontext['migrate_settings_recommended']) ? '' : ' checked="checked"', '>
4115
						<label for="migrateSettings">
4116
							', $txt['upgrade_migrate_settings_file'], '
4117
						</label>
4118
					</li>
4119
				</ul>
4120
				<input type="hidden" name="upcont" value="1">';
4121
4122
	// We need a normal continue button here!
4123
	$upcontext['continue'] = 1;
4124
}
4125
4126
// Template for the database backup tool/
4127
function template_backup_database()
4128
{
4129
	global $upcontext, $support_js, $is_debug, $txt;
4130
4131
	echo '
4132
				<h3>', $txt['upgrade_wait'], '</h3>';
4133
4134
	echo '
4135
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4136
					<input type="hidden" name="backup_done" id="backup_done" value="0">
4137
					<strong>', sprintf($txt['upgrade_completedtables_outof'], $upcontext['cur_table_num'], $upcontext['table_count']), '</strong>
4138
					<div id="debug_section">
4139
						<span id="debuginfo"></span>
4140
					</div>';
4141
4142
	// Dont any tables so far?
4143
	if (!empty($upcontext['previous_tables']))
4144
		foreach ($upcontext['previous_tables'] as $table)
4145
			echo '
4146
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4147
4148
	echo '
4149
					<h3 id="current_tab">
4150
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4151
					</h3>
4152
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">Backup Complete! Click Continue to Proceed.</p>';
4153
4154
	// Continue please!
4155
	$upcontext['continue'] = $support_js ? 2 : 1;
4156
4157
	// If javascript allows we want to do this using XML.
4158
	if ($support_js)
4159
	{
4160
		echo '
4161
					<script>
4162
						var lastTable = ', $upcontext['cur_table_num'], ';
4163
						function getNextTables()
4164
						{
4165
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4166
						}
4167
4168
						// Got an update!
4169
						function onBackupUpdate(oXMLDoc)
4170
						{
4171
							var sCurrentTableName = "";
4172
							var iTableNum = 0;
4173
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4174
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4175
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4176
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4177
4178
							// Update the page.
4179
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4180
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4181
							lastTable = iTableNum;
4182
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4183
4184
		// If debug flood the screen.
4185
		if ($is_debug)
4186
			echo '
4187
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4188
4189
							if (document.getElementById(\'debug_section\').scrollHeight)
4190
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4191
4192
		echo '
4193
							// Get the next update...
4194
							if (iTableNum == ', $upcontext['table_count'], ')
4195
							{
4196
								document.getElementById(\'commess\').classList.remove("hidden");
4197
								document.getElementById(\'current_tab\').classList.add("hidden");
4198
								document.getElementById(\'contbutt\').disabled = 0;
4199
								document.getElementById(\'backup_done\').value = 1;
4200
							}
4201
							else
4202
								getNextTables();
4203
						}
4204
						getNextTables();
4205
					//# sourceURL=dynamicScript-bkup.js
4206
					</script>';
4207
	}
4208
}
4209
4210
function template_backup_xml()
4211
{
4212
	global $upcontext;
4213
4214
	echo '
4215
		<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4216
}
4217
4218
// Here is the actual "make the changes" template!
4219
function template_database_changes()
4220
{
4221
	global $upcontext, $support_js, $is_debug, $timeLimitThreshold, $txt;
4222
4223
	if (empty($is_debug) && !empty($upcontext['upgrade_status']['debug']))
4224
		$is_debug = true;
4225
4226
	echo '
4227
				<h3>', $txt['upgrade_db_changes'], '</h3>
4228
				<h4><em>', $txt['upgrade_db_patient'], '</em></h4>';
4229
4230
	echo '
4231
				<form action="', $upcontext['form_url'], '&amp;filecount=', $upcontext['file_count'], '" name="upform" id="upform" method="post">
4232
					<input type="hidden" name="database_done" id="database_done" value="0">';
4233
4234
	// No javascript looks rubbish!
4235
	if (!$support_js)
4236
	{
4237
		foreach ($upcontext['actioned_items'] as $num => $item)
4238
		{
4239
			if ($num != 0)
4240
				echo ' Successful!';
4241
			echo '<br>' . $item;
4242
		}
4243
4244
		// Only tell deubbers how much time they wasted waiting for the upgrade because they don't have javascript.
4245
		if (!empty($upcontext['changes_complete']))
4246
		{
4247
			if ($is_debug)
4248
			{
4249
				$active = time() - $upcontext['started'];
4250
				$hours = floor($active / 3600);
4251
				$minutes = intval(($active / 60) % 60);
4252
				$seconds = intval($active % 60);
4253
4254
				echo '', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '<br>';
4255
			}
4256
			else
4257
				echo '', $txt['upgrade_success'], '<br>';
4258
4259
			echo '
4260
					<p id="commess">', $txt['upgrade_db_complete'], '</p>';
4261
		}
4262
	}
4263
	else
4264
	{
4265
		// Tell them how many files we have in total.
4266
		if ($upcontext['file_count'] > 1)
4267
			echo '
4268
					<strong id="info1">', $txt['upgrade_script'], ' <span id="file_done">', $upcontext['cur_file_num'], '</span> of ', $upcontext['file_count'], '.</strong>';
4269
4270
		echo '
4271
					<h3 id="info2">
4272
						<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>
4273
					</h3>
4274
					<p id="commess" class="', !empty($upcontext['changes_complete']) || $upcontext['current_debug_item_num'] == $upcontext['debug_items'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_db_complete2'], '</p>';
4275
4276
		if ($is_debug)
4277
		{
4278
			// Let our debuggers know how much time was spent, but not wasted since JS handled refreshing the page!
4279
			if ($upcontext['current_debug_item_num'] == $upcontext['debug_items'])
4280
			{
4281
				$active = time() - $upcontext['started'];
4282
				$hours = floor($active / 3600);
4283
				$minutes = intval(($active / 60) % 60);
4284
				$seconds = intval($active % 60);
4285
4286
				echo '
4287
					<p id="upgradeCompleted">', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '</p>';
4288
			}
4289
			else
4290
				echo '
4291
					<p id="upgradeCompleted"></p>';
4292
4293
			echo '
4294
					<div id="debug_section">
4295
						<span id="debuginfo"></span>
4296
					</div>';
4297
		}
4298
	}
4299
4300
	// Place for the XML error message.
4301
	echo '
4302
					<div id="error_block" class="errorbox', empty($upcontext['error_message']) ? ' hidden' : '', '">
4303
						<h3>', $txt['upgrade_error'], '</h3>
4304
						<div id="error_message">', isset($upcontext['error_message']) ? $upcontext['error_message'] : $txt['upgrade_unknown_error'], '</div>
4305
					</div>';
4306
4307
	// We want to continue at some point!
4308
	$upcontext['continue'] = $support_js ? 2 : 1;
4309
4310
	// If javascript allows we want to do this using XML.
4311
	if ($support_js)
4312
	{
4313
		echo '
4314
					<script>
4315
						var lastItem = ', $upcontext['current_debug_item_num'], ';
4316
						var sLastString = "', strtr($upcontext['current_debug_item_name'], array('"' => '&quot;')), '";
4317
						var iLastSubStepProgress = -1;
4318
						var curFile = ', $upcontext['cur_file_num'], ';
4319
						var totalItems = 0;
4320
						var prevFile = 0;
4321
						var retryCount = 0;
4322
						var testvar = 0;
4323
						var timeOutID = 0;
4324
						var getData = "";
4325
						var debugItems = ', $upcontext['debug_items'], ';';
4326
4327
		if ($is_debug)
4328
			echo '
4329
						var upgradeStartTime = ' . $upcontext['started'] . ';';
4330
4331
		echo '
4332
						function getNextItem()
4333
						{
4334
							// We want to track this...
4335
							if (timeOutID)
4336
								clearTimeout(timeOutID);
4337
							timeOutID = window.setTimeout("retTimeout()", ', (10 * $timeLimitThreshold), '000);
4338
4339
							getXMLDocument(\'', $upcontext['form_url'], '&xml&filecount=', $upcontext['file_count'], '&substep=\' + lastItem + getData, onItemUpdate);
4340
						}
4341
4342
						// Got an update!
4343
						function onItemUpdate(oXMLDoc)
4344
						{
4345
							var sItemName = "";
4346
							var sDebugName = "";
4347
							var iItemNum = 0;
4348
							var iSubStepProgress = -1;
4349
							var iDebugNum = 0;
4350
							var bIsComplete = 0;
4351
							var bSkipped = 0;
4352
							getData = "";
4353
4354
							// We\'ve got something - so reset the timeout!
4355
							if (timeOutID)
4356
								clearTimeout(timeOutID);
4357
4358
							// Assume no error at this time...
4359
							document.getElementById("error_block").classList.add("hidden");
4360
4361
							// Are we getting some duff info?
4362
							if (!oXMLDoc.getElementsByTagName("item")[0])
4363
							{
4364
								// Too many errors?
4365
								if (retryCount > 15)
4366
								{
4367
									document.getElementById("error_block").classList.remove("hidden");
4368
									setInnerHTML(document.getElementById("error_message"), "Error retrieving information on step: " + (sDebugName == "" ? sLastString : sDebugName));';
4369
4370
		if ($is_debug)
4371
			echo '
4372
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4373
4374
		echo '
4375
								}
4376
								else
4377
								{
4378
									retryCount++;
4379
									getNextItem();
4380
								}
4381
								return false;
4382
							}
4383
4384
							// Never allow loops.
4385
							if (curFile == prevFile)
4386
							{
4387
								retryCount++;
4388
								if (retryCount > 10)
4389
								{
4390
									document.getElementById("error_block").classList.remove("hidden");
4391
									setInnerHTML(document.getElementById("error_message"), "', $txt['upgrade_loop'], '" + sDebugName);';
4392
4393
		if ($is_debug)
4394
			echo '
4395
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4396
4397
		echo '
4398
								}
4399
							}
4400
							retryCount = 0;
4401
4402
							for (var i = 0; i < oXMLDoc.getElementsByTagName("item")[0].childNodes.length; i++)
4403
								sItemName += oXMLDoc.getElementsByTagName("item")[0].childNodes[i].nodeValue;
4404
							for (var i = 0; i < oXMLDoc.getElementsByTagName("debug")[0].childNodes.length; i++)
4405
								sDebugName += oXMLDoc.getElementsByTagName("debug")[0].childNodes[i].nodeValue;
4406
							for (var i = 0; i < oXMLDoc.getElementsByTagName("get").length; i++)
4407
							{
4408
								getData += "&" + oXMLDoc.getElementsByTagName("get")[i].getAttribute("key") + "=";
4409
								for (var j = 0; j < oXMLDoc.getElementsByTagName("get")[i].childNodes.length; j++)
4410
								{
4411
									getData += oXMLDoc.getElementsByTagName("get")[i].childNodes[j].nodeValue;
4412
								}
4413
							}
4414
4415
							iItemNum = oXMLDoc.getElementsByTagName("item")[0].getAttribute("num");
4416
							iDebugNum = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("num"));
4417
							bIsComplete = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("complete"));
4418
							bSkipped = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("skipped"));
4419
							iSubStepProgress = parseFloat(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("percent"));
4420
							sLastString = sDebugName + " (Item: " + iDebugNum + ")";
4421
4422
							curFile = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("num"));
4423
							debugItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("debug_items"));
4424
							totalItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("items"));
4425
4426
							// If we have an error we haven\'t completed!
4427
							if (oXMLDoc.getElementsByTagName("error")[0] && bIsComplete)
4428
								iDebugNum = lastItem;
4429
4430
							// Do we have the additional progress bar?
4431
							if (iSubStepProgress != -1)
4432
							{
4433
								document.getElementById("substep_bar_div").classList.remove("hidden");
4434
								document.getElementById("substep_progress").style.width = iSubStepProgress + "%";
4435
								setInnerHTML(document.getElementById("substep_text"), iSubStepProgress + "%");
4436
								setInnerHTML(document.getElementById("substep_name"), sDebugName.replace(/\./g, ""));
4437
							}
4438
							else
4439
							{
4440
								document.getElementById("substep_bar_div").classList.add("hidden");
4441
							}
4442
4443
							// Move onto the next item?
4444
							if (bIsComplete)
4445
								lastItem = iDebugNum;
4446
							else
4447
								lastItem = iDebugNum - 1;
4448
4449
							// Are we finished?
4450
							if (bIsComplete && iDebugNum == -1 && curFile >= ', $upcontext['file_count'], ')
4451
							{';
4452
4453
		// Database Changes, tell us how much time we spen to do this.  If this gets updated via JS.
4454
		if ($is_debug)
4455
			echo '
4456
								document.getElementById(\'debug_section\').classList.add("hidden");
4457
4458
								var upgradeFinishedTime = parseInt(oXMLDoc.getElementsByTagName("curtime")[0].childNodes[0].nodeValue);
4459
								var diffTime = upgradeFinishedTime - upgradeStartTime;
4460
								var diffHours = Math.floor(diffTime / 3600);
4461
								var diffMinutes = parseInt((diffTime / 60) % 60);
4462
								var diffSeconds = parseInt(diffTime % 60);
4463
4464
								var completedTxt = "', $txt['upgrade_success_time_db'], '";
4465
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4466
4467
								completedTxt = completedTxt.replace("%1$d", diffSeconds).replace("%2$d", diffMinutes).replace("%3$d", diffHours);
4468
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4469
								setInnerHTML(document.getElementById("upgradeCompleted"), completedTxt);';
4470
4471
		echo '
4472
4473
								document.getElementById(\'commess\').classList.remove("hidden");
4474
								document.getElementById(\'contbutt\').disabled = 0;
4475
								document.getElementById(\'database_done\').value = 1;';
4476
4477
		if ($upcontext['file_count'] > 1)
4478
			echo '
4479
								document.getElementById(\'info1\').classList.add(\'hidden\');';
4480
4481
		echo '
4482
								document.getElementById(\'info2\').classList.add(\'hidden\');
4483
								updateStepProgress(100, 100, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4484
								return true;
4485
							}
4486
							// Was it the last step in the file?
4487
							else if (bIsComplete && iDebugNum == -1)
4488
							{
4489
								lastItem = 0;
4490
								prevFile = curFile;';
4491
4492
		if ($is_debug)
4493
			echo '
4494
								setOuterHTML(document.getElementById(\'debuginfo\'), \'Moving to next script file...done<br><span id="debuginfo"><\' + \'/span>\');';
4495
4496
		echo '
4497
								getNextItem();
4498
								return true;
4499
							}';
4500
4501
		// If debug scroll the screen.
4502
		if ($is_debug)
4503
			echo '
4504
							if (iLastSubStepProgress == -1)
4505
							{
4506
								// Give it consistent dots.
4507
								dots = sDebugName.match(/\./g);
4508
								numDots = dots ? dots.length : 0;
4509
								for (var i = numDots; i < 3; i++)
4510
									sDebugName += ".";
4511
								setOuterHTML(document.getElementById(\'debuginfo\'), sDebugName + \'<span id="debuginfo"><\' + \'/span>\');
4512
							}
4513
							iLastSubStepProgress = iSubStepProgress;
4514
4515
							if (bIsComplete && bSkipped)
4516
								setOuterHTML(document.getElementById(\'debuginfo\'), \'skipped<br><span id="debuginfo"><\' + \'/span>\');
4517
							else if (bIsComplete)
4518
								setOuterHTML(document.getElementById(\'debuginfo\'), \'done<br><span id="debuginfo"><\' + \'/span>\');
4519
							else
4520
								setOuterHTML(document.getElementById(\'debuginfo\'), \'...<span id="debuginfo"><\' + \'/span>\');
4521
4522
							if (document.getElementById(\'debug_section\').scrollHeight)
4523
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4524
4525
		echo '
4526
							// Update the page.
4527
							setInnerHTML(document.getElementById(\'item_num\'), iItemNum);
4528
							setInnerHTML(document.getElementById(\'cur_item_name\'), sItemName);';
4529
4530
		if ($upcontext['file_count'] > 1)
4531
		{
4532
			echo '
4533
							setInnerHTML(document.getElementById(\'file_done\'), curFile);
4534
							setInnerHTML(document.getElementById(\'item_count\'), totalItems);';
4535
		}
4536
4537
		echo '
4538
							// Is there an error?
4539
							if (oXMLDoc.getElementsByTagName("error")[0])
4540
							{
4541
								var sErrorMsg = "";
4542
								for (var i = 0; i < oXMLDoc.getElementsByTagName("error")[0].childNodes.length; i++)
4543
									sErrorMsg += oXMLDoc.getElementsByTagName("error")[0].childNodes[i].nodeValue;
4544
								document.getElementById("error_block").classList.remove("hidden");
4545
								setInnerHTML(document.getElementById("error_message"), sErrorMsg);
4546
								return false;
4547
							}
4548
4549
							// Get the progress bar right.
4550
							barTotal = debugItems * ', $upcontext['file_count'], ';
4551
							barDone = (debugItems * (curFile - 1)) + lastItem;
4552
4553
							updateStepProgress(barDone, barTotal, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4554
4555
							// Finally - update the time here as it shows the server is responding!
4556
							curTime = new Date();
4557
							iElapsed = (curTime.getTime() / 1000 - ', $upcontext['started'], ');
4558
							mins = parseInt(iElapsed / 60);
4559
							secs = parseInt(iElapsed - mins * 60);
4560
							setInnerHTML(document.getElementById("mins_elapsed"), mins);
4561
							setInnerHTML(document.getElementById("secs_elapsed"), secs);
4562
4563
							getNextItem();
4564
							return true;
4565
						}
4566
4567
						// What if we timeout?!
4568
						function retTimeout(attemptAgain)
4569
						{
4570
							// Oh noes...
4571
							if (!attemptAgain)
4572
							{
4573
								document.getElementById("error_block").classList.remove("hidden");
4574
								setInnerHTML(document.getElementById("error_message"), "', sprintf($txt['upgrade_respondtime'], ($timeLimitThreshold * 10)), '" + "<a href=\"#\" onclick=\"retTimeout(true); return false;\">', $txt['upgrade_respondtime_clickhere'], '</a>");
4575
							}
4576
							else
4577
							{
4578
								document.getElementById("error_block").classList.add("hidden");
4579
								getNextItem();
4580
							}
4581
						}';
4582
4583
		// Start things off assuming we've not errored.
4584
		if (empty($upcontext['error_message']))
4585
			echo '
4586
						getNextItem();';
4587
4588
		echo '
4589
					//# sourceURL=dynamicScript-dbch.js
4590
					</script>';
4591
	}
4592
	return;
4593
}
4594
4595
function template_database_xml()
4596
{
4597
	global $is_debug, $upcontext;
4598
4599
	echo '
4600
	<file num="', $upcontext['cur_file_num'], '" items="', $upcontext['total_items'], '" debug_items="', $upcontext['debug_items'], '">', $upcontext['cur_file_name'], '</file>
4601
	<item num="', $upcontext['current_item_num'], '">', $upcontext['current_item_name'], '</item>
4602
	<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>';
4603
4604
	if (!empty($upcontext['error_message']))
4605
		echo '
4606
	<error>', $upcontext['error_message'], '</error>';
4607
4608
	if (!empty($upcontext['error_string']))
4609
		echo '
4610
	<sql>', $upcontext['error_string'], '</sql>';
4611
4612
	if ($is_debug)
4613
		echo '
4614
	<curtime>', time(), '</curtime>';
4615
}
4616
4617
// Template for the UTF-8 conversion step. Basically a copy of the backup stuff with slight modifications....
4618
function template_convert_utf8()
4619
{
4620
	global $upcontext, $support_js, $is_debug, $txt;
4621
4622
	echo '
4623
				<h3>', $txt['upgrade_wait2'], '</h3>
4624
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4625
					<input type="hidden" name="utf8_done" id="utf8_done" value="0">
4626
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4627
					<div id="debug_section">
4628
						<span id="debuginfo"></span>
4629
					</div>';
4630
4631
	// Done any tables so far?
4632
	if (!empty($upcontext['previous_tables']))
4633
		foreach ($upcontext['previous_tables'] as $table)
4634
			echo '
4635
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4636
4637
	echo '
4638
					<h3 id="current_tab">
4639
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4640
					</h3>';
4641
4642
	// If we dropped their index, let's let them know
4643
	if ($upcontext['dropping_index'])
4644
		echo '
4645
					<p id="indexmsg" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '>', $txt['upgrade_fulltext'], '</p>';
4646
4647
	// Completion notification
4648
	echo '
4649
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_conversion_proceed'], '</p>';
4650
4651
	// Continue please!
4652
	$upcontext['continue'] = $support_js ? 2 : 1;
4653
4654
	// If javascript allows we want to do this using XML.
4655
	if ($support_js)
4656
	{
4657
		echo '
4658
					<script>
4659
						var lastTable = ', $upcontext['cur_table_num'], ';
4660
						function getNextTables()
4661
						{
4662
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onConversionUpdate);
4663
						}
4664
4665
						// Got an update!
4666
						function onConversionUpdate(oXMLDoc)
4667
						{
4668
							var sCurrentTableName = "";
4669
							var iTableNum = 0;
4670
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4671
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4672
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4673
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4674
4675
							// Update the page.
4676
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4677
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4678
							lastTable = iTableNum;
4679
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4680
4681
		// If debug flood the screen.
4682
		if ($is_debug)
4683
			echo '
4684
						setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4685
4686
						if (document.getElementById(\'debug_section\').scrollHeight)
4687
							document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4688
4689
		echo '
4690
						// Get the next update...
4691
						if (iTableNum == ', $upcontext['table_count'], ')
4692
						{
4693
							document.getElementById(\'commess\').classList.remove(\'hidden\');
4694
							if (document.getElementById(\'indexmsg\') != null) {
4695
								document.getElementById(\'indexmsg\').classList.remove(\'hidden\');
4696
							}
4697
							document.getElementById(\'current_tab\').classList.add(\'hidden\');
4698
							document.getElementById(\'contbutt\').disabled = 0;
4699
							document.getElementById(\'utf8_done\').value = 1;
4700
						}
4701
						else
4702
							getNextTables();
4703
					}
4704
					getNextTables();
4705
				//# sourceURL=dynamicScript-conv.js
4706
				</script>';
4707
	}
4708
}
4709
4710
function template_convert_xml()
4711
{
4712
	global $upcontext;
4713
4714
	echo '
4715
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4716
}
4717
4718
// Template for the database backup tool/
4719
function template_serialize_json()
4720
{
4721
	global $upcontext, $support_js, $is_debug, $txt;
4722
4723
	echo '
4724
				<h3>', $txt['upgrade_convert_datajson'], '</h3>
4725
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4726
					<input type="hidden" name="json_done" id="json_done" value="0">
4727
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4728
					<div id="debug_section">
4729
						<span id="debuginfo"></span>
4730
					</div>';
4731
4732
	// Dont any tables so far?
4733
	if (!empty($upcontext['previous_tables']))
4734
		foreach ($upcontext['previous_tables'] as $table)
4735
			echo '
4736
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4737
4738
	echo '
4739
					<h3 id="current_tab">
4740
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4741
					</h3>
4742
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_json_completed'], '</p>';
4743
4744
	// Try to make sure substep was reset.
4745
	if ($upcontext['cur_table_num'] == $upcontext['table_count'])
4746
		echo '
4747
					<input type="hidden" name="substep" id="substep" value="0">';
4748
4749
	// Continue please!
4750
	$upcontext['continue'] = $support_js ? 2 : 1;
4751
4752
	// If javascript allows we want to do this using XML.
4753
	if ($support_js)
4754
	{
4755
		echo '
4756
					<script>
4757
						var lastTable = ', $upcontext['cur_table_num'], ';
4758
						function getNextTables()
4759
						{
4760
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4761
						}
4762
4763
						// Got an update!
4764
						function onBackupUpdate(oXMLDoc)
4765
						{
4766
							var sCurrentTableName = "";
4767
							var iTableNum = 0;
4768
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4769
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4770
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4771
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4772
4773
							// Update the page.
4774
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4775
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4776
							lastTable = iTableNum;
4777
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4778
4779
		// If debug flood the screen.
4780
		if ($is_debug)
4781
			echo '
4782
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4783
4784
							if (document.getElementById(\'debug_section\').scrollHeight)
4785
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4786
4787
		echo '
4788
							// Get the next update...
4789
							if (iTableNum == ', $upcontext['table_count'], ')
4790
							{
4791
								document.getElementById(\'commess\').classList.remove("hidden");
4792
								document.getElementById(\'current_tab\').classList.add("hidden");
4793
								document.getElementById(\'contbutt\').disabled = 0;
4794
								document.getElementById(\'json_done\').value = 1;
4795
							}
4796
							else
4797
								getNextTables();
4798
						}
4799
						getNextTables();
4800
					//# sourceURL=dynamicScript-json.js
4801
					</script>';
4802
	}
4803
}
4804
4805
function template_serialize_json_xml()
4806
{
4807
	global $upcontext;
4808
4809
	echo '
4810
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4811
}
4812
4813
function template_upgrade_complete()
4814
{
4815
	global $upcontext, $upgradeurl, $settings, $boardurl, $is_debug, $txt;
4816
4817
	echo '
4818
				<h3>', $txt['upgrade_done'], ' <a href="', $boardurl, '/index.php">', $txt['upgrade_done2'], '</a>.  ', $txt['upgrade_done3'], '</h3>
4819
				<form action="', $boardurl, '/index.php">';
4820
4821
	if (!empty($upcontext['can_delete_script']))
4822
		echo '
4823
					<label>
4824
						<input type="checkbox" id="delete_self" onclick="doTheDelete(this);"> ', $txt['upgrade_delete_now'], '
4825
					</label>
4826
					<em>', $txt['upgrade_delete_server'], '</em>
4827
					<script>
4828
						function doTheDelete(theCheck)
4829
						{
4830
							var theImage = document.getElementById ? document.getElementById("delete_upgrader") : document.all.delete_upgrader;
4831
							theImage.src = "', $upgradeurl, '?delete=1&ts_" + (new Date().getTime());
4832
							theCheck.disabled = true;
4833
						}
4834
					</script>
4835
					<img src="', $settings['default_theme_url'], '/images/blank.png" alt="" id="delete_upgrader"><br>';
4836
4837
	// Show Upgrade time in debug mode when we completed the upgrade process totally
4838
	if ($is_debug)
4839
	{
4840
		$active = time() - $upcontext['started'];
4841
		$hours = floor($active / 3600);
4842
		$minutes = intval(($active / 60) % 60);
4843
		$seconds = intval($active % 60);
4844
4845
		if ($hours > 0)
4846
			echo '', sprintf($txt['upgrade_completed_time_hms'], $seconds, $minutes, $hours), '';
4847
		elseif ($minutes > 0)
4848
			echo '', sprintf($txt['upgrade_completed_time_ms'], $seconds, $minutes), '';
4849
		elseif ($seconds > 0)
4850
			echo '', sprintf($txt['upgrade_completed_time_s'], $seconds), '';
4851
	}
4852
4853
	echo '
4854
					<p>
4855
						', sprintf($txt['upgrade_problems'], 'http://simplemachines.org'), '
4856
						<br>
4857
						', $txt['upgrade_luck'], '<br>
4858
						Simple Machines
4859
					</p>';
4860
}
4861
4862
/**
4863
 * Convert MySQL (var)char ip col to binary
4864
 *
4865
 * @param string $targetTable The table to perform the operation on
4866
 * @param string $oldCol The old column to gather data from
4867
 * @param string $newCol The new column to put data in
4868
 * @param int $limit The amount of entries to handle at once.
4869
 * @param int $setSize The amount of entries after which to update the database.
4870
 *
4871
 * newCol needs to be a varbinary(16) null able field
4872
 * @return bool
4873
 */
4874
function MySQLConvertOldIp($targetTable, $oldCol, $newCol, $limit = 50000, $setSize = 100)
4875
{
4876
	global $smcFunc, $step_progress;
4877
4878
	$current_substep = $_GET['substep'];
4879
4880
	if (empty($_GET['a']))
4881
		$_GET['a'] = 0;
4882
	$step_progress['name'] = 'Converting ips';
4883
	$step_progress['current'] = $_GET['a'];
4884
4885
	// Skip this if we don't have the column
4886
	$request = $smcFunc['db_query']('', '
4887
		SHOW FIELDS
4888
		FROM {db_prefix}{raw:table}
4889
		WHERE Field = {string:name}',
4890
		array(
4891
			'table' => $targetTable,
4892
			'name' => $oldCol,
4893
		)
4894
	);
4895
	if ($smcFunc['db_num_rows']($request) !== 1)
4896
	{
4897
		$smcFunc['db_free_result']($request);
4898
		return;
4899
	}
4900
	$smcFunc['db_free_result']($request);
4901
4902
	$is_done = false;
4903
	while (!$is_done)
4904
	{
4905
		// Keep looping at the current step.
4906
		nextSubstep($current_substep);
4907
4908
		// mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html
4909
		$arIp = array();
4910
4911
		$request = $smcFunc['db_query']('', '
4912
			SELECT DISTINCT {raw:old_col}
4913
			FROM {db_prefix}{raw:table_name}
4914
			WHERE {raw:new_col} IS NULL AND
4915
				{raw:old_col} != {string:unknown} AND
4916
				{raw:old_col} != {string:empty}
4917
			LIMIT {int:limit}',
4918
			array(
4919
				'old_col' => $oldCol,
4920
				'new_col' => $newCol,
4921
				'table_name' => $targetTable,
4922
				'empty' => '',
4923
				'limit' => $limit,
4924
				'unknown' => 'unknown',
4925
			)
4926
		);
4927
		while ($row = $smcFunc['db_fetch_assoc']($request))
4928
			$arIp[] = $row[$oldCol];
4929
4930
		$smcFunc['db_free_result']($request);
4931
4932
		// Special case, null ip could keep us in a loop.
4933
		if (is_null($arIp[0]))
4934
			unset($arIp[0]);
4935
4936
		if (empty($arIp))
4937
			$is_done = true;
4938
4939
		$updates = array();
4940
		$cases = array();
4941
		$count = count($arIp);
4942
		for ($i = 0; $i < $count; $i++)
4943
		{
4944
			$arIp[$i] = trim($arIp[$i]);
4945
4946
			if (empty($arIp[$i]))
4947
				continue;
4948
4949
			$updates['ip' . $i] = $arIp[$i];
4950
			$cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:ip' . $i . '}';
4951
4952
			if ($setSize > 0 && $i % $setSize === 0)
4953
			{
4954
				if (count($updates) == 1)
4955
					continue;
4956
4957
				$updates['whereSet'] = array_values($updates);
4958
				$smcFunc['db_query']('', '
4959
					UPDATE {db_prefix}' . $targetTable . '
4960
					SET ' . $newCol . ' = CASE ' .
4961
					implode('
4962
						', $cases) . '
4963
						ELSE NULL
4964
					END
4965
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
4966
					$updates
4967
				);
4968
4969
				$updates = array();
4970
				$cases = array();
4971
			}
4972
		}
4973
4974
		// Incase some extras made it through.
4975
		if (!empty($updates))
4976
		{
4977
			if (count($updates) == 1)
4978
			{
4979
				foreach ($updates as $key => $ip)
4980
				{
4981
					$smcFunc['db_query']('', '
4982
						UPDATE {db_prefix}' . $targetTable . '
4983
						SET ' . $newCol . ' = {inet:ip}
4984
						WHERE ' . $oldCol . ' = {string:ip}',
4985
						array(
4986
							'ip' => $ip
4987
						)
4988
					);
4989
				}
4990
			}
4991
			else
4992
			{
4993
				$updates['whereSet'] = array_values($updates);
4994
				$smcFunc['db_query']('', '
4995
					UPDATE {db_prefix}' . $targetTable . '
4996
					SET ' . $newCol . ' = CASE ' .
4997
					implode('
4998
						', $cases) . '
4999
						ELSE NULL
5000
					END
5001
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
5002
					$updates
5003
				);
5004
			}
5005
		}
5006
		else
5007
			$is_done = true;
5008
5009
		$_GET['a'] += $limit;
5010
		$step_progress['current'] = $_GET['a'];
5011
	}
5012
5013
	unset($_GET['a']);
5014
}
5015
5016
/**
5017
 * Get the column info. This is basically the same as smf_db_list_columns but we get 1 column, force detail and other checks.
5018
 *
5019
 * @param string $targetTable The table to perform the operation on
5020
 * @param string $column The column we are looking for.
5021
 *
5022
 * @return array Info on the table.
5023
 */
5024
function upgradeGetColumnInfo($targetTable, $column)
5025
{
5026
	global $smcFunc;
5027
5028
	// This should already be here, but be safe.
5029
	db_extend('packages');
5030
5031
	$columns = $smcFunc['db_list_columns']($targetTable, true);
5032
5033
	if (isset($columns[$column]))
5034
		return $columns[$column];
5035
	else
5036
		return null;
5037
}
5038
5039
?>