Passed
Push — release-2.1 ( 8c6d9c...8593cf )
by John
04:22 queued 10s
created

other/upgrade.php (1 issue)

Severity
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 RC3
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1 RC3');
16
define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION);
17
define('SMF_SOFTWARE_YEAR', '2020');
18
define('SMF_LANG_VERSION', '2.1 RC3');
19
define('SMF_INSTALLING', 1);
20
21
define('JQUERY_VERSION', '3.5.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("\n" . $txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
387
			flush();
388
			die();
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']));
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();
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;
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
	// This is now needed for loadUserSettings()
665
	$smcFunc['random_bytes'] = function($bytes)
666
	{
667
		global $sourcedir;
668
669
		if (!is_callable('random_bytes'))
670
			require_once($sourcedir . '/random_compat/random.php');
671
672
		return random_bytes($bytes);
673
	};
674
675
	// We need this for authentication and some upgrade code
676
	require_once($sourcedir . '/Subs-Auth.php');
677
	require_once($sourcedir . '/Class-Package.php');
678
679
	$smcFunc['strtolower'] = 'smf_strtolower';
680
681
	// Initialize everything...
682
	initialize_inputs();
683
684
	$utf8 = (empty($modSettings['global_character_set']) ? $txt['lang_character_set'] : $modSettings['global_character_set']) === 'UTF-8';
685
686
	// Get the database going!
687
	if (empty($db_type) || $db_type == 'mysqli')
688
	{
689
		$db_type = 'mysql';
690
		// If overriding $db_type, need to set its settings.php entry too
691
		$changes = array();
692
		$changes['db_type'] = 'mysql';
693
		require_once($sourcedir . '/Subs-Admin.php');
694
		updateSettingsFile($changes);
695
	}
696
697
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
698
	{
699
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
700
701
		// Make the connection...
702
		if (empty($db_connection))
703
		{
704
			$options = array('non_fatal' => true);
705
			// Add in the port if needed
706
			if (!empty($db_port))
707
				$options['port'] = $db_port;
708
709
			if (!empty($db_mb4))
710
				$options['db_mb4'] = $db_mb4;
711
712
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
713
		}
714
		else
715
			// If we've returned here, ping/reconnect to be safe
716
			$smcFunc['db_ping']($db_connection);
717
718
		// Oh dear god!!
719
		if ($db_connection === null)
720
		{
721
			// Get error info...  Recast just in case we get false or 0...
722
			$error_message = $smcFunc['db_connect_error']();
723
			if (empty($error_message))
724
				$error_message = '';
725
			$error_number = $smcFunc['db_connect_errno']();
726
			if (empty($error_number))
727
				$error_number = '';
728
			$db_error = (!empty($error_number) ? $error_number . ': ' : '') . $error_message;
729
730
			die($txt['error_db_connect_settings'] . '<br><br>' . $db_error);
0 ignored issues
show
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...
731
		}
732
733
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
734
			$smcFunc['db_query']('', '
735
				SET NAMES {string:db_character_set}',
736
				array(
737
					'db_error_skip' => true,
738
					'db_character_set' => $db_character_set,
739
				)
740
			);
741
742
		// Load the modSettings data...
743
		$request = $smcFunc['db_query']('', '
744
			SELECT variable, value
745
			FROM {db_prefix}settings',
746
			array(
747
				'db_error_skip' => true,
748
			)
749
		);
750
		$modSettings = array();
751
		while ($row = $smcFunc['db_fetch_assoc']($request))
752
			$modSettings[$row['variable']] = $row['value'];
753
		$smcFunc['db_free_result']($request);
754
	}
755
	else
756
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
757
758
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
759
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
760
	{
761
		require_once($sourcedir . '/QueryString.php');
762
		cleanRequest();
763
	}
764
765
	if (!isset($_GET['substep']))
766
		$_GET['substep'] = 0;
767
}
768
769
function initialize_inputs()
770
{
771
	global $start_time, $db_type;
772
773
	$start_time = time();
774
775
	umask(0);
776
777
	ob_start();
778
779
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
780
	ignore_user_abort(true);
781
782
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
783
	if (isset($_GET['delete']))
784
	{
785
		@unlink(__FILE__);
786
787
		// And the extra little files ;).
788
		@unlink(dirname(__FILE__) . '/upgrade_1-0.sql');
789
		@unlink(dirname(__FILE__) . '/upgrade_1-1.sql');
790
		@unlink(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
791
		@unlink(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
792
		@unlink(dirname(__FILE__) . '/upgrade-helper.php');
793
794
		$dh = opendir(dirname(__FILE__));
795
		while ($file = readdir($dh))
796
		{
797
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
798
				@unlink(dirname(__FILE__) . '/' . $file);
799
		}
800
		closedir($dh);
801
802
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
803
		// 1.1 Sources files not in 2.0+
804
		@unlink(dirname(__FILE__) . '/Sources/ModSettings.php');
805
		// 1.1 Templates that don't exist any more (e.g. renamed)
806
		@unlink(dirname(__FILE__) . '/Themes/default/Combat.template.php');
807
		@unlink(dirname(__FILE__) . '/Themes/default/Modlog.template.php');
808
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
809
		@unlink(dirname(__FILE__) . '/Themes/default/fader.js');
810
		@unlink(dirname(__FILE__) . '/Themes/default/script.js');
811
		@unlink(dirname(__FILE__) . '/Themes/default/spellcheck.js');
812
		@unlink(dirname(__FILE__) . '/Themes/default/xml_board.js');
813
		@unlink(dirname(__FILE__) . '/Themes/default/xml_topic.js');
814
815
		// 2.0 Sources files not in 2.1+
816
		@unlink(dirname(__FILE__) . '/Sources/DumpDatabase.php');
817
		@unlink(dirname(__FILE__) . '/Sources/LockTopic.php');
818
819
		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');
820
		exit;
821
	}
822
823
	// Something is causing this to happen, and it's annoying.  Stop it.
824
	$temp = 'upgrade_php?step';
825
	while (strlen($temp) > 4)
826
	{
827
		if (isset($_GET[$temp]))
828
			unset($_GET[$temp]);
829
		$temp = substr($temp, 1);
830
	}
831
832
	// Force a step, defaulting to 0.
833
	$_GET['step'] = (int) @$_GET['step'];
834
	$_GET['substep'] = (int) @$_GET['substep'];
835
}
836
837
// Step 0 - Let's welcome them in and ask them to login!
838
function WelcomeLogin()
839
{
840
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
841
	global $smcFunc, $db_type, $databases, $boardurl;
842
843
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
844
	global $txt;
845
846
	$upcontext['sub_template'] = 'welcome_message';
847
848
	// Check for some key files - one template, one language, and a new and an old source file.
849
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
850
		&& @file_exists($sourcedir . '/QueryString.php')
851
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
852
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
853
854
	// Need legacy scripts?
855
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
856
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
857
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
858
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
859
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
860
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
861
862
	// We don't need "-utf8" files anymore...
863
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
864
865
	if (!$check)
866
		// 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.
867
		return throw_error($txt['error_upgrade_files_missing']);
868
869
	// Do they meet the install requirements?
870
	if (!php_version_check())
871
		return throw_error($txt['error_php_too_low']);
872
873
	if (!db_version_check())
874
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
875
876
	// Do some checks to make sure they have proper privileges
877
	db_extend('packages');
878
879
	// CREATE
880
	$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');
881
882
	// ALTER
883
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
884
885
	// DROP
886
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
887
888
	// Sorry... we need CREATE, ALTER and DROP
889
	if (!$create || !$alter || !$drop)
890
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
891
892
	// Do a quick version spot check.
893
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
894
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
895
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
896
		return throw_error($txt['error_upgrade_old_files']);
897
898
	// What absolutely needs to be writable?
899
	$writable_files = array(
900
		$boarddir . '/Settings.php',
901
		$boarddir . '/Settings_bak.php',
902
	);
903
904
	// Only check for minified writable files if we have it enabled or not set.
905
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
906
		$writable_files += array(
907
			$modSettings['theme_dir'] . '/css/minified.css',
908
			$modSettings['theme_dir'] . '/scripts/minified.js',
909
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
910
		);
911
912
	// Do we need to add this setting?
913
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
914
915
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
916
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
917
918
	// This little fellow has to cooperate...
919
	quickFileWritable($custom_av_dir);
920
921
	// Are we good now?
922
	if (!is_writable($custom_av_dir))
923
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
924
	elseif ($need_settings_update)
925
	{
926
		if (!function_exists('cache_put_data'))
927
			require_once($sourcedir . '/Load.php');
928
929
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
930
		updateSettings(array('custom_avatar_url' => $custom_av_url));
931
	}
932
933
	require_once($sourcedir . '/Security.php');
934
935
	// Check the cache directory.
936
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
937
	if (!file_exists($cachedir_temp))
938
		@mkdir($cachedir_temp);
939
940
	if (!file_exists($cachedir_temp))
941
		return throw_error($txt['error_cache_not_found']);
942
943
	quickFileWritable($cachedir_temp . '/db_last_error.php');
944
945
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
946
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
947
	elseif (!isset($_GET['skiplang']))
948
	{
949
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
950
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
951
952
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
953
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
954
	}
955
956
	if (!makeFilesWritable($writable_files))
957
		return false;
958
959
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
960
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
961
		return throw_error($txt['error_agreement_not_writable']);
962
963
	// Upgrade the agreement.
964
	elseif (isset($modSettings['agreement']))
965
	{
966
		$fp = fopen($boarddir . '/agreement.txt', 'w');
967
		fwrite($fp, $modSettings['agreement']);
968
		fclose($fp);
969
	}
970
971
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
972
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr(dirname(__FILE__), array('/' => '', '\\' => '')))
973
		$upcontext['warning'] = '
974
			' . sprintf($txt['upgrade_boarddir_settings'], $boarddir, dirname(__FILE__)) . '<br>
975
			<ul>
976
				<li>' . $txt['upgrade_boarddir'] . '  ' . $boarddir . '</li>
977
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
978
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
979
			</ul>
980
			' . $txt['upgrade_incorrect_settings'] . '';
981
982
	// Confirm mbstring is loaded...
983
	if (!extension_loaded('mbstring'))
984
		return throw_error($txt['install_no_mbstring']);
985
986
	// Check for https stream support.
987
	$supported_streams = stream_get_wrappers();
988
	if (!in_array('https', $supported_streams))
989
		$upcontext['custom_warning'] = $txt['install_no_https'];
990
991
	// Either we're logged in or we're going to present the login.
992
	if (checkLogin())
993
		return true;
994
995
	$upcontext += createToken('login');
996
997
	return false;
998
}
999
1000
// Step 0.5: Does the login work?
1001
function checkLogin()
1002
{
1003
	global $modSettings, $upcontext, $disable_security;
1004
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
1005
1006
	// Are we trying to login?
1007
	if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security))
1008
	{
1009
		// If we've disabled security pick a suitable name!
1010
		if (empty($_POST['user']))
1011
			$_POST['user'] = 'Administrator';
1012
1013
		// Before 2.0 these column names were different!
1014
		$oldDB = false;
1015
		if (empty($db_type) || $db_type == 'mysql')
1016
		{
1017
			$request = $smcFunc['db_query']('', '
1018
				SHOW COLUMNS
1019
				FROM {db_prefix}members
1020
				LIKE {string:member_name}',
1021
				array(
1022
					'member_name' => 'memberName',
1023
					'db_error_skip' => true,
1024
				)
1025
			);
1026
			if ($smcFunc['db_num_rows']($request) != 0)
1027
				$oldDB = true;
1028
			$smcFunc['db_free_result']($request);
1029
		}
1030
1031
		// Get what we believe to be their details.
1032
		if (!$disable_security)
1033
		{
1034
			if ($oldDB)
1035
				$request = $smcFunc['db_query']('', '
1036
					SELECT id_member, memberName AS member_name, passwd, id_group,
1037
						additionalGroups AS additional_groups, lngfile
1038
					FROM {db_prefix}members
1039
					WHERE memberName = {string:member_name}',
1040
					array(
1041
						'member_name' => $_POST['user'],
1042
						'db_error_skip' => true,
1043
					)
1044
				);
1045
			else
1046
				$request = $smcFunc['db_query']('', '
1047
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
1048
					FROM {db_prefix}members
1049
					WHERE member_name = {string:member_name}',
1050
					array(
1051
						'member_name' => $_POST['user'],
1052
						'db_error_skip' => true,
1053
					)
1054
				);
1055
			if ($smcFunc['db_num_rows']($request) != 0)
1056
			{
1057
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1058
1059
				$groups = explode(',', $addGroups);
1060
				$groups[] = $id_group;
1061
1062
				foreach ($groups as $k => $v)
1063
					$groups[$k] = (int) $v;
1064
1065
				$sha_passwd = sha1(strtolower($name) . un_htmlspecialchars($_REQUEST['passwrd']));
1066
1067
				// We don't use "-utf8" anymore...
1068
				$user_language = str_ireplace('-utf8', '', $user_language);
1069
			}
1070
			else
1071
				$upcontext['username_incorrect'] = true;
1072
1073
			$smcFunc['db_free_result']($request);
1074
		}
1075
		$upcontext['username'] = $_POST['user'];
1076
1077
		// Track whether javascript works!
1078
		if (isset($_POST['js_works']))
1079
		{
1080
			if (!empty($_POST['js_works']))
1081
			{
1082
				$upcontext['upgrade_status']['js'] = 1;
1083
				$support_js = 1;
1084
			}
1085
			else
1086
				$support_js = 0;
1087
		}
1088
1089
		// Note down the version we are coming from.
1090
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1091
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1092
1093
		// Didn't get anywhere?
1094
		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']))
1095
		{
1096
			// MD5?
1097
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1098
			if ($md5pass != $password)
1099
			{
1100
				$upcontext['password_failed'] = true;
1101
				// Disable the hashing this time.
1102
				$upcontext['disable_login_hashing'] = true;
1103
			}
1104
		}
1105
1106
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1107
		{
1108
			// Set the password.
1109
			if (!$disable_security)
1110
			{
1111
				// Do we actually have permission?
1112
				if (!in_array(1, $groups))
1113
				{
1114
					$request = $smcFunc['db_query']('', '
1115
						SELECT permission
1116
						FROM {db_prefix}permissions
1117
						WHERE id_group IN ({array_int:groups})
1118
							AND permission = {string:admin_forum}',
1119
						array(
1120
							'groups' => $groups,
1121
							'admin_forum' => 'admin_forum',
1122
							'db_error_skip' => true,
1123
						)
1124
					);
1125
					if ($smcFunc['db_num_rows']($request) == 0)
1126
						return throw_error($txt['error_not_admin']);
1127
					$smcFunc['db_free_result']($request);
1128
				}
1129
1130
				$upcontext['user']['id'] = $id_member;
1131
				$upcontext['user']['name'] = $name;
1132
			}
1133
			else
1134
			{
1135
				$upcontext['user']['id'] = 1;
1136
				$upcontext['user']['name'] = 'Administrator';
1137
			}
1138
1139
			if (!is_callable('random_int'))
1140
				require_once('Sources/random_compat/random.php');
1141
1142
			$upcontext['user']['pass'] = random_int(0, 60000);
1143
			// This basically is used to match the GET variables to Settings.php.
1144
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1145
1146
			// Set the language to that of the user?
1147
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1148
			{
1149
				$user_language = basename($user_language, '.lng');
1150
				$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
1151
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1152
1153
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1154
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1155
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1156
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1157
				else
1158
				{
1159
					// Set this as the new language.
1160
					$upcontext['language'] = $user_language;
1161
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1162
1163
					// Include the file.
1164
					load_lang_file();
1165
				}
1166
			}
1167
1168
			// If we're resuming set the step and substep to be correct.
1169
			if (isset($_POST['cont']))
1170
			{
1171
				$upcontext['current_step'] = $upcontext['user']['step'];
1172
				$_GET['substep'] = $upcontext['user']['substep'];
1173
			}
1174
1175
			return true;
1176
		}
1177
	}
1178
1179
	return false;
1180
}
1181
1182
// Step 1: Do the maintenance and backup.
1183
function UpgradeOptions()
1184
{
1185
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1186
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1187
1188
	$upcontext['sub_template'] = 'upgrade_options';
1189
	$upcontext['page_title'] = $txt['upgrade_options'];
1190
1191
	db_extend('packages');
1192
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1193
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1194
1195
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1196
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1197
1198
	$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', '<');
1199
1200
	unset($member_columns);
1201
1202
	// If we've not submitted then we're done.
1203
	if (empty($_POST['upcont']))
1204
		return false;
1205
1206
	// Firstly, if they're enabling SM stat collection just do it.
1207
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1208
	{
1209
		$upcontext['allow_sm_stats'] = true;
1210
1211
		// Don't register if we still have a key.
1212
		if (empty($modSettings['sm_stats_key']))
1213
		{
1214
			// Attempt to register the site etc.
1215
			$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1216
			if ($fp)
1217
			{
1218
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1219
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1220
				$out .= 'Connection: Close' . "\r\n\r\n";
1221
				fwrite($fp, $out);
1222
1223
				$return_data = '';
1224
				while (!feof($fp))
1225
					$return_data .= fgets($fp, 128);
1226
1227
				fclose($fp);
1228
1229
				// Get the unique site ID.
1230
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1231
1232
				if (!empty($ID[1]))
1233
					$smcFunc['db_insert']('replace',
1234
						$db_prefix . 'settings',
1235
						array('variable' => 'string', 'value' => 'string'),
1236
						array(
1237
							array('sm_stats_key', $ID[1]),
1238
							array('enable_sm_stats', 1),
1239
						),
1240
						array('variable')
1241
					);
1242
			}
1243
		}
1244
		else
1245
		{
1246
			$smcFunc['db_insert']('replace',
1247
				$db_prefix . 'settings',
1248
				array('variable' => 'string', 'value' => 'string'),
1249
				array('enable_sm_stats', 1),
1250
				array('variable')
1251
			);
1252
		}
1253
	}
1254
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1255
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1256
		$smcFunc['db_query']('', '
1257
			DELETE FROM {db_prefix}settings
1258
			WHERE variable = {string:enable_sm_stats}',
1259
			array(
1260
				'enable_sm_stats' => 'enable_sm_stats',
1261
				'db_error_skip' => true,
1262
			)
1263
		);
1264
1265
	// Deleting old karma stuff?
1266
	if (!empty($_POST['delete_karma']))
1267
	{
1268
		// Delete old settings vars.
1269
		$smcFunc['db_query']('', '
1270
			DELETE FROM {db_prefix}settings
1271
			WHERE variable IN ({array_string:karma_vars})',
1272
			array(
1273
				'karma_vars' => array('karmaMode', 'karmaTimeRestrictAdmins', 'karmaWaitTime', 'karmaMinPosts', 'karmaLabel', 'karmaSmiteLabel', 'karmaApplaudLabel'),
1274
			)
1275
		);
1276
1277
		// Cleaning up old karma member settings.
1278
		if ($upcontext['karma_installed']['good'])
1279
			$smcFunc['db_query']('', '
1280
				ALTER TABLE {db_prefix}members
1281
				DROP karma_good',
1282
				array()
1283
			);
1284
1285
		// Does karma bad was enable?
1286
		if ($upcontext['karma_installed']['bad'])
1287
			$smcFunc['db_query']('', '
1288
				ALTER TABLE {db_prefix}members
1289
				DROP karma_bad',
1290
				array()
1291
			);
1292
1293
		// Cleaning up old karma permissions.
1294
		$smcFunc['db_query']('', '
1295
			DELETE FROM {db_prefix}permissions
1296
			WHERE permission = {string:karma_vars}',
1297
			array(
1298
				'karma_vars' => 'karma_edit',
1299
			)
1300
		);
1301
		// Cleaning up old log_karma table
1302
		$smcFunc['db_query']('', '
1303
			DROP TABLE IF EXISTS {db_prefix}log_karma',
1304
			array()
1305
		);
1306
	}
1307
1308
	// Emptying the error log?
1309
	if (!empty($_POST['empty_error']))
1310
		$smcFunc['db_query']('truncate_table', '
1311
			TRUNCATE {db_prefix}log_errors',
1312
			array(
1313
			)
1314
		);
1315
1316
	$changes = array();
1317
1318
	// Add proxy settings.
1319
	if (!isset($GLOBALS['image_proxy_secret']) || $GLOBALS['image_proxy_secret'] == 'smfisawesome')
1320
		$changes['image_proxy_secret'] = substr(sha1(mt_rand()), 0, 20);
1321
	if (!isset($GLOBALS['image_proxy_maxsize']))
1322
		$changes['image_proxy_maxsize'] = 5190;
1323
	if (!isset($GLOBALS['image_proxy_enabled']))
1324
		$changes['image_proxy_enabled'] = false;
1325
1326
	// If $boardurl reflects https, set force_ssl
1327
	if (!function_exists('cache_put_data'))
1328
		require_once($sourcedir . '/Load.php');
1329
	if (stripos($boardurl, 'https://') !== false)
1330
		updateSettings(array('force_ssl' => '1'));
1331
1332
	// If we're overriding the language follow it through.
1333
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1334
		$changes['language'] = $upcontext['lang'];
1335
1336
	if (!empty($_POST['maint']))
1337
	{
1338
		$changes['maintenance'] = 2;
1339
		// Remember what it was...
1340
		$upcontext['user']['main'] = $maintenance;
1341
1342
		if (!empty($_POST['maintitle']))
1343
		{
1344
			$changes['mtitle'] = $_POST['maintitle'];
1345
			$changes['mmessage'] = $_POST['mainmessage'];
1346
		}
1347
		else
1348
		{
1349
			$changes['mtitle'] = $txt['mtitle'];
1350
			$changes['mmessage'] = $txt['mmessage'];
1351
		}
1352
	}
1353
1354
	if ($command_line)
1355
		echo ' * Updating Settings.php...';
1356
1357
	// Fix some old paths.
1358
	if (substr($boarddir, 0, 1) == '.')
1359
		$changes['boarddir'] = fixRelativePath($boarddir);
1360
1361
	if (substr($sourcedir, 0, 1) == '.')
1362
		$changes['sourcedir'] = fixRelativePath($sourcedir);
1363
1364
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1365
		$changes['cachedir'] = fixRelativePath($boarddir) . '/cache';
1366
1367
	// Migrate cache settings.
1368
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1369
	if (!isset($GLOBALS['cache_enable']))
1370
		$changes += array(
1371
			'cache_accelerator' => !empty($modSettings['cache_enable']) ? 'smf' : '',
1372
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1373
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? $modSettings['cache_memcached'] : '',
1374
		);
1375
1376
	// If they have a "host:port" setup for the host, split that into separate values
1377
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1378
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1379
	{
1380
		list ($db_server, $db_port) = explode(':', $db_server);
1381
1382
		$changes['db_server'] = $db_server;
1383
1384
		// Only set this if we're not using the default port
1385
		if ($db_port != ini_get('mysqli.default_port'))
1386
			$changes['db_port'] = (int) $db_port;
1387
	}
1388
1389
	// If db_port is set and is the same as the default, set it to 0.
1390
	if (!empty($db_port))
1391
	{
1392
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1393
			$changes['db_port'] = 0;
1394
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1395
			$changes['db_port'] = 0;
1396
	}
1397
1398
	// Maybe we haven't had this option yet?
1399
	if (empty($packagesdir))
1400
		$changes['packagesdir'] = fixRelativePath($boarddir) . '/Packages';
1401
1402
	// Add support for $tasksdir var.
1403
	if (empty($tasksdir))
1404
		$changes['tasksdir'] = fixRelativePath($sourcedir) . '/tasks';
1405
1406
	// Make sure we fix the language as well.
1407
	if (stristr($language, '-utf8'))
1408
		$changes['language'] = str_ireplace('-utf8', '', $language);
1409
1410
	// @todo Maybe change the cookie name if going to 1.1, too?
1411
1412
	// Ensure this doesn't get lost in translation.
1413
	$changes['upgradeData'] = base64_encode(json_encode($upcontext['user']));
1414
1415
	// Update Settings.php with the new settings, and rebuild if they selected that option.
1416
	require_once($sourcedir . '/Subs.php');
1417
	require_once($sourcedir . '/Subs-Admin.php');
1418
	updateSettingsFile($changes, false, !empty($_POST['migrateSettings']));
1419
1420
	if ($command_line)
1421
		echo ' Successful.' . "\n";
1422
1423
	// Are we doing debug?
1424
	if (isset($_POST['debug']))
1425
	{
1426
		$upcontext['upgrade_status']['debug'] = true;
1427
		$is_debug = true;
1428
	}
1429
1430
	// If we're not backing up then jump one.
1431
	if (empty($_POST['backup']))
1432
		$upcontext['current_step']++;
1433
1434
	// If we've got here then let's proceed to the next step!
1435
	return true;
1436
}
1437
1438
// Backup the database - why not...
1439
function BackupDatabase()
1440
{
1441
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1442
1443
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1444
	$upcontext['page_title'] = $txt['backup_database'];
1445
1446
	// Done it already - js wise?
1447
	if (!empty($_POST['backup_done']))
1448
		return true;
1449
1450
	// Some useful stuff here.
1451
	db_extend();
1452
1453
	// Might need this as well
1454
	db_extend('packages');
1455
1456
	// Get all the table names.
1457
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1458
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1459
	$tables = $smcFunc['db_list_tables']($db, $filter);
1460
1461
	$table_names = array();
1462
	foreach ($tables as $table)
1463
		if (substr($table, 0, 7) !== 'backup_')
1464
			$table_names[] = $table;
1465
1466
	$upcontext['table_count'] = count($table_names);
1467
	$upcontext['cur_table_num'] = $_GET['substep'];
1468
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1469
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1470
	// For non-java auto submit...
1471
	$file_steps = $upcontext['table_count'];
1472
1473
	// What ones have we already done?
1474
	foreach ($table_names as $id => $table)
1475
		if ($id < $_GET['substep'])
1476
			$upcontext['previous_tables'][] = $table;
1477
1478
	if ($command_line)
1479
		echo 'Backing Up Tables.';
1480
1481
	// If we don't support javascript we backup here.
1482
	if (!$support_js || isset($_GET['xml']))
1483
	{
1484
		// Backup each table!
1485
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1486
		{
1487
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1488
			$upcontext['cur_table_num'] = $substep + 1;
1489
1490
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1491
1492
			// Do we need to pause?
1493
			nextSubstep($substep);
1494
1495
			backupTable($table_names[$substep]);
1496
1497
			// If this is XML to keep it nice for the user do one table at a time anyway!
1498
			if (isset($_GET['xml']))
1499
				return upgradeExit();
1500
		}
1501
1502
		if ($command_line)
1503
		{
1504
			echo "\n" . ' Successful.\'' . "\n";
1505
			flush();
1506
		}
1507
		$upcontext['step_progress'] = 100;
1508
1509
		$_GET['substep'] = 0;
1510
		// Make sure we move on!
1511
		return true;
1512
	}
1513
1514
	// Either way next place to post will be database changes!
1515
	$_GET['substep'] = 0;
1516
	return false;
1517
}
1518
1519
// Backup one table...
1520
function backupTable($table)
1521
{
1522
	global $command_line, $db_prefix, $smcFunc;
1523
1524
	if ($command_line)
1525
	{
1526
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1527
		flush();
1528
	}
1529
1530
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1531
1532
	if ($command_line)
1533
		echo ' done.';
1534
}
1535
1536
// Step 2: Everything.
1537
function DatabaseChanges()
1538
{
1539
	global $db_prefix, $modSettings, $smcFunc, $txt;
1540
	global $upcontext, $support_js, $db_type, $boarddir;
1541
1542
	// Have we just completed this?
1543
	if (!empty($_POST['database_done']))
1544
		return true;
1545
1546
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1547
	$upcontext['page_title'] = $txt['database_changes'];
1548
1549
	// All possible files.
1550
	// Name, < version, insert_on_complete
1551
	// Last entry in array indicates whether to use sql_mode of STRICT or not.
1552
	$files = array(
1553
		array('upgrade_1-0.sql', '1.1', '1.1 RC0', false),
1554
		array('upgrade_1-1.sql', '2.0', '2.0 a', false),
1555
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0', false),
1556
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION, true),
1557
	);
1558
1559
	// How many files are there in total?
1560
	if (isset($_GET['filecount']))
1561
		$upcontext['file_count'] = (int) $_GET['filecount'];
1562
	else
1563
	{
1564
		$upcontext['file_count'] = 0;
1565
		foreach ($files as $file)
1566
		{
1567
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1568
				$upcontext['file_count']++;
1569
		}
1570
	}
1571
1572
	// Do each file!
1573
	$did_not_do = count($files) - $upcontext['file_count'];
1574
	$upcontext['step_progress'] = 0;
1575
	$upcontext['cur_file_num'] = 0;
1576
	foreach ($files as $file)
1577
	{
1578
		if ($did_not_do)
1579
			$did_not_do--;
1580
		else
1581
		{
1582
			$upcontext['cur_file_num']++;
1583
			$upcontext['cur_file_name'] = $file[0];
1584
			// Do we actually need to do this still?
1585
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1586
			{
1587
				// Use STRICT mode on more recent steps
1588
				setSqlMode($file[3]);
1589
1590
				// Reload modSettings to capture any adds/updates made along the way
1591
				$request = $smcFunc['db_query']('', '
1592
					SELECT variable, value
1593
					FROM {db_prefix}settings',
1594
					array(
1595
						'db_error_skip' => true,
1596
					)
1597
				);
1598
1599
				$modSettings = array();
1600
				while ($row = $smcFunc['db_fetch_assoc']($request))
1601
					$modSettings[$row['variable']] = $row['value'];
1602
1603
				$smcFunc['db_free_result']($request);
1604
1605
				// Some theme settings are in $modSettings
1606
				// Note we still might be doing yabbse (no smf ver)
1607
				if (isset($modSettings['smfVersion']))
1608
				{
1609
					$request = $smcFunc['db_query']('', '
1610
						SELECT variable, value
1611
						FROM {db_prefix}themes
1612
						WHERE id_theme = {int:id_theme}
1613
							AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
1614
						array(
1615
							'id_theme' => 1,
1616
							'theme_url' => 'theme_url',
1617
							'theme_dir' => 'theme_dir',
1618
							'images_url' => 'images_url',
1619
							'db_error_skip' => true,
1620
						)
1621
					);
1622
1623
					while ($row = $smcFunc['db_fetch_assoc']($request))
1624
						$modSettings[$row['variable']] = $row['value'];
1625
1626
					$smcFunc['db_free_result']($request);
1627
				}
1628
1629
				if (!isset($modSettings['theme_url']))
1630
				{
1631
					$modSettings['theme_dir'] = $boarddir . '/Themes/default';
1632
					$modSettings['theme_url'] = 'Themes/default';
1633
					$modSettings['images_url'] = 'Themes/default/images';
1634
				}
1635
1636
				// Now process the file...
1637
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1638
				if ($nextFile)
1639
				{
1640
					// Only update the version of this if complete.
1641
					$smcFunc['db_insert']('replace',
1642
						$db_prefix . 'settings',
1643
						array('variable' => 'string', 'value' => 'string'),
1644
						array('smfVersion', $file[2]),
1645
						array('variable')
1646
					);
1647
1648
					$modSettings['smfVersion'] = $file[2];
1649
				}
1650
1651
				// If this is XML we only do this stuff once.
1652
				if (isset($_GET['xml']))
1653
				{
1654
					// Flag to move on to the next.
1655
					$upcontext['completed_step'] = true;
1656
					// Did we complete the whole file?
1657
					if ($nextFile)
1658
						$upcontext['current_debug_item_num'] = -1;
1659
					return upgradeExit();
1660
				}
1661
				elseif ($support_js)
1662
					break;
1663
			}
1664
			// Set the progress bar to be right as if we had - even if we hadn't...
1665
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1666
		}
1667
	}
1668
1669
	$_GET['substep'] = 0;
1670
	// So the template knows we're done.
1671
	if (!$support_js)
1672
	{
1673
		$upcontext['changes_complete'] = true;
1674
1675
		return true;
1676
	}
1677
	return false;
1678
}
1679
1680
// Different versions of the files use different sql_modes
1681
function setSqlMode($strict = true)
1682
{
1683
	global $db_type, $db_connection;
1684
1685
	if ($db_type != 'mysql')
1686
		return;
1687
1688
	if ($strict)
1689
		$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';
1690
	else
1691
		$mode = '';
1692
1693
	mysqli_query($db_connection, 'SET SESSION sql_mode = \'' . $mode . '\'');
1694
1695
	return;
1696
}
1697
1698
// Delete the damn thing!
1699
function DeleteUpgrade()
1700
{
1701
	global $command_line, $language, $upcontext, $sourcedir;
1702
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1703
1704
	// Now it's nice to have some of the basic SMF source files.
1705
	if (!isset($_GET['ssi']) && !$command_line)
1706
		redirectLocation('&ssi=1');
1707
1708
	$upcontext['sub_template'] = 'upgrade_complete';
1709
	$upcontext['page_title'] = $txt['upgrade_complete'];
1710
1711
	$endl = $command_line ? "\n" : '<br>' . "\n";
1712
1713
	$changes = array(
1714
		'language' => (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language),
1715
		'db_error_send' => true,
1716
		'upgradeData' => null,
1717
	);
1718
1719
	// Are we in maintenance mode?
1720
	if (isset($upcontext['user']['main']))
1721
	{
1722
		if ($command_line)
1723
			echo ' * ';
1724
		$upcontext['removed_maintenance'] = true;
1725
		$changes['maintenance'] = $upcontext['user']['main'];
1726
	}
1727
	// Otherwise if somehow we are in 2 let's go to 1.
1728
	elseif (!empty($maintenance) && $maintenance == 2)
1729
		$changes['maintenance'] = 1;
1730
1731
	// Wipe this out...
1732
	$upcontext['user'] = array();
1733
1734
	require_once($sourcedir . '/Subs.php');
1735
	require_once($sourcedir . '/Subs-Admin.php');
1736
	updateSettingsFile($changes);
1737
1738
	// Clean any old cache files away.
1739
	upgrade_clean_cache();
1740
1741
	// Can we delete the file?
1742
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1743
1744
	// Now is the perfect time to fetch the SM files.
1745
	if ($command_line)
1746
		cli_scheduled_fetchSMfiles();
1747
	else
1748
	{
1749
		require_once($sourcedir . '/ScheduledTasks.php');
1750
		scheduled_fetchSMfiles(); // Now go get those files!
1751
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1752
		if (httpsOn())
1753
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1754
	}
1755
1756
	// Log what we've done.
1757
	if (empty($user_info['id']))
1758
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1759
1760
	// Log the action manually, so CLI still works.
1761
	$smcFunc['db_insert']('',
1762
		'{db_prefix}log_actions',
1763
		array(
1764
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1765
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1766
		),
1767
		array(
1768
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1769
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1770
		),
1771
		array('id_action')
1772
	);
1773
	$user_info['id'] = 0;
1774
1775
	if ($command_line)
1776
	{
1777
		echo $endl;
1778
		echo 'Upgrade Complete!', $endl;
1779
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1780
		exit;
1781
	}
1782
1783
	// Make sure it says we're done.
1784
	$upcontext['overall_percent'] = 100;
1785
	if (isset($upcontext['step_progress']))
1786
		unset($upcontext['step_progress']);
1787
1788
	$_GET['substep'] = 0;
1789
	return false;
1790
}
1791
1792
// Just like the built in one, but setup for CLI to not use themes.
1793
function cli_scheduled_fetchSMfiles()
1794
{
1795
	global $sourcedir, $language, $modSettings, $smcFunc;
1796
1797
	if (empty($modSettings['time_format']))
1798
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1799
1800
	// What files do we want to get
1801
	$request = $smcFunc['db_query']('', '
1802
		SELECT id_file, filename, path, parameters
1803
		FROM {db_prefix}admin_info_files',
1804
		array(
1805
		)
1806
	);
1807
1808
	$js_files = array();
1809
	while ($row = $smcFunc['db_fetch_assoc']($request))
1810
	{
1811
		$js_files[$row['id_file']] = array(
1812
			'filename' => $row['filename'],
1813
			'path' => $row['path'],
1814
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1815
		);
1816
	}
1817
	$smcFunc['db_free_result']($request);
1818
1819
	// We're gonna need fetch_web_data() to pull this off.
1820
	require_once($sourcedir . '/Subs.php');
1821
1822
	foreach ($js_files as $ID_FILE => $file)
1823
	{
1824
		// Create the url
1825
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
1826
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1827
1828
		// Get the file
1829
		$file_data = fetch_web_data($url);
1830
1831
		// If we got an error - give up - the site might be down.
1832
		if ($file_data === false)
1833
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
1834
1835
		// Save the file to the database.
1836
		$smcFunc['db_query']('substring', '
1837
			UPDATE {db_prefix}admin_info_files
1838
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1839
			WHERE id_file = {int:id_file}',
1840
			array(
1841
				'id_file' => $ID_FILE,
1842
				'file_data' => $file_data,
1843
			)
1844
		);
1845
	}
1846
	return true;
1847
}
1848
1849
function convertSettingsToTheme()
1850
{
1851
	global $db_prefix, $modSettings, $smcFunc;
1852
1853
	$values = array(
1854
		'show_latest_member' => @$GLOBALS['showlatestmember'],
1855
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
1856
		'show_modify' => @$GLOBALS['showmodify'],
1857
		'show_user_images' => @$GLOBALS['showuserpic'],
1858
		'show_blurb' => @$GLOBALS['showusertext'],
1859
		'show_gender' => @$GLOBALS['showgenderimage'],
1860
		'show_newsfader' => @$GLOBALS['shownewsfader'],
1861
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
1862
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
1863
		'linktree_link' => @$GLOBALS['curposlinks'],
1864
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
1865
		'show_mark_read' => @$GLOBALS['showmarkread'],
1866
		'show_board_desc' => @$GLOBALS['ShowBDescrip'],
1867
		'newsfader_time' => @$GLOBALS['fadertime'],
1868
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
1869
		'enable_news' => @$GLOBALS['enable_news'],
1870
		'linktree_inline' => @$modSettings['enableInlineLinks'],
1871
		'return_to_post' => @$modSettings['returnToPost'],
1872
	);
1873
1874
	$themeData = array();
1875
	foreach ($values as $variable => $value)
1876
	{
1877
		if (!isset($value) || $value === null)
1878
			$value = 0;
1879
1880
		$themeData[] = array(0, 1, $variable, $value);
1881
	}
1882
	if (!empty($themeData))
1883
	{
1884
		$smcFunc['db_insert']('ignore',
1885
			$db_prefix . 'themes',
1886
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
1887
			$themeData,
1888
			array('id_member', 'id_theme', 'variable')
1889
		);
1890
	}
1891
}
1892
1893
// This function only works with MySQL but that's fine as it is only used for v1.0.
1894
function convertSettingstoOptions()
1895
{
1896
	global $modSettings, $smcFunc;
1897
1898
	// Format: new_setting -> old_setting_name.
1899
	$values = array(
1900
		'calendar_start_day' => 'cal_startmonday',
1901
		'view_newest_first' => 'viewNewestFirst',
1902
		'view_newest_pm_first' => 'viewNewestFirst',
1903
	);
1904
1905
	foreach ($values as $variable => $value)
1906
	{
1907
		if (empty($modSettings[$value[0]]))
1908
			continue;
1909
1910
		$smcFunc['db_query']('', '
1911
			INSERT IGNORE INTO {db_prefix}themes
1912
				(id_member, id_theme, variable, value)
1913
			SELECT id_member, 1, {string:variable}, {string:value}
1914
			FROM {db_prefix}members',
1915
			array(
1916
				'variable' => $variable,
1917
				'value' => $modSettings[$value[0]],
1918
				'db_error_skip' => true,
1919
			)
1920
		);
1921
1922
		$smcFunc['db_query']('', '
1923
			INSERT IGNORE INTO {db_prefix}themes
1924
				(id_member, id_theme, variable, value)
1925
			VALUES (-1, 1, {string:variable}, {string:value})',
1926
			array(
1927
				'variable' => $variable,
1928
				'value' => $modSettings[$value[0]],
1929
				'db_error_skip' => true,
1930
			)
1931
		);
1932
	}
1933
}
1934
1935
function php_version_check()
1936
{
1937
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
1938
}
1939
1940
function db_version_check()
1941
{
1942
	global $db_type, $databases;
1943
1944
	$curver = eval($databases[$db_type]['version_check']);
1945
	$curver = preg_replace('~\-.+?$~', '', $curver);
1946
1947
	return version_compare($databases[$db_type]['version'], $curver, '<=');
1948
}
1949
1950
function fixRelativePath($path)
1951
{
1952
	global $install_path;
1953
1954
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
1955
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
1956
}
1957
1958
function parse_sql($filename)
1959
{
1960
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
1961
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set, $smcFunc;
1962
1963
/*
1964
	Failure allowed on:
1965
		- INSERT INTO but not INSERT IGNORE INTO.
1966
		- UPDATE IGNORE but not UPDATE.
1967
		- ALTER TABLE and ALTER IGNORE TABLE.
1968
		- DROP TABLE.
1969
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
1970
1971
	If a comment...
1972
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
1973
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
1974
		- is only ---#, it is "done." and then a break - only shown in debug.
1975
		- begins with ---{ it is a code block terminating at ---}.
1976
1977
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
1978
1979
	Replaces the following variables:
1980
		- {$boarddir}
1981
		- {$boardurl}
1982
		- {$db_prefix}
1983
		- {$db_collation}
1984
*/
1985
1986
	// May want to use extended functionality.
1987
	db_extend();
1988
	db_extend('packages');
1989
1990
	// Our custom error handler - does nothing but does stop public errors from XML!
1991
	// Note that php error suppression - @ - used heavily in the upgrader, calls the error handler
1992
	// but error_reporting() will return 0 as it does so.
1993
	set_error_handler(
1994
		function($errno, $errstr, $errfile, $errline) use ($support_js)
1995
		{
1996
			if ($support_js)
1997
				return true;
1998
			elseif (error_reporting() != 0)
1999
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
2000
		}
2001
	);
2002
2003
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
2004
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
2005
	if ($db_type == 'mysql')
2006
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
2007
	else
2008
		$db_collation = '';
2009
2010
	$endl = $command_line ? "\n" : '<br>' . "\n";
2011
2012
	$lines = file($filename);
2013
2014
	$current_type = 'sql';
2015
	$current_data = '';
2016
	$substep = 0;
2017
	$last_step = '';
2018
2019
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
2020
	if (isset($db_character_set) && $db_character_set === 'utf8')
2021
		$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
2022
2023
	// Count the total number of steps within this file - for progress.
2024
	$file_steps = substr_count(implode('', $lines), '---#');
2025
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
2026
	$upcontext['debug_items'] = $file_steps;
2027
	$upcontext['current_item_num'] = 0;
2028
	$upcontext['current_item_name'] = '';
2029
	$upcontext['current_debug_item_num'] = 0;
2030
	$upcontext['current_debug_item_name'] = '';
2031
	// This array keeps a record of what we've done in case java is dead...
2032
	$upcontext['actioned_items'] = array();
2033
2034
	$done_something = false;
2035
2036
	foreach ($lines as $line_number => $line)
2037
	{
2038
		$do_current = $substep >= $_GET['substep'];
2039
2040
		// Get rid of any comments in the beginning of the line...
2041
		if (substr(trim($line), 0, 2) === '/*')
2042
			$line = preg_replace('~/\*.+?\*/~', '', $line);
2043
2044
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
2045
		if ($is_debug && !$support_js && $command_line)
2046
			flush();
2047
2048
		if (trim($line) === '')
2049
			continue;
2050
2051
		if (trim(substr($line, 0, 3)) === '---')
2052
		{
2053
			$type = substr($line, 3, 1);
2054
2055
			// An error??
2056
			if (trim($current_data) != '' && $type !== '}')
2057
			{
2058
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
2059
				if ($command_line)
2060
					echo $upcontext['error_message'];
2061
			}
2062
2063
			if ($type == ' ')
2064
			{
2065
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
2066
				{
2067
					echo ' Successful.', $endl;
2068
					flush();
2069
				}
2070
2071
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
2072
				$upcontext['current_item_num']++;
2073
				$upcontext['current_item_name'] = $last_step;
2074
2075
				if ($do_current)
2076
				{
2077
					$upcontext['actioned_items'][] = $last_step;
2078
					if ($command_line)
2079
						echo ' * ';
2080
2081
					// Starting a new main step in our DB changes, so it's time to reset this.
2082
					$upcontext['skip_db_substeps'] = false;
2083
				}
2084
			}
2085
			elseif ($type == '#')
2086
			{
2087
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
2088
2089
				$upcontext['current_debug_item_num']++;
2090
				if (trim($line) != '---#')
2091
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
2092
2093
				// Have we already done something?
2094
				if (isset($_GET['xml']) && $done_something)
2095
				{
2096
					restore_error_handler();
2097
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
2098
				}
2099
2100
				if ($do_current)
2101
				{
2102
					if (trim($line) == '---#' && $command_line)
2103
						echo ' done.', $endl;
2104
					elseif ($command_line)
2105
						echo ' +++ ', rtrim(substr($line, 4));
2106
					elseif (trim($line) != '---#')
2107
					{
2108
						if ($is_debug)
2109
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
2110
					}
2111
				}
2112
2113
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
2114
				{
2115
					if ($command_line)
2116
						echo ' * ';
2117
					else
2118
						$upcontext['actioned_items'][] = $last_step;
2119
				}
2120
2121
				// Small step - only if we're actually doing stuff.
2122
				if ($do_current)
2123
					nextSubstep(++$substep);
2124
				else
2125
					$substep++;
2126
			}
2127
			elseif ($type == '{')
2128
				$current_type = 'code';
2129
			elseif ($type == '}')
2130
			{
2131
				$current_type = 'sql';
2132
2133
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2134
				{
2135
					$current_data = '';
2136
2137
					// Avoid confusion when skipping something we normally would have done
2138
					if ($do_current)
2139
						$done_something = true;
2140
2141
					continue;
2142
				}
2143
2144
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2145
				if (eval('global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false)
2146
				{
2147
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2148
					if ($command_line)
2149
						echo $upcontext['error_message'];
2150
				}
2151
2152
				// Done with code!
2153
				$current_data = '';
2154
				$done_something = true;
2155
			}
2156
2157
			continue;
2158
		}
2159
2160
		$current_data .= $line;
2161
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2162
		{
2163
			if ((!$support_js || isset($_GET['xml'])))
2164
			{
2165
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2166
				{
2167
					$current_data = '';
2168
2169
					if ($do_current)
2170
						$done_something = true;
2171
2172
					continue;
2173
				}
2174
2175
				// {$sboarddir} is deprecated, but blah blah backward compatibility blah...
2176
				$current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $smcFunc['db_escape_string']($boarddir), '{$sboarddir}' => $smcFunc['db_escape_string']($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation));
2177
2178
				upgrade_query($current_data);
2179
2180
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2181
				/*
2182
				$result = $smcFunc['db_query']('', $current_data, false, false);
2183
				// Went wrong?
2184
				if (!$result)
2185
				{
2186
					// Bit of a bodge - do we want the error?
2187
					if (!empty($upcontext['return_error']))
2188
					{
2189
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2190
						return false;
2191
					}
2192
				}*/
2193
				$done_something = true;
2194
			}
2195
			$current_data = '';
2196
		}
2197
		// If this is xml based and we're just getting the item name then that's grand.
2198
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2199
		{
2200
			restore_error_handler();
2201
			return false;
2202
		}
2203
2204
		// Clean up by cleaning any step info.
2205
		$step_progress = array();
2206
		$custom_warning = '';
2207
	}
2208
2209
	// Put back the error handler.
2210
	restore_error_handler();
2211
2212
	if ($command_line)
2213
	{
2214
		echo ' Successful.' . "\n";
2215
		flush();
2216
	}
2217
2218
	$_GET['substep'] = 0;
2219
	return true;
2220
}
2221
2222
function upgrade_query($string, $unbuffered = false)
2223
{
2224
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2225
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2226
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2227
2228
	// Get the query result - working around some SMF specific security - just this once!
2229
	$modSettings['disableQueryCheck'] = true;
2230
	$db_unbuffered = $unbuffered;
2231
	$ignore_insert_error = false;
2232
2233
	// If we got an old pg version and use a insert ignore query
2234
	if ($db_type == 'postgresql' && !$smcFunc['db_native_replace']() && strpos($string, 'ON CONFLICT DO NOTHING') !== false)
2235
	{
2236
		$ignore_insert_error = true;
2237
		$string = str_replace('ON CONFLICT DO NOTHING', '', $string);
2238
	}
2239
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2240
	$db_unbuffered = false;
2241
2242
	// Failure?!
2243
	if ($result !== false)
2244
		return $result;
2245
2246
	$db_error_message = $smcFunc['db_error']($db_connection);
2247
	// If MySQL we do something more clever.
2248
	if ($db_type == 'mysql')
2249
	{
2250
		$mysqli_errno = mysqli_errno($db_connection);
2251
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR', 'INSERT IGNO'));
2252
2253
		// Error numbers:
2254
		//    1016: Can't open file '....MYI'
2255
		//    1050: Table already exists.
2256
		//    1054: Unknown column name.
2257
		//    1060: Duplicate column name.
2258
		//    1061: Duplicate key name.
2259
		//    1062: Duplicate entry for unique key.
2260
		//    1068: Multiple primary keys.
2261
		//    1072: Key column '%s' doesn't exist in table.
2262
		//    1091: Can't drop key, doesn't exist.
2263
		//    1146: Table doesn't exist.
2264
		//    2013: Lost connection to server during query.
2265
2266
		if ($mysqli_errno == 1016)
2267
		{
2268
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2269
			{
2270
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2271
				$result = mysqli_query($db_connection, $string);
2272
				if ($result !== false)
2273
					return $result;
2274
			}
2275
		}
2276
		elseif ($mysqli_errno == 2013)
2277
		{
2278
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
2279
			mysqli_select_db($db_connection, $db_name);
2280
			if ($db_connection)
2281
			{
2282
				$result = mysqli_query($db_connection, $string);
2283
				if ($result !== false)
2284
					return $result;
2285
			}
2286
		}
2287
		// Duplicate column name... should be okay ;).
2288
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2289
			return false;
2290
		// Duplicate insert... make sure it's the proper type of query ;).
2291
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2292
			return false;
2293
		// Creating an index on a non-existent column.
2294
		elseif ($mysqli_errno == 1072)
2295
			return false;
2296
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2297
			return false;
2298
		// Testing for legacy tables or columns? Needed for 1.0 & 1.1 scripts.
2299
		elseif (in_array($mysqli_errno, array(1054, 1146)) && in_array(substr(trim($string), 0, 7), array('SELECT ', 'SHOW CO')))
2300
			return false;
2301
	}
2302
	// If a table already exists don't go potty.
2303
	else
2304
	{
2305
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2306
		{
2307
			if (strpos($db_error_message, 'exist') !== false)
2308
				return true;
2309
		}
2310
		elseif (strpos(trim($string), 'INSERT ') !== false)
2311
		{
2312
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2313
				return true;
2314
		}
2315
	}
2316
2317
	// Get the query string so we pass everything.
2318
	$query_string = '';
2319
	foreach ($_GET as $k => $v)
2320
		$query_string .= ';' . $k . '=' . $v;
2321
	if (strlen($query_string) != 0)
2322
		$query_string = '?' . substr($query_string, 1);
2323
2324
	if ($command_line)
2325
	{
2326
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2327
		die;
2328
	}
2329
2330
	// Bit of a bodge - do we want the error?
2331
	if (!empty($upcontext['return_error']))
2332
	{
2333
		$upcontext['error_message'] = $db_error_message;
2334
		$upcontext['error_string'] = $string;
2335
		return false;
2336
	}
2337
2338
	// Otherwise we have to display this somewhere appropriate if possible.
2339
	$upcontext['forced_error_message'] = '
2340
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2341
2342
			<div style="margin: 2ex;">
2343
				' . $txt['upgrade_thisquery'] . '
2344
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2345
2346
				' . $txt['upgrade_causerror'] . '
2347
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2348
			</div>
2349
2350
			<form action="' . $upgradeurl . $query_string . '" method="post">
2351
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2352
			</form>
2353
		</div>';
2354
2355
	upgradeExit();
2356
}
2357
2358
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2359
function protected_alter($change, $substep, $is_test = false)
2360
{
2361
	global $db_prefix, $smcFunc;
2362
2363
	db_extend('packages');
2364
2365
	// Firstly, check whether the current index/column exists.
2366
	$found = false;
2367
	if ($change['type'] === 'column')
2368
	{
2369
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2370
		foreach ($columns as $column)
2371
		{
2372
			// Found it?
2373
			if ($column['name'] === $change['name'])
2374
			{
2375
				$found |= true;
2376
				// Do some checks on the data if we have it set.
2377
				if (isset($change['col_type']))
2378
					$found &= $change['col_type'] === $column['type'];
2379
				if (isset($change['null_allowed']))
2380
					$found &= $column['null'] == $change['null_allowed'];
2381
				if (isset($change['default']))
2382
					$found &= $change['default'] === $column['default'];
2383
			}
2384
		}
2385
	}
2386
	elseif ($change['type'] === 'index')
2387
	{
2388
		$request = upgrade_query('
2389
			SHOW INDEX
2390
			FROM ' . $db_prefix . $change['table']);
2391
		if ($request !== false)
2392
		{
2393
			$cur_index = array();
2394
2395
			while ($row = $smcFunc['db_fetch_assoc']($request))
2396
				if ($row['Key_name'] === $change['name'])
2397
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2398
2399
			ksort($cur_index, SORT_NUMERIC);
2400
			$found = array_values($cur_index) === $change['target_columns'];
2401
2402
			$smcFunc['db_free_result']($request);
2403
		}
2404
	}
2405
2406
	// If we're trying to add and it's added, we're done.
2407
	if ($found && in_array($change['method'], array('add', 'change')))
2408
		return true;
2409
	// Otherwise if we're removing and it wasn't found we're also done.
2410
	elseif (!$found && in_array($change['method'], array('remove', 'change_remove')))
2411
		return true;
2412
	// Otherwise is it just a test?
2413
	elseif ($is_test)
2414
		return false;
2415
2416
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2417
	$running = false;
2418
	$found = false;
2419
	while (1 == 1)
2420
	{
2421
		$request = upgrade_query('
2422
			SHOW FULL PROCESSLIST');
2423
		while ($row = $smcFunc['db_fetch_assoc']($request))
2424
		{
2425
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2426
				$found = true;
2427
		}
2428
2429
		// Can't find it? Then we need to run it fools!
2430
		if (!$found && !$running)
2431
		{
2432
			$smcFunc['db_free_result']($request);
2433
2434
			$success = upgrade_query('
2435
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2436
				' . $change['text'], true) !== false;
2437
2438
			if (!$success)
2439
				return false;
2440
2441
			// Return
2442
			$running = true;
2443
		}
2444
		// What if we've not found it, but we'd ran it already? Must of completed.
2445
		elseif (!$found)
2446
		{
2447
			$smcFunc['db_free_result']($request);
2448
			return true;
2449
		}
2450
2451
		// Pause execution for a sec or three.
2452
		sleep(3);
2453
2454
		// Can never be too well protected.
2455
		nextSubstep($substep);
2456
	}
2457
2458
	// Protect it.
2459
	nextSubstep($substep);
2460
}
2461
2462
/**
2463
 * Alter a text column definition preserving its character set.
2464
 *
2465
 * @param array $change
2466
 * @param int $substep
2467
 */
2468
function textfield_alter($change, $substep)
2469
{
2470
	global $db_prefix, $smcFunc;
2471
2472
	$request = $smcFunc['db_query']('', '
2473
		SHOW FULL COLUMNS
2474
		FROM {db_prefix}' . $change['table'] . '
2475
		LIKE {string:column}',
2476
		array(
2477
			'column' => $change['column'],
2478
			'db_error_skip' => true,
2479
		)
2480
	);
2481
	if ($smcFunc['db_num_rows']($request) === 0)
2482
		die('Unable to find column ' . $change['column'] . ' inside table ' . $db_prefix . $change['table']);
2483
	$table_row = $smcFunc['db_fetch_assoc']($request);
2484
	$smcFunc['db_free_result']($request);
2485
2486
	// If something of the current column definition is different, fix it.
2487
	$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']);
2488
2489
	// Columns that previously allowed null, need to be converted first.
2490
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2491
2492
	// Get the character set that goes with the collation of the column.
2493
	if ($column_fix && !empty($table_row['Collation']))
2494
	{
2495
		$request = $smcFunc['db_query']('', '
2496
			SHOW COLLATION
2497
			LIKE {string:collation}',
2498
			array(
2499
				'collation' => $table_row['Collation'],
2500
				'db_error_skip' => true,
2501
			)
2502
		);
2503
		// No results? Just forget it all together.
2504
		if ($smcFunc['db_num_rows']($request) === 0)
2505
			unset($table_row['Collation']);
2506
		else
2507
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2508
		$smcFunc['db_free_result']($request);
2509
	}
2510
2511
	if ($column_fix)
2512
	{
2513
		// Make sure there are no NULL's left.
2514
		if ($null_fix)
2515
			$smcFunc['db_query']('', '
2516
				UPDATE {db_prefix}' . $change['table'] . '
2517
				SET ' . $change['column'] . ' = {string:default}
2518
				WHERE ' . $change['column'] . ' IS NULL',
2519
				array(
2520
					'default' => isset($change['default']) ? $change['default'] : '',
2521
					'db_error_skip' => true,
2522
				)
2523
			);
2524
2525
		// Do the actual alteration.
2526
		$smcFunc['db_query']('', '
2527
			ALTER TABLE {db_prefix}' . $change['table'] . '
2528
			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}' : ''),
2529
			array(
2530
				'default' => isset($change['default']) ? $change['default'] : '',
2531
				'db_error_skip' => true,
2532
			)
2533
		);
2534
	}
2535
	nextSubstep($substep);
2536
}
2537
2538
// Check if we need to alter this query.
2539
function checkChange(&$change)
2540
{
2541
	global $smcFunc, $db_type, $databases;
2542
	static $database_version, $where_field_support;
2543
2544
	// Attempt to find a database_version.
2545
	if (empty($database_version))
2546
	{
2547
		$database_version = $databases[$db_type]['version_check'];
2548
		$where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version, '<=');
2549
	}
2550
2551
	// Not a column we need to check on?
2552
	if (!in_array($change['name'], array('memberGroups', 'passwordSalt')))
2553
		return;
2554
2555
	// Break it up you (six|seven).
2556
	$temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text']));
2557
2558
	// Can we support a shortcut method?
2559
	if ($where_field_support)
2560
	{
2561
		// Get the details about this change.
2562
		$request = $smcFunc['db_query']('', '
2563
			SHOW FIELDS
2564
			FROM {db_prefix}{raw:table}
2565
			WHERE Field = {string:old_name} OR Field = {string:new_name}',
2566
			array(
2567
				'table' => $change['table'],
2568
				'old_name' => $temp[1],
2569
				'new_name' => $temp[2],
2570
			)
2571
		);
2572
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2573
		if ($smcFunc['db_num_rows'] != 1)
2574
			return;
2575
2576
		list (, $current_type) = $smcFunc['db_fetch_assoc']($request);
2577
		$smcFunc['db_free_result']($request);
2578
	}
2579
	else
2580
	{
2581
		// Do this the old fashion, sure method way.
2582
		$request = $smcFunc['db_query']('', '
2583
			SHOW FIELDS
2584
			FROM {db_prefix}{raw:table}',
2585
			array(
2586
				'table' => $change['table'],
2587
			)
2588
		);
2589
		// Mayday!
2590
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2591
		if ($smcFunc['db_num_rows'] == 0)
2592
			return;
2593
2594
		// Oh where, oh where has my little field gone. Oh where can it be...
2595
		while ($row = $smcFunc['db_query']($request))
2596
			if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2])
2597
			{
2598
				$current_type = $row['Type'];
2599
				break;
2600
			}
2601
	}
2602
2603
	// If this doesn't match, the column may of been altered for a reason.
2604
	if (trim($current_type) != trim($temp[3]))
2605
		$temp[3] = $current_type;
2606
2607
	// Piece this back together.
2608
	$change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp));
2609
}
2610
2611
// The next substep.
2612
function nextSubstep($substep)
2613
{
2614
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2615
	global $step_progress, $is_debug, $upcontext;
2616
2617
	if ($_GET['substep'] < $substep)
2618
		$_GET['substep'] = $substep;
2619
2620
	if ($command_line)
2621
	{
2622
		if (time() - $start_time > 1 && empty($is_debug))
2623
		{
2624
			echo '.';
2625
			$start_time = time();
2626
		}
2627
		return;
2628
	}
2629
2630
	@set_time_limit(300);
2631
	if (function_exists('apache_reset_timeout'))
2632
		@apache_reset_timeout();
2633
2634
	if (time() - $start_time <= $timeLimitThreshold)
2635
		return;
2636
2637
	// Do we have some custom step progress stuff?
2638
	if (!empty($step_progress))
2639
	{
2640
		$upcontext['substep_progress'] = 0;
2641
		$upcontext['substep_progress_name'] = $step_progress['name'];
2642
		if ($step_progress['current'] > $step_progress['total'])
2643
			$upcontext['substep_progress'] = 99.9;
2644
		else
2645
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2646
2647
		// Make it nicely rounded.
2648
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2649
	}
2650
2651
	// If this is XML we just exit right away!
2652
	if (isset($_GET['xml']))
2653
		return upgradeExit();
2654
2655
	// We're going to pause after this!
2656
	$upcontext['pause'] = true;
2657
2658
	$upcontext['query_string'] = '';
2659
	foreach ($_GET as $k => $v)
2660
	{
2661
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2662
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2663
	}
2664
2665
	// Custom warning?
2666
	if (!empty($custom_warning))
2667
		$upcontext['custom_warning'] = $custom_warning;
2668
2669
	upgradeExit();
2670
}
2671
2672
function cmdStep0()
2673
{
2674
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2675
	global $is_debug, $boardurl, $txt;
2676
	$start_time = time();
2677
2678
	ob_end_clean();
2679
	ob_implicit_flush(1);
2680
	@set_time_limit(600);
2681
2682
	if (!isset($_SERVER['argv']))
2683
		$_SERVER['argv'] = array();
2684
	$_GET['maint'] = 1;
2685
2686
	foreach ($_SERVER['argv'] as $i => $arg)
2687
	{
2688
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2689
			$upcontext['lang'] = $match[1];
2690
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2691
			continue;
2692
		elseif ($arg == '--no-maintenance')
2693
			$_GET['maint'] = 0;
2694
		elseif ($arg == '--debug')
2695
			$is_debug = true;
2696
		elseif ($arg == '--backup')
2697
			$_POST['backup'] = 1;
2698
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2699
			$_GET['conv'] = 1;
2700
		elseif ($i != 0)
2701
		{
2702
			echo 'SMF Command-line Upgrader
2703
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2704
2705
	--language=LANG         Reset the forum\'s language to LANG.
2706
	--no-maintenance        Don\'t put the forum into maintenance mode.
2707
	--debug                 Output debugging information.
2708
	--backup                Create backups of tables with "backup_" prefix.';
2709
			echo "\n";
2710
			exit;
2711
		}
2712
	}
2713
2714
	if (!php_version_check())
2715
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
2716
	if (!db_version_check())
2717
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2718
2719
	// Do some checks to make sure they have proper privileges
2720
	db_extend('packages');
2721
2722
	// CREATE
2723
	$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');
2724
2725
	// ALTER
2726
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2727
2728
	// DROP
2729
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2730
2731
	// Sorry... we need CREATE, ALTER and DROP
2732
	if (!$create || !$alter || !$drop)
2733
		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);
2734
2735
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2736
		&& @file_exists($sourcedir . '/QueryString.php')
2737
		&& @file_exists($sourcedir . '/ManageBoards.php');
2738
	if (!$check && !isset($modSettings['smfVersion']))
2739
		print_error('Error: Some files are missing or out-of-date.', true);
2740
2741
	// Do a quick version spot check.
2742
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
2743
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2744
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2745
		print_error('Error: Some files have not yet been updated properly.');
2746
2747
	// Make sure Settings.php is writable.
2748
	quickFileWritable($boarddir . '/Settings.php');
2749
	if (!is_writable($boarddir . '/Settings.php'))
2750
		print_error('Error: Unable to obtain write access to "Settings.php".', true);
2751
2752
	// Make sure Settings_bak.php is writable.
2753
	quickFileWritable($boarddir . '/Settings_bak.php');
2754
	if (!is_writable($boarddir . '/Settings_bak.php'))
2755
		print_error('Error: Unable to obtain write access to "Settings_bak.php".');
2756
2757
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
2758
		print_error('Error: Unable to obtain write access to "agreement.txt".');
2759
	elseif (isset($modSettings['agreement']))
2760
	{
2761
		$fp = fopen($boarddir . '/agreement.txt', 'w');
2762
		fwrite($fp, $modSettings['agreement']);
2763
		fclose($fp);
2764
	}
2765
2766
	// Make sure Themes is writable.
2767
	quickFileWritable($modSettings['theme_dir']);
2768
2769
	if (!is_writable($modSettings['theme_dir']) && !isset($modSettings['smfVersion']))
2770
		print_error('Error: Unable to obtain write access to "Themes".');
2771
2772
	// Make sure cache directory exists and is writable!
2773
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
2774
	if (!file_exists($cachedir_temp))
2775
		@mkdir($cachedir_temp);
2776
2777
	// Make sure the cache temp dir is writable.
2778
	quickFileWritable($cachedir_temp);
2779
2780
	if (!is_writable($cachedir_temp))
2781
		print_error('Error: Unable to obtain write access to "cache".', true);
2782
2783
	// Make sure db_last_error.php is writable.
2784
	quickFileWritable($cachedir_temp . '/db_last_error.php');
2785
	if (!is_writable($cachedir_temp . '/db_last_error.php'))
2786
		print_error('Error: Unable to obtain write access to "db_last_error.php".');
2787
2788
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
2789
		print_error('Error: Unable to find language files!', true);
2790
	else
2791
	{
2792
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
2793
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
2794
2795
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
2796
			print_error('Error: Language files out of date.', true);
2797
		if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'))
2798
			print_error('Error: Install language is missing for selected language.', true);
2799
2800
		// Otherwise include it!
2801
		require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php');
2802
	}
2803
2804
	// Do we need to add this setting?
2805
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
2806
2807
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $boarddir . '/custom_avatar';
2808
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
2809
2810
	// This little fellow has to cooperate...
2811
	quickFileWritable($custom_av_dir);
2812
2813
	// Are we good now?
2814
	if (!is_writable($custom_av_dir))
2815
		print_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
2816
	elseif ($need_settings_update)
2817
	{
2818
		if (!function_exists('cache_put_data'))
2819
			require_once($sourcedir . '/Load.php');
2820
2821
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
2822
		updateSettings(array('custom_avatar_url' => $custom_av_url));
2823
	}
2824
2825
	// Make sure we skip the HTML for login.
2826
	$_POST['upcont'] = true;
2827
	$upcontext['current_step'] = 1;
2828
}
2829
2830
/**
2831
 * Handles converting your database to UTF-8
2832
 */
2833
function ConvertUtf8()
2834
{
2835
	global $upcontext, $db_character_set, $sourcedir, $smcFunc, $modSettings, $language;
2836
	global $db_prefix, $db_type, $command_line, $support_js, $txt;
2837
2838
	// Done it already?
2839
	if (!empty($_POST['utf8_done']))
2840
	{
2841
		if ($command_line)
2842
			return DeleteUpgrade();
2843
		else
2844
			return true;
2845
	}
2846
	// First make sure they aren't already on UTF-8 before we go anywhere...
2847
	if ($db_type == 'postgresql' || ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8'))
2848
	{
2849
		$smcFunc['db_insert']('replace',
2850
			'{db_prefix}settings',
2851
			array('variable' => 'string', 'value' => 'string'),
2852
			array(array('global_character_set', 'UTF-8')),
2853
			array('variable')
2854
		);
2855
2856
		if ($command_line)
2857
			return DeleteUpgrade();
2858
		else
2859
			return true;
2860
	}
2861
	else
2862
	{
2863
		$upcontext['page_title'] = $txt['converting_utf8'];
2864
		$upcontext['sub_template'] = isset($_GET['xml']) ? 'convert_xml' : 'convert_utf8';
2865
2866
		// The character sets used in SMF's language files with their db equivalent.
2867
		$charsets = array(
2868
			// Armenian
2869
			'armscii8' => 'armscii8',
2870
			// Chinese-traditional.
2871
			'big5' => 'big5',
2872
			// Chinese-simplified.
2873
			'gbk' => 'gbk',
2874
			// West European.
2875
			'ISO-8859-1' => 'latin1',
2876
			// Romanian.
2877
			'ISO-8859-2' => 'latin2',
2878
			// Turkish.
2879
			'ISO-8859-9' => 'latin5',
2880
			// Latvian
2881
			'ISO-8859-13' => 'latin7',
2882
			// West European with Euro sign.
2883
			'ISO-8859-15' => 'latin9',
2884
			// Thai.
2885
			'tis-620' => 'tis620',
2886
			// Persian, Chinese, etc.
2887
			'UTF-8' => 'utf8',
2888
			// Russian.
2889
			'windows-1251' => 'cp1251',
2890
			// Greek.
2891
			'windows-1253' => 'utf8',
2892
			// Hebrew.
2893
			'windows-1255' => 'utf8',
2894
			// Arabic.
2895
			'windows-1256' => 'cp1256',
2896
		);
2897
2898
		// Get a list of character sets supported by your MySQL server.
2899
		$request = $smcFunc['db_query']('', '
2900
			SHOW CHARACTER SET',
2901
			array(
2902
			)
2903
		);
2904
		$db_charsets = array();
2905
		while ($row = $smcFunc['db_fetch_assoc']($request))
2906
			$db_charsets[] = $row['Charset'];
2907
2908
		$smcFunc['db_free_result']($request);
2909
2910
		// Character sets supported by both MySQL and SMF's language files.
2911
		$charsets = array_intersect($charsets, $db_charsets);
2912
2913
		// Use the messages.body column as indicator for the database charset.
2914
		$request = $smcFunc['db_query']('', '
2915
			SHOW FULL COLUMNS
2916
			FROM {db_prefix}messages
2917
			LIKE {string:body_like}',
2918
			array(
2919
				'body_like' => 'body',
2920
			)
2921
		);
2922
		$column_info = $smcFunc['db_fetch_assoc']($request);
2923
		$smcFunc['db_free_result']($request);
2924
2925
		// A collation looks like latin1_swedish. We only need the character set.
2926
		list($upcontext['database_charset']) = explode('_', $column_info['Collation']);
2927
		$upcontext['database_charset'] = in_array($upcontext['database_charset'], $charsets) ? array_search($upcontext['database_charset'], $charsets) : $upcontext['database_charset'];
2928
2929
		// Detect whether a fulltext index is set.
2930
		$request = $smcFunc['db_query']('', '
2931
			SHOW INDEX
2932
			FROM {db_prefix}messages',
2933
			array(
2934
			)
2935
		);
2936
2937
		$upcontext['dropping_index'] = false;
2938
2939
		// If there's a fulltext index, we need to drop it first...
2940
		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
2941
		{
2942
			while ($row = $smcFunc['db_fetch_assoc']($request))
2943
				if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
2944
					$upcontext['fulltext_index'][] = $row['Key_name'];
2945
			$smcFunc['db_free_result']($request);
2946
2947
			if (isset($upcontext['fulltext_index']))
2948
				$upcontext['fulltext_index'] = array_unique($upcontext['fulltext_index']);
2949
		}
2950
2951
		// Drop it and make a note...
2952
		if (!empty($upcontext['fulltext_index']))
2953
		{
2954
			$upcontext['dropping_index'] = true;
2955
2956
			$smcFunc['db_query']('', '
2957
				ALTER TABLE {db_prefix}messages
2958
				DROP INDEX ' . implode(',
2959
				DROP INDEX ', $upcontext['fulltext_index']),
2960
				array(
2961
					'db_error_skip' => true,
2962
				)
2963
			);
2964
2965
			// Update the settings table
2966
			$smcFunc['db_insert']('replace',
2967
				'{db_prefix}settings',
2968
				array('variable' => 'string', 'value' => 'string'),
2969
				array('db_search_index', ''),
2970
				array('variable')
2971
			);
2972
		}
2973
2974
		// Figure out what charset we should be converting from...
2975
		$lang_charsets = array(
2976
			'arabic' => 'windows-1256',
2977
			'armenian_east' => 'armscii-8',
2978
			'armenian_west' => 'armscii-8',
2979
			'azerbaijani_latin' => 'ISO-8859-9',
2980
			'bangla' => 'UTF-8',
2981
			'belarusian' => 'ISO-8859-5',
2982
			'bulgarian' => 'windows-1251',
2983
			'cambodian' => 'UTF-8',
2984
			'chinese_simplified' => 'gbk',
2985
			'chinese_traditional' => 'big5',
2986
			'croation' => 'ISO-8859-2',
2987
			'czech' => 'ISO-8859-2',
2988
			'czech_informal' => 'ISO-8859-2',
2989
			'english_pirate' => 'UTF-8',
2990
			'esperanto' => 'ISO-8859-3',
2991
			'estonian' => 'ISO-8859-15',
2992
			'filipino_tagalog' => 'UTF-8',
2993
			'filipino_vasayan' => 'UTF-8',
2994
			'georgian' => 'UTF-8',
2995
			'greek' => 'ISO-8859-3',
2996
			'hebrew' => 'windows-1255',
2997
			'hungarian' => 'ISO-8859-2',
2998
			'irish' => 'UTF-8',
2999
			'japanese' => 'UTF-8',
3000
			'khmer' => 'UTF-8',
3001
			'korean' => 'UTF-8',
3002
			'kurdish_kurmanji' => 'ISO-8859-9',
3003
			'kurdish_sorani' => 'windows-1256',
3004
			'lao' => 'tis-620',
3005
			'latvian' => 'ISO-8859-13',
3006
			'lithuanian' => 'ISO-8859-4',
3007
			'macedonian' => 'UTF-8',
3008
			'malayalam' => 'UTF-8',
3009
			'mongolian' => 'UTF-8',
3010
			'nepali' => 'UTF-8',
3011
			'persian' => 'UTF-8',
3012
			'polish' => 'ISO-8859-2',
3013
			'romanian' => 'ISO-8859-2',
3014
			'russian' => 'windows-1252',
3015
			'sakha' => 'UTF-8',
3016
			'serbian_cyrillic' => 'ISO-8859-5',
3017
			'serbian_latin' => 'ISO-8859-2',
3018
			'sinhala' => 'UTF-8',
3019
			'slovak' => 'ISO-8859-2',
3020
			'slovenian' => 'ISO-8859-2',
3021
			'telugu' => 'UTF-8',
3022
			'thai' => 'tis-620',
3023
			'turkish' => 'ISO-8859-9',
3024
			'turkmen' => 'ISO-8859-9',
3025
			'ukranian' => 'windows-1251',
3026
			'urdu' => 'UTF-8',
3027
			'uzbek_cyrillic' => 'ISO-8859-5',
3028
			'uzbek_latin' => 'ISO-8859-5',
3029
			'vietnamese' => 'UTF-8',
3030
			'yoruba' => 'UTF-8'
3031
		);
3032
3033
		// Default to ISO-8859-1 unless we detected another supported charset
3034
		$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';
3035
3036
		$upcontext['charset_list'] = array_keys($charsets);
3037
3038
		// Translation table for the character sets not native for MySQL.
3039
		$translation_tables = array(
3040
			'windows-1255' => array(
3041
				'0x81' => '\'\'',		'0x8A' => '\'\'',		'0x8C' => '\'\'',
3042
				'0x8D' => '\'\'',		'0x8E' => '\'\'',		'0x8F' => '\'\'',
3043
				'0x90' => '\'\'',		'0x9A' => '\'\'',		'0x9C' => '\'\'',
3044
				'0x9D' => '\'\'',		'0x9E' => '\'\'',		'0x9F' => '\'\'',
3045
				'0xCA' => '\'\'',		'0xD9' => '\'\'',		'0xDA' => '\'\'',
3046
				'0xDB' => '\'\'',		'0xDC' => '\'\'',		'0xDD' => '\'\'',
3047
				'0xDE' => '\'\'',		'0xDF' => '\'\'',		'0xFB' => '0xD792',
3048
				'0xFC' => '0xE282AC',		'0xFF' => '0xD6B2',		'0xC2' => '0xFF',
3049
				'0x80' => '0xFC',		'0xE2' => '0xFB',		'0xA0' => '0xC2A0',
3050
				'0xA1' => '0xC2A1',		'0xA2' => '0xC2A2',		'0xA3' => '0xC2A3',
3051
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
3052
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
3053
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
3054
				'0xAF' => '0xC2AF',		'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',
3055
				'0xB2' => '0xC2B2',		'0xB3' => '0xC2B3',		'0xB4' => '0xC2B4',
3056
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
3057
				'0xB8' => '0xC2B8',		'0xB9' => '0xC2B9',		'0xBB' => '0xC2BB',
3058
				'0xBC' => '0xC2BC',		'0xBD' => '0xC2BD',		'0xBE' => '0xC2BE',
3059
				'0xBF' => '0xC2BF',		'0xD7' => '0xD7B3',		'0xD1' => '0xD781',
3060
				'0xD4' => '0xD7B0',		'0xD5' => '0xD7B1',		'0xD6' => '0xD7B2',
3061
				'0xE0' => '0xD790',		'0xEA' => '0xD79A',		'0xEC' => '0xD79C',
3062
				'0xED' => '0xD79D',		'0xEE' => '0xD79E',		'0xEF' => '0xD79F',
3063
				'0xF0' => '0xD7A0',		'0xF1' => '0xD7A1',		'0xF2' => '0xD7A2',
3064
				'0xF3' => '0xD7A3',		'0xF5' => '0xD7A5',		'0xF6' => '0xD7A6',
3065
				'0xF7' => '0xD7A7',		'0xF8' => '0xD7A8',		'0xF9' => '0xD7A9',
3066
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
3067
				'0x86' => '0xE280A0',	'0x87' => '0xE280A1',	'0x89' => '0xE280B0',
3068
				'0x8B' => '0xE280B9',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
3069
				'0x95' => '0xE280A2',	'0x97' => '0xE28094',	'0x99' => '0xE284A2',
3070
				'0xC0' => '0xD6B0',		'0xC1' => '0xD6B1',		'0xC3' => '0xD6B3',
3071
				'0xC4' => '0xD6B4',		'0xC5' => '0xD6B5',		'0xC6' => '0xD6B6',
3072
				'0xC7' => '0xD6B7',		'0xC8' => '0xD6B8',		'0xC9' => '0xD6B9',
3073
				'0xCB' => '0xD6BB',		'0xCC' => '0xD6BC',		'0xCD' => '0xD6BD',
3074
				'0xCE' => '0xD6BE',		'0xCF' => '0xD6BF',		'0xD0' => '0xD780',
3075
				'0xD2' => '0xD782',		'0xE3' => '0xD793',		'0xE4' => '0xD794',
3076
				'0xE5' => '0xD795',		'0xE7' => '0xD797',		'0xE9' => '0xD799',
3077
				'0xFD' => '0xE2808E',	'0xFE' => '0xE2808F',	'0x92' => '0xE28099',
3078
				'0x83' => '0xC692',		'0xD3' => '0xD783',		'0x88' => '0xCB86',
3079
				'0x98' => '0xCB9C',		'0x91' => '0xE28098',	'0x96' => '0xE28093',
3080
				'0xBA' => '0xC3B7',		'0x9B' => '0xE280BA',	'0xAA' => '0xC397',
3081
				'0xA4' => '0xE282AA',	'0xE1' => '0xD791',		'0xE6' => '0xD796',
3082
				'0xE8' => '0xD798',		'0xEB' => '0xD79B',		'0xF4' => '0xD7A4',
3083
				'0xFA' => '0xD7AA',
3084
			),
3085
			'windows-1253' => array(
3086
				'0x81' => '\'\'',			'0x88' => '\'\'',			'0x8A' => '\'\'',
3087
				'0x8C' => '\'\'',			'0x8D' => '\'\'',			'0x8E' => '\'\'',
3088
				'0x8F' => '\'\'',			'0x90' => '\'\'',			'0x98' => '\'\'',
3089
				'0x9A' => '\'\'',			'0x9C' => '\'\'',			'0x9D' => '\'\'',
3090
				'0x9E' => '\'\'',			'0x9F' => '\'\'',			'0xAA' => '\'\'',
3091
				'0xD2' => '0xE282AC',			'0xFF' => '0xCE92',			'0xCE' => '0xCE9E',
3092
				'0xB8' => '0xCE88',		'0xBA' => '0xCE8A',		'0xBC' => '0xCE8C',
3093
				'0xBE' => '0xCE8E',		'0xBF' => '0xCE8F',		'0xC0' => '0xCE90',
3094
				'0xC8' => '0xCE98',		'0xCA' => '0xCE9A',		'0xCC' => '0xCE9C',
3095
				'0xCD' => '0xCE9D',		'0xCF' => '0xCE9F',		'0xDA' => '0xCEAA',
3096
				'0xE8' => '0xCEB8',		'0xEA' => '0xCEBA',		'0xEC' => '0xCEBC',
3097
				'0xEE' => '0xCEBE',		'0xEF' => '0xCEBF',		'0xC2' => '0xFF',
3098
				'0xBD' => '0xC2BD',		'0xED' => '0xCEBD',		'0xB2' => '0xC2B2',
3099
				'0xA0' => '0xC2A0',		'0xA3' => '0xC2A3',		'0xA4' => '0xC2A4',
3100
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
3101
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
3102
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
3103
				'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',		'0xB3' => '0xC2B3',
3104
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
3105
				'0xBB' => '0xC2BB',		'0xE2' => '0xCEB2',		'0x80' => '0xD2',
3106
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
3107
				'0x86' => '0xE280A0',	'0xA1' => '0xCE85',		'0xA2' => '0xCE86',
3108
				'0x87' => '0xE280A1',	'0x89' => '0xE280B0',	'0xB9' => '0xCE89',
3109
				'0x8B' => '0xE280B9',	'0x91' => '0xE28098',	'0x99' => '0xE284A2',
3110
				'0x92' => '0xE28099',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
3111
				'0x95' => '0xE280A2',	'0x96' => '0xE28093',	'0x97' => '0xE28094',
3112
				'0x9B' => '0xE280BA',	'0xAF' => '0xE28095',	'0xB4' => '0xCE84',
3113
				'0xC1' => '0xCE91',		'0xC3' => '0xCE93',		'0xC4' => '0xCE94',
3114
				'0xC5' => '0xCE95',		'0xC6' => '0xCE96',		'0x83' => '0xC692',
3115
				'0xC7' => '0xCE97',		'0xC9' => '0xCE99',		'0xCB' => '0xCE9B',
3116
				'0xD0' => '0xCEA0',		'0xD1' => '0xCEA1',		'0xD3' => '0xCEA3',
3117
				'0xD4' => '0xCEA4',		'0xD5' => '0xCEA5',		'0xD6' => '0xCEA6',
3118
				'0xD7' => '0xCEA7',		'0xD8' => '0xCEA8',		'0xD9' => '0xCEA9',
3119
				'0xDB' => '0xCEAB',		'0xDC' => '0xCEAC',		'0xDD' => '0xCEAD',
3120
				'0xDE' => '0xCEAE',		'0xDF' => '0xCEAF',		'0xE0' => '0xCEB0',
3121
				'0xE1' => '0xCEB1',		'0xE3' => '0xCEB3',		'0xE4' => '0xCEB4',
3122
				'0xE5' => '0xCEB5',		'0xE6' => '0xCEB6',		'0xE7' => '0xCEB7',
3123
				'0xE9' => '0xCEB9',		'0xEB' => '0xCEBB',		'0xF0' => '0xCF80',
3124
				'0xF1' => '0xCF81',		'0xF2' => '0xCF82',		'0xF3' => '0xCF83',
3125
				'0xF4' => '0xCF84',		'0xF5' => '0xCF85',		'0xF6' => '0xCF86',
3126
				'0xF7' => '0xCF87',		'0xF8' => '0xCF88',		'0xF9' => '0xCF89',
3127
				'0xFA' => '0xCF8A',		'0xFB' => '0xCF8B',		'0xFC' => '0xCF8C',
3128
				'0xFD' => '0xCF8D',		'0xFE' => '0xCF8E',
3129
			),
3130
		);
3131
3132
		// Make some preparations.
3133
		if (isset($translation_tables[$upcontext['charset_detected']]))
3134
		{
3135
			$replace = '%field%';
3136
3137
			// Build a huge REPLACE statement...
3138
			foreach ($translation_tables[$upcontext['charset_detected']] as $from => $to)
3139
				$replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')';
3140
		}
3141
3142
		// Get a list of table names ahead of time... This makes it easier to set our substep and such
3143
		db_extend();
3144
		$queryTables = $smcFunc['db_list_tables'](false, $db_prefix . '%');
3145
3146
		$upcontext['table_count'] = count($queryTables);
3147
3148
		// What ones have we already done?
3149
		foreach ($queryTables as $id => $table)
3150
			if ($id < $_GET['substep'])
3151
				$upcontext['previous_tables'][] = $table;
3152
3153
		$upcontext['cur_table_num'] = $_GET['substep'];
3154
		$upcontext['cur_table_name'] = str_replace($db_prefix, '', $queryTables[$_GET['substep']]);
3155
		$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3156
3157
		// Make sure we're ready & have painted the template before proceeding
3158
		if ($support_js && !isset($_GET['xml']))
3159
		{
3160
			$_GET['substep'] = 0;
3161
			return false;
3162
		}
3163
3164
		// We want to start at the first table.
3165
		for ($substep = $_GET['substep'], $n = count($queryTables); $substep < $n; $substep++)
3166
		{
3167
			$table = $queryTables[$substep];
3168
3169
			$getTableStatus = $smcFunc['db_query']('', '
3170
				SHOW TABLE STATUS
3171
				LIKE {string:table_name}',
3172
				array(
3173
					'table_name' => str_replace('_', '\_', $table)
3174
				)
3175
			);
3176
3177
			// Only one row so we can just fetch_assoc and free the result...
3178
			$table_info = $smcFunc['db_fetch_assoc']($getTableStatus);
3179
			$smcFunc['db_free_result']($getTableStatus);
3180
3181
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($queryTables[$substep + 1]) ? $queryTables[$substep + 1] : $queryTables[$substep]));
3182
			$upcontext['cur_table_num'] = $substep + 1;
3183
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3184
3185
			// Do we need to pause?
3186
			nextSubstep($substep);
3187
3188
			// Just to make sure it doesn't time out.
3189
			if (function_exists('apache_reset_timeout'))
3190
				@apache_reset_timeout();
3191
3192
			$table_charsets = array();
3193
3194
			// Loop through each column.
3195
			$queryColumns = $smcFunc['db_query']('', '
3196
				SHOW FULL COLUMNS
3197
				FROM ' . $table_info['Name'],
3198
				array(
3199
				)
3200
			);
3201
			while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns))
3202
			{
3203
				// Only text'ish columns have a character set and need converting.
3204
				if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
3205
				{
3206
					$collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation'];
3207
					if (!empty($collation) && $collation !== 'NULL')
3208
					{
3209
						list($charset) = explode('_', $collation);
3210
3211
						// Build structure of columns to operate on organized by charset; only operate on columns not yet utf8
3212
						if ($charset != 'utf8')
3213
						{
3214
							if (!isset($table_charsets[$charset]))
3215
								$table_charsets[$charset] = array();
3216
3217
							$table_charsets[$charset][] = $column_info;
3218
						}
3219
					}
3220
				}
3221
			}
3222
			$smcFunc['db_free_result']($queryColumns);
3223
3224
			// Only change the non-utf8 columns identified above
3225
			if (count($table_charsets) > 0)
3226
			{
3227
				$updates_blob = '';
3228
				$updates_text = '';
3229
				foreach ($table_charsets as $charset => $columns)
3230
				{
3231
					if ($charset !== $charsets[$upcontext['charset_detected']])
3232
					{
3233
						foreach ($columns as $column)
3234
						{
3235
							$updates_blob .= '
3236
								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'] . '\'') . ',';
3237
							$updates_text .= '
3238
								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'] . '\'') . ',';
3239
						}
3240
					}
3241
				}
3242
3243
				// Change the columns to binary form.
3244
				$smcFunc['db_query']('', '
3245
					ALTER TABLE {raw:table_name}{raw:updates_blob}',
3246
					array(
3247
						'table_name' => $table_info['Name'],
3248
						'updates_blob' => substr($updates_blob, 0, -1),
3249
					)
3250
				);
3251
3252
				// Convert the character set if MySQL has no native support for it.
3253
				if (isset($translation_tables[$upcontext['charset_detected']]))
3254
				{
3255
					$update = '';
3256
					foreach ($table_charsets as $charset => $columns)
3257
						foreach ($columns as $column)
3258
							$update .= '
3259
								' . $column['Field'] . ' = ' . strtr($replace, array('%field%' => $column['Field'])) . ',';
3260
3261
					$smcFunc['db_query']('', '
3262
						UPDATE {raw:table_name}
3263
						SET {raw:updates}',
3264
						array(
3265
							'table_name' => $table_info['Name'],
3266
							'updates' => substr($update, 0, -1),
3267
						)
3268
					);
3269
				}
3270
3271
				// Change the columns back, but with the proper character set.
3272
				$smcFunc['db_query']('', '
3273
					ALTER TABLE {raw:table_name}{raw:updates_text}',
3274
					array(
3275
						'table_name' => $table_info['Name'],
3276
						'updates_text' => substr($updates_text, 0, -1),
3277
					)
3278
				);
3279
			}
3280
3281
			// Now do the actual conversion (if still needed).
3282
			if ($charsets[$upcontext['charset_detected']] !== 'utf8')
3283
			{
3284
				if ($command_line)
3285
					echo 'Converting table ' . $table_info['Name'] . ' to UTF-8...';
3286
3287
				$smcFunc['db_query']('', '
3288
					ALTER TABLE {raw:table_name}
3289
					CONVERT TO CHARACTER SET utf8',
3290
					array(
3291
						'table_name' => $table_info['Name'],
3292
					)
3293
				);
3294
3295
				if ($command_line)
3296
					echo " done.\n";
3297
			}
3298
			// If this is XML to keep it nice for the user do one table at a time anyway!
3299
			if (isset($_GET['xml']) && $upcontext['cur_table_num'] < $upcontext['table_count'])
3300
				return upgradeExit();
3301
		}
3302
3303
		$prev_charset = empty($translation_tables[$upcontext['charset_detected']]) ? $charsets[$upcontext['charset_detected']] : $translation_tables[$upcontext['charset_detected']];
3304
3305
		$smcFunc['db_insert']('replace',
3306
			'{db_prefix}settings',
3307
			array('variable' => 'string', 'value' => 'string'),
3308
			array(array('global_character_set', 'UTF-8'), array('previousCharacterSet', $prev_charset)),
3309
			array('variable')
3310
		);
3311
3312
		// Store it in Settings.php too because it's needed before db connection.
3313
		// Hopefully this works...
3314
		require_once($sourcedir . '/Subs.php');
3315
		require_once($sourcedir . '/Subs-Admin.php');
3316
		updateSettingsFile(array('db_character_set' => 'utf8'));
3317
3318
		// The conversion might have messed up some serialized strings. Fix them!
3319
		$request = $smcFunc['db_query']('', '
3320
			SELECT id_action, extra
3321
			FROM {db_prefix}log_actions
3322
			WHERE action IN ({string:remove}, {string:delete})',
3323
			array(
3324
				'remove' => 'remove',
3325
				'delete' => 'delete',
3326
			)
3327
		);
3328
		while ($row = $smcFunc['db_fetch_assoc']($request))
3329
		{
3330
			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)
3331
				$smcFunc['db_query']('', '
3332
					UPDATE {db_prefix}log_actions
3333
					SET extra = {string:extra}
3334
					WHERE id_action = {int:current_action}',
3335
					array(
3336
						'current_action' => $row['id_action'],
3337
						'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4],
3338
					)
3339
				);
3340
		}
3341
		$smcFunc['db_free_result']($request);
3342
3343
		if ($upcontext['dropping_index'] && $command_line)
3344
		{
3345
			echo "\n" . '', $txt['upgrade_fulltext_error'], '';
3346
			flush();
3347
		}
3348
	}
3349
3350
	// Make sure we move on!
3351
	if ($command_line)
3352
		return DeleteUpgrade();
3353
3354
	$_GET['substep'] = 0;
3355
	return false;
3356
}
3357
3358
/**
3359
 * Attempts to repair corrupted serialized data strings
3360
 *
3361
 * @param string $string Serialized data that has been corrupted
3362
 * @return string|bool A working version of the serialized data, or the original if the repair failed
3363
 */
3364
function fix_serialized_data($string)
3365
{
3366
	// If its not broken, don't fix it.
3367
	if (!is_string($string) || !preg_match('/^[bidsa]:/', $string) || @safe_unserialize($string) !== false)
3368
		return $string;
3369
3370
	// This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8)
3371
	$new_string = preg_replace_callback('~\bs:(\d+):"(.*?)";(?=$|[bidsa]:|[{}]|N;)~s', function ($matches) {return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';}, $string);
3372
3373
	// @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc.
3374
3375
	// Did it work?
3376
	if (@safe_unserialize($new_string) !== false)
3377
		return $new_string;
3378
	else
3379
		return $string;
3380
}
3381
3382
function serialize_to_json()
3383
{
3384
	global $command_line, $smcFunc, $modSettings, $sourcedir, $upcontext, $support_js, $txt;
3385
3386
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json';
3387
	// First thing's first - did we already do this?
3388
	if (!empty($modSettings['json_done']))
3389
	{
3390
		if ($command_line)
3391
			return ConvertUtf8();
3392
		else
3393
			return true;
3394
	}
3395
3396
	// Done it already - js wise?
3397
	if (!empty($_POST['json_done']))
3398
		return true;
3399
3400
	// List of tables affected by this function
3401
	// name => array('key', col1[,col2|true[,col3]])
3402
	// If 3rd item in array is true, it indicates that col1 could be empty...
3403
	$tables = array(
3404
		'background_tasks' => array('id_task', 'task_data'),
3405
		'log_actions' => array('id_action', 'extra'),
3406
		'log_online' => array('session', 'url'),
3407
		'log_packages' => array('id_install', 'db_changes', 'failed_steps', 'credits'),
3408
		'log_spider_hits' => array('id_hit', 'url'),
3409
		'log_subscribed' => array('id_sublog', 'pending_details'),
3410
		'pm_rules' => array('id_rule', 'criteria', 'actions'),
3411
		'qanda' => array('id_question', 'answers'),
3412
		'subscriptions' => array('id_subscribe', 'cost'),
3413
		'user_alerts' => array('id_alert', 'extra', true),
3414
		'user_drafts' => array('id_draft', 'to_list', true),
3415
		// These last two are a bit different - we'll handle those separately
3416
		'settings' => array(),
3417
		'themes' => array()
3418
	);
3419
3420
	// Set up some context stuff...
3421
	// Because we're not using numeric indices, we need this to figure out the current table name...
3422
	$keys = array_keys($tables);
3423
3424
	$upcontext['page_title'] = $txt['converting_json'];
3425
	$upcontext['table_count'] = count($keys);
3426
	$upcontext['cur_table_num'] = $_GET['substep'];
3427
	$upcontext['cur_table_name'] = isset($keys[$_GET['substep']]) ? $keys[$_GET['substep']] : $keys[0];
3428
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3429
3430
	foreach ($keys as $id => $table)
3431
		if ($id < $_GET['substep'])
3432
			$upcontext['previous_tables'][] = $table;
3433
3434
	if ($command_line)
3435
		echo 'Converting data from serialize() to json_encode().';
3436
3437
	if (!$support_js || isset($_GET['xml']))
3438
	{
3439
		// Fix the data in each table
3440
		for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++)
3441
		{
3442
			$upcontext['cur_table_name'] = isset($keys[$substep + 1]) ? $keys[$substep + 1] : $keys[$substep];
3443
			$upcontext['cur_table_num'] = $substep + 1;
3444
3445
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3446
3447
			// Do we need to pause?
3448
			nextSubstep($substep);
3449
3450
			// Initialize a few things...
3451
			$where = '';
3452
			$vars = array();
3453
			$table = $keys[$substep];
3454
			$info = $tables[$table];
3455
3456
			// Now the fun - build our queries and all that fun stuff
3457
			if ($table == 'settings')
3458
			{
3459
				// Now a few settings...
3460
				$serialized_settings = array(
3461
					'attachment_basedirectories',
3462
					'attachmentUploadDir',
3463
					'cal_today_birthday',
3464
					'cal_today_event',
3465
					'cal_today_holiday',
3466
					'displayFields',
3467
					'last_attachments_directory',
3468
					'memberlist_cache',
3469
					'search_custom_index_config',
3470
					'spider_name_cache'
3471
				);
3472
3473
				// Loop through and fix these...
3474
				$new_settings = array();
3475
				if ($command_line)
3476
					echo "\n" . 'Fixing some settings...';
3477
3478
				foreach ($serialized_settings as $var)
3479
				{
3480
					if (isset($modSettings[$var]))
3481
					{
3482
						// Attempt to unserialize the setting
3483
						$temp = @safe_unserialize($modSettings[$var]);
3484
						// Maybe conversion to UTF-8 corrupted it
3485
						if ($temp === false)
3486
							$temp = @safe_unserialize(fix_serialized_data($modSettings[$var]));
3487
3488
						if (!$temp && $command_line)
3489
							echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping.";
3490
						elseif ($temp !== false)
3491
							$new_settings[$var] = json_encode($temp);
3492
					}
3493
				}
3494
3495
				// Update everything at once
3496
				if (!function_exists('cache_put_data'))
3497
					require_once($sourcedir . '/Load.php');
3498
				updateSettings($new_settings, true);
3499
3500
				if ($command_line)
3501
					echo ' done.';
3502
			}
3503
			elseif ($table == 'themes')
3504
			{
3505
				// Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point...
3506
				$query = $smcFunc['db_query']('', '
3507
					SELECT id_member, id_theme, value FROM {db_prefix}themes
3508
					WHERE variable = {string:admin_prefs}',
3509
					array(
3510
						'admin_prefs' => 'admin_preferences'
3511
					)
3512
				);
3513
3514
				if ($smcFunc['db_num_rows']($query) != 0)
3515
				{
3516
					while ($row = $smcFunc['db_fetch_assoc']($query))
3517
					{
3518
						$temp = @safe_unserialize($row['value']);
3519
						if ($temp === false)
3520
							$temp = @safe_unserialize(fix_serialized_data($row['value']));
3521
3522
						if ($command_line)
3523
						{
3524
							if ($temp === false)
3525
								echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.';
3526
							else
3527
								echo "\n" . 'Fixing admin preferences...';
3528
						}
3529
3530
						if ($temp !== false)
3531
						{
3532
							$row['value'] = json_encode($temp);
3533
3534
							// Even though we have all values from the table, UPDATE is still faster than REPLACE
3535
							$smcFunc['db_query']('', '
3536
								UPDATE {db_prefix}themes
3537
								SET value = {string:prefs}
3538
								WHERE id_theme = {int:theme}
3539
									AND id_member = {int:member}
3540
									AND variable = {string:admin_prefs}',
3541
								array(
3542
									'prefs' => $row['value'],
3543
									'theme' => $row['id_theme'],
3544
									'member' => $row['id_member'],
3545
									'admin_prefs' => 'admin_preferences'
3546
								)
3547
							);
3548
3549
							if ($command_line)
3550
								echo ' done.';
3551
						}
3552
					}
3553
3554
					$smcFunc['db_free_result']($query);
3555
				}
3556
			}
3557
			else
3558
			{
3559
				// First item is always the key...
3560
				$key = $info[0];
3561
				unset($info[0]);
3562
3563
				// Now we know what columns we have and such...
3564
				if (count($info) == 2 && $info[2] === true)
3565
				{
3566
					$col_select = $info[1];
3567
					$where = ' WHERE ' . $info[1] . ' != {empty}';
3568
				}
3569
				else
3570
				{
3571
					$col_select = implode(', ', $info);
3572
				}
3573
3574
				$query = $smcFunc['db_query']('', '
3575
					SELECT ' . $key . ', ' . $col_select . '
3576
					FROM {db_prefix}' . $table . $where,
3577
					array()
3578
				);
3579
3580
				if ($smcFunc['db_num_rows']($query) != 0)
3581
				{
3582
					if ($command_line)
3583
					{
3584
						echo "\n" . ' +++ Fixing the "' . $table . '" table...';
3585
						flush();
3586
					}
3587
3588
					while ($row = $smcFunc['db_fetch_assoc']($query))
3589
					{
3590
						$update = '';
3591
3592
						// We already know what our key is...
3593
						foreach ($info as $col)
3594
						{
3595
							if ($col !== true && $row[$col] != '')
3596
							{
3597
								$temp = @safe_unserialize($row[$col]);
3598
3599
								// Maybe we can fix the data?
3600
								if ($temp === false)
3601
									$temp = @safe_unserialize(fix_serialized_data($row[$col]));
3602
3603
								// Maybe the data is already JSON?
3604
								if ($temp === false)
3605
									$temp = smf_json_decode($row[$col], true, false);
3606
3607
								// Oh well...
3608
								if ($temp === null)
3609
								{
3610
									$temp = array();
3611
3612
									if ($command_line)
3613
										echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n";
3614
								}
3615
3616
								$row[$col] = json_encode($temp);
3617
3618
								// Build our SET string and variables array
3619
								$update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}';
3620
								$vars[$col] = $row[$col];
3621
							}
3622
						}
3623
3624
						$vars[$key] = $row[$key];
3625
3626
						// In a few cases, we might have empty data, so don't try to update in those situations...
3627
						if (!empty($update))
3628
						{
3629
							$smcFunc['db_query']('', '
3630
								UPDATE {db_prefix}' . $table . '
3631
								SET ' . $update . '
3632
								WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}',
3633
								$vars
3634
							);
3635
						}
3636
					}
3637
3638
					if ($command_line)
3639
						echo ' done.';
3640
3641
					// Free up some memory...
3642
					$smcFunc['db_free_result']($query);
3643
				}
3644
			}
3645
			// If this is XML to keep it nice for the user do one table at a time anyway!
3646
			if (isset($_GET['xml']))
3647
				return upgradeExit();
3648
		}
3649
3650
		if ($command_line)
3651
		{
3652
			echo "\n" . 'Successful.' . "\n";
3653
			flush();
3654
		}
3655
		$upcontext['step_progress'] = 100;
3656
3657
		// Last but not least, insert a dummy setting so we don't have to do this again in the future...
3658
		updateSettings(array('json_done' => true));
3659
3660
		$_GET['substep'] = 0;
3661
		// Make sure we move on!
3662
		if ($command_line)
3663
			return ConvertUtf8();
3664
3665
		return true;
3666
	}
3667
3668
	// If this fails we just move on to deleting the upgrade anyway...
3669
	$_GET['substep'] = 0;
3670
	return false;
3671
}
3672
3673
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3674
						Templates are below this point
3675
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
3676
3677
// This is what is displayed if there's any chmod to be done. If not it returns nothing...
3678
function template_chmod()
3679
{
3680
	global $upcontext, $txt, $settings;
3681
3682
	// Don't call me twice!
3683
	if (!empty($upcontext['chmod_called']))
3684
		return;
3685
3686
	$upcontext['chmod_called'] = true;
3687
3688
	// Nothing?
3689
	if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error']))
3690
		return;
3691
3692
	// Was it a problem with Windows?
3693
	if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess')
3694
	{
3695
		echo '
3696
		<div class="error">
3697
			<p>', $txt['upgrade_writable_files'], '</p>
3698
			<ul class="error_content">
3699
				<li>' . implode('</li>
3700
				<li>', $upcontext['chmod']['files']) . '</li>
3701
			</ul>
3702
		</div>';
3703
3704
		return false;
3705
	}
3706
3707
	echo '
3708
		<div class="panel">
3709
			<h2>', $txt['upgrade_ftp_login'], '</h2>
3710
			<h3>', $txt['upgrade_ftp_perms'], '</h3>
3711
			<script>
3712
				function warning_popup()
3713
				{
3714
					popup = window.open(\'\',\'popup\',\'height=150,width=400,scrollbars=yes\');
3715
					var content = popup.document;
3716
					content.write(\'<!DOCTYPE html>\n\');
3717
					content.write(\'<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>\n\t<head>\n\t\t<meta name="robots" content="noindex">\n\t\t\');
3718
					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\');
3719
					content.write(\'<div class="windowbg description">\n\t\t\t<h4>', $txt['upgrade_ftp_files'], '</h4>\n\t\t\t\');
3720
					content.write(\'<p>', implode('<br>\n\t\t\t', $upcontext['chmod']['files']), '</p>\n\t\t\t\');';
3721
3722
	if (isset($upcontext['systemos']) && $upcontext['systemos'] == 'linux')
3723
		echo '
3724
					content.write(\'<hr>\n\t\t\t\');
3725
					content.write(\'<p>', $txt['upgrade_ftp_shell'], '</p>\n\t\t\t\');
3726
					content.write(\'<tt># chmod a+w ', implode(' ', $upcontext['chmod']['files']), '</tt>\n\t\t\t\');';
3727
3728
	echo '
3729
					content.write(\'<a href="javascript:self.close();">close</a>\n\t\t</div>\n\t</body>\n</html>\');
3730
					content.close();
3731
				}
3732
			</script>';
3733
3734
	if (!empty($upcontext['chmod']['ftp_error']))
3735
		echo '
3736
			<div class="error">
3737
				<p>', $txt['upgrade_ftp_error'], '<p>
3738
				<code>', $upcontext['chmod']['ftp_error'], '</code>
3739
			</div>';
3740
3741
	if (empty($upcontext['chmod_in_form']))
3742
		echo '
3743
			<form action="', $upcontext['form_url'], '" method="post">';
3744
3745
	echo '
3746
				<dl class="settings">
3747
					<dt>
3748
						<label for="ftp_server">', $txt['ftp_server'], ':</label>
3749
					</dt>
3750
					<dd>
3751
						<div class="floatright">
3752
							<label for="ftp_port" class="textbox"><strong>', $txt['ftp_port'], ':</strong></label>
3753
							<input type="text" size="3" name="ftp_port" id="ftp_port" value="', isset($upcontext['chmod']['port']) ? $upcontext['chmod']['port'] : '21', '">
3754
						</div>
3755
						<input type="text" size="30" name="ftp_server" id="ftp_server" value="', isset($upcontext['chmod']['server']) ? $upcontext['chmod']['server'] : 'localhost', '">
3756
						<div class="smalltext">', $txt['ftp_server_info'], '</div>
3757
					</dd>
3758
					<dt>
3759
						<label for="ftp_username">', $txt['ftp_username'], ':</label>
3760
					</dt>
3761
					<dd>
3762
						<input type="text" size="30" name="ftp_username" id="ftp_username" value="', isset($upcontext['chmod']['username']) ? $upcontext['chmod']['username'] : '', '">
3763
						<div class="smalltext">', $txt['ftp_username_info'], '</div>
3764
					</dd>
3765
					<dt>
3766
						<label for="ftp_password">', $txt['ftp_password'], ':</label>
3767
					</dt>
3768
					<dd>
3769
						<input type="password" size="30" name="ftp_password" id="ftp_password">
3770
						<div class="smalltext">', $txt['ftp_password_info'], '</div>
3771
					</dd>
3772
					<dt>
3773
						<label for="ftp_path">', $txt['ftp_path'], ':</label>
3774
					</dt>
3775
					<dd>
3776
						<input type="text" size="30" name="ftp_path" id="ftp_path" value="', isset($upcontext['chmod']['path']) ? $upcontext['chmod']['path'] : '', '">
3777
						<div class="smalltext">', !empty($upcontext['chmod']['path']) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info'], '</div>
3778
					</dd>
3779
				</dl>
3780
3781
				<div class="righttext buttons">
3782
					<input type="submit" value="', $txt['ftp_connect'], '" class="button">
3783
				</div>';
3784
3785
	if (empty($upcontext['chmod_in_form']))
3786
		echo '
3787
			</form>';
3788
3789
	echo '
3790
		</div><!-- .panel -->';
3791
}
3792
3793
function template_upgrade_above()
3794
{
3795
	global $modSettings, $txt, $settings, $upcontext, $upgradeurl;
3796
3797
	echo '<!DOCTYPE html>
3798
<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>
3799
<head>
3800
	<meta charset="', isset($txt['lang_character_set']) ? $txt['lang_character_set'] : 'UTF-8', '">
3801
	<meta name="robots" content="noindex">
3802
	<title>', $txt['upgrade_upgrade_utility'], '</title>
3803
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">
3804
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/install.css">
3805
	', $txt['lang_rtl'] == true ? '<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/rtl.css">' : '', '
3806
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
3807
	<script src="', $settings['default_theme_url'], '/scripts/script.js"></script>
3808
	<script>
3809
		var smf_scripturl = \'', $upgradeurl, '\';
3810
		var smf_charset = \'', (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'UTF-8' : $txt['lang_character_set']) : $modSettings['global_character_set']), '\';
3811
		var startPercent = ', $upcontext['overall_percent'], ';
3812
3813
		// This function dynamically updates the step progress bar - and overall one as required.
3814
		function updateStepProgress(current, max, overall_weight)
3815
		{
3816
			// What out the actual percent.
3817
			var width = parseInt((current / max) * 100);
3818
			if (document.getElementById(\'step_progress\'))
3819
			{
3820
				document.getElementById(\'step_progress\').style.width = width + "%";
3821
				setInnerHTML(document.getElementById(\'step_text\'), width + "%");
3822
			}
3823
			if (overall_weight && document.getElementById(\'overall_progress\'))
3824
			{
3825
				overall_width = parseInt(startPercent + width * (overall_weight / 100));
3826
				document.getElementById(\'overall_progress\').style.width = overall_width + "%";
3827
				setInnerHTML(document.getElementById(\'overall_text\'), overall_width + "%");
3828
			}
3829
		}
3830
	</script>
3831
</head>
3832
<body>
3833
	<div id="footerfix">
3834
	<div id="header">
3835
		<h1 class="forumtitle">', $txt['upgrade_upgrade_utility'], '</h1>
3836
		<img id="smflogo" src="', $settings['default_theme_url'], '/images/smflogo.svg" alt="Simple Machines Forum" title="Simple Machines Forum">
3837
	</div>
3838
	<div id="wrapper">
3839
		<div id="content_section">
3840
			<div id="main_content_section">
3841
				<div id="main_steps">
3842
					<h2>', $txt['upgrade_progress'], '</h2>
3843
					<ul class="steps_list">';
3844
3845
	foreach ($upcontext['steps'] as $num => $step)
3846
		echo '
3847
						<li', $num == $upcontext['current_step'] ? ' class="stepcurrent"' : '', '>
3848
							', $txt['upgrade_step'], ' ', $step[0], ': ', $txt[$step[1]], '
3849
						</li>';
3850
3851
	echo '
3852
					</ul>
3853
				</div><!-- #main_steps -->
3854
3855
				<div id="install_progress">
3856
					<div id="progress_bar" class="progress_bar progress_green">
3857
						<h3>', $txt['upgrade_overall_progress'], '</h3>
3858
						<div id="overall_progress" class="bar" style="width: ', $upcontext['overall_percent'], '%;"></div>
3859
						<span id="overall_text">', $upcontext['overall_percent'], '%</span>
3860
					</div>';
3861
3862
	if (isset($upcontext['step_progress']))
3863
		echo '
3864
					<div id="progress_bar_step" class="progress_bar progress_yellow">
3865
						<h3>', $txt['upgrade_step_progress'], '</h3>
3866
						<div id="step_progress" class="bar" style="width: ', $upcontext['step_progress'], '%;"></div>
3867
						<span id="step_text">', $upcontext['step_progress'], '%</span>
3868
					</div>';
3869
3870
	echo '
3871
					<div id="substep_bar_div" class="progress_bar ', isset($upcontext['substep_progress']) ? '' : 'hidden', '">
3872
						<h3 id="substep_name">', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], array('.' => ''))) : '', '</h3>
3873
						<div id="substep_progress" class="bar" style="width: ', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%;"></div>
3874
						<span id="substep_text">', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%</span>
3875
					</div>';
3876
3877
	// How long have we been running this?
3878
	$elapsed = time() - $upcontext['started'];
3879
	$mins = (int) ($elapsed / 60);
3880
	$seconds = $elapsed - $mins * 60;
3881
	echo '
3882
					<div class="smalltext time_elapsed">
3883
						', $txt['upgrade_time_elapsed'], ':
3884
						<span id="mins_elapsed">', $mins, '</span> ', $txt['upgrade_time_mins'], ', <span id="secs_elapsed">', $seconds, '</span> ', $txt['upgrade_time_secs'], '.
3885
					</div>';
3886
	echo '
3887
				</div><!-- #install_progress -->
3888
				<div id="main_screen" class="clear">
3889
					<h2>', $upcontext['page_title'], '</h2>
3890
					<div class="panel">';
3891
}
3892
3893
function template_upgrade_below()
3894
{
3895
	global $upcontext, $txt;
3896
3897
	if (!empty($upcontext['pause']))
3898
		echo '
3899
							<em>', $txt['upgrade_incomplete'], '.</em><br>
3900
3901
							<h2 style="margin-top: 2ex;">', $txt['upgrade_not_quite_done'], '</h2>
3902
							<h3>
3903
								', $txt['upgrade_paused_overload'], '
3904
							</h3>';
3905
3906
	if (!empty($upcontext['custom_warning']))
3907
		echo '
3908
							<div class="errorbox">
3909
								<h3>', $txt['upgrade_note'], '</h3>
3910
								', $upcontext['custom_warning'], '
3911
							</div>';
3912
3913
	echo '
3914
							<div class="righttext buttons">';
3915
3916
	if (!empty($upcontext['continue']))
3917
		echo '
3918
								<input type="submit" id="contbutt" name="contbutt" value="', $txt['upgrade_continue'], '"', $upcontext['continue'] == 2 ? ' disabled' : '', ' class="button">';
3919
	if (!empty($upcontext['skip']))
3920
		echo '
3921
								<input type="submit" id="skip" name="skip" value="', $txt['upgrade_skip'], '" onclick="dontSubmit = true; document.getElementById(\'contbutt\').disabled = \'disabled\'; return true;" class="button">';
3922
3923
	echo '
3924
							</div>
3925
						</form>
3926
					</div><!-- .panel -->
3927
				</div><!-- #main_screen -->
3928
			</div><!-- #main_content_section -->
3929
		</div><!-- #content_section -->
3930
	</div><!-- #wrapper -->
3931
	</div><!-- #footerfix -->
3932
	<div id="footer">
3933
		<ul>
3934
			<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>
3935
		</ul>
3936
	</div>';
3937
3938
	// Are we on a pause?
3939
	if (!empty($upcontext['pause']))
3940
	{
3941
		echo '
3942
	<script>
3943
		window.onload = doAutoSubmit;
3944
		var countdown = 3;
3945
		var dontSubmit = false;
3946
3947
		function doAutoSubmit()
3948
		{
3949
			if (countdown == 0 && !dontSubmit)
3950
				document.upform.submit();
3951
			else if (countdown == -1)
3952
				return;
3953
3954
			document.getElementById(\'contbutt\').value = "', $txt['upgrade_continue'], ' (" + countdown + ")";
3955
			countdown--;
3956
3957
			setTimeout("doAutoSubmit();", 1000);
3958
		}
3959
	</script>';
3960
	}
3961
3962
	echo '
3963
</body>
3964
</html>';
3965
}
3966
3967
function template_xml_above()
3968
{
3969
	global $upcontext;
3970
3971
	echo '<', '?xml version="1.0" encoding="UTF-8"?', '>
3972
	<smf>';
3973
3974
	if (!empty($upcontext['get_data']))
3975
		foreach ($upcontext['get_data'] as $k => $v)
3976
			echo '
3977
		<get key="', $k, '">', $v, '</get>';
3978
}
3979
3980
function template_xml_below()
3981
{
3982
	echo '
3983
	</smf>';
3984
}
3985
3986
function template_error_message()
3987
{
3988
	global $upcontext;
3989
3990
	echo '
3991
	<div class="error">
3992
		', $upcontext['error_msg'], '
3993
		<br>
3994
		<a href="', $_SERVER['PHP_SELF'], '">Click here to try again.</a>
3995
	</div>';
3996
}
3997
3998
function template_welcome_message()
3999
{
4000
	global $upcontext, $disable_security, $settings, $txt;
4001
4002
	echo '
4003
				<script src="https://www.simplemachines.org/smf/current-version.js?version=' . SMF_VERSION . '"></script>
4004
4005
				<h3>', sprintf($txt['upgrade_ready_proceed'], SMF_VERSION), '</h3>
4006
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">
4007
					<input type="hidden" name="', $upcontext['login_token_var'], '" value="', $upcontext['login_token'], '">
4008
4009
					<div id="version_warning" class="noticebox hidden">
4010
						<h3>', $txt['upgrade_warning'], '</h3>
4011
						', sprintf($txt['upgrade_warning_out_of_date'], SMF_VERSION, 'https://www.simplemachines.org'), '
4012
					</div>';
4013
4014
	$upcontext['chmod_in_form'] = true;
4015
	template_chmod();
4016
4017
	// For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade!
4018
	if ($upcontext['is_large_forum'])
4019
		echo '
4020
					<div class="errorbox">
4021
						<h3>', $txt['upgrade_warning'], '</h3>
4022
						', $txt['upgrade_warning_lots_data'], '
4023
					</div>';
4024
4025
	// A warning message?
4026
	if (!empty($upcontext['warning']))
4027
		echo '
4028
					<div class="errorbox">
4029
						<h3>', $txt['upgrade_warning'], '</h3>
4030
						', $upcontext['warning'], '
4031
					</div>';
4032
4033
	// Paths are incorrect?
4034
	echo '
4035
					<div class="errorbox', (file_exists($settings['default_theme_dir'] . '/scripts/script.js') ? ' hidden' : ''), '" id="js_script_missing_error">
4036
						<h3>', $txt['upgrade_critical_error'], '</h3>
4037
						', sprintf($txt['upgrade_error_script_js'], 'https://download.simplemachines.org/?tools'), '
4038
					</div>';
4039
4040
	// Is there someone already doing this?
4041
	if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600))
4042
	{
4043
		$ago = time() - $upcontext['started'];
4044
		$ago_hours = floor($ago / 3600);
4045
		$ago_minutes = intval(($ago / 60) % 60);
4046
		$ago_seconds = intval($ago % 60);
4047
		$agoTxt = $ago < 60 ? 'upgrade_time_s' : ($ago < 3600 ? 'upgrade_time_ms' : 'upgrade_time_hms');
4048
4049
		$updated = time() - $upcontext['updated'];
4050
		$updated_hours = floor($updated / 3600);
4051
		$updated_minutes = intval(($updated / 60) % 60);
4052
		$updated_seconds = intval($updated % 60);
4053
		$updatedTxt = $updated < 60 ? 'upgrade_time_updated_s' : ($updated < 3600 ? 'upgrade_time_updated_hm' : 'upgrade_time_updated_hms');
4054
4055
		echo '
4056
					<div class="errorbox">
4057
						<h3>', $txt['upgrade_warning'], '</h3>
4058
						<p>', sprintf($txt['upgrade_time_user'], $upcontext['user']['name']), '</p>
4059
						<p>', sprintf($txt[$agoTxt], $ago_seconds, $ago_minutes, $ago_hours), '</p>
4060
						<p>', sprintf($txt[$updatedTxt], $updated_seconds, $updated_minutes, $updated_hours), '</p>';
4061
4062
		if ($updated < 600)
4063
			echo '
4064
						<p>', $txt['upgrade_run_script'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_run_script2'], '</p>';
4065
4066
		if ($updated > $upcontext['inactive_timeout'])
4067
			echo '
4068
						<p>', $txt['upgrade_run'], '</p>';
4069
		elseif ($upcontext['inactive_timeout'] > 120)
4070
			echo '
4071
						<p>', sprintf($txt['upgrade_script_timeout_minutes'], $upcontext['user']['name'], round($upcontext['inactive_timeout'] / 60, 1)), '</p>';
4072
		else
4073
			echo '
4074
						<p>', sprintf($txt['upgrade_script_timeout_seconds'], $upcontext['user']['name'], $upcontext['inactive_timeout']), '</p>';
4075
4076
		echo '
4077
					</div>';
4078
	}
4079
4080
	echo '
4081
					<strong>', $txt['upgrade_admin_login'], ' ', $disable_security ? '(DISABLED)' : '', '</strong>
4082
					<h3>', $txt['upgrade_sec_login'], '</h3>
4083
					<dl class="settings adminlogin">
4084
						<dt>
4085
							<label for="user"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_username'], '</label>
4086
						</dt>
4087
						<dd>
4088
							<input type="text" name="user" value="', !empty($upcontext['username']) ? $upcontext['username'] : '', '"', $disable_security ? ' disabled' : '', '>';
4089
4090
	if (!empty($upcontext['username_incorrect']))
4091
		echo '
4092
							<div class="smalltext red">', $txt['upgrade_wrong_username'], '</div>';
4093
4094
	echo '
4095
						</dd>
4096
						<dt>
4097
							<label for="passwrd"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_password'], '</label>
4098
						</dt>
4099
						<dd>
4100
							<input type="password" name="passwrd" value=""', $disable_security ? ' disabled' : '', '>';
4101
4102
	if (!empty($upcontext['password_failed']))
4103
		echo '
4104
							<div class="smalltext red">', $txt['upgrade_wrong_password'], '</div>';
4105
4106
	echo '
4107
						</dd>';
4108
4109
	// Can they continue?
4110
	if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1)
4111
	{
4112
		echo '
4113
						<dd>
4114
							<label for="cont"><input type="checkbox" id="cont" name="cont" checked>', $txt['upgrade_continue_step'], '</label>
4115
						</dd>';
4116
	}
4117
4118
	echo '
4119
					</dl>
4120
					<span class="smalltext">
4121
						', $txt['upgrade_bypass'], '
4122
					</span>
4123
					<input type="hidden" name="login_attempt" id="login_attempt" value="1">
4124
					<input type="hidden" name="js_works" id="js_works" value="0">';
4125
4126
	// Say we want the continue button!
4127
	$upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1;
4128
4129
	// This defines whether javascript is going to work elsewhere :D
4130
	echo '
4131
					<script>
4132
						if (\'XMLHttpRequest\' in window && document.getElementById(\'js_works\'))
4133
							document.getElementById(\'js_works\').value = 1;
4134
4135
						// Latest version?
4136
						function smfCurrentVersion()
4137
						{
4138
							var smfVer, yourVer;
4139
4140
							if (!(\'smfVersion\' in window))
4141
								return;
4142
4143
							window.smfVersion = window.smfVersion.replace(/SMF\s?/g, \'\');
4144
4145
							smfVer = document.getElementById(\'smfVersion\');
4146
							yourVer = document.getElementById(\'yourVersion\');
4147
4148
							setInnerHTML(smfVer, window.smfVersion);
4149
4150
							var currentVersion = getInnerHTML(yourVer);
4151
							if (currentVersion < window.smfVersion)
4152
								document.getElementById(\'version_warning\').classList.remove(\'hidden\');
4153
						}
4154
						addLoadEvent(smfCurrentVersion);
4155
4156
						// This checks that the script file even exists!
4157
						if (typeof(smfSelectText) == \'undefined\')
4158
							document.getElementById(\'js_script_missing_error\').classList.remove(\'hidden\');
4159
4160
					</script>';
4161
}
4162
4163
function template_upgrade_options()
4164
{
4165
	global $upcontext, $modSettings, $db_prefix, $mmessage, $mtitle, $txt;
4166
4167
	echo '
4168
				<h3>', $txt['upgrade_areyouready'], '</h3>
4169
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">';
4170
4171
	// Warning message?
4172
	if (!empty($upcontext['upgrade_options_warning']))
4173
		echo '
4174
				<div class="errorbox">
4175
					<h3>', $txt['upgrade_warning'], '</h3>
4176
					', $upcontext['upgrade_options_warning'], '
4177
				</div>';
4178
4179
	echo '
4180
				<ul class="upgrade_settings">
4181
					<li>
4182
						<input type="checkbox" name="backup" id="backup" value="1">
4183
						<label for="backup">', $txt['upgrade_backup_table'], ' &quot;backup_' . $db_prefix . '&quot;.</label>
4184
						(', $txt['upgrade_recommended'], ')
4185
					</li>
4186
					<li>
4187
						<input type="checkbox" name="maint" id="maint" value="1" checked>
4188
						<label for="maint">', $txt['upgrade_maintenance'], '</label>
4189
						<span class="smalltext">(<a href="javascript:void(0)" onclick="document.getElementById(\'mainmess\').classList.toggle(\'hidden\')">', $txt['upgrade_customize'], '</a>)</span>
4190
						<div id="mainmess" class="hidden">
4191
							<strong class="smalltext">', $txt['upgrade_maintenance_title'], ' </strong><br>
4192
							<input type="text" name="maintitle" size="30" value="', htmlspecialchars($mtitle), '"><br>
4193
							<strong class="smalltext">', $txt['upgrade_maintenance_message'], ' </strong><br>
4194
							<textarea name="mainmessage" rows="3" cols="50">', htmlspecialchars($mmessage), '</textarea>
4195
						</div>
4196
					</li>
4197
					<li>
4198
						<input type="checkbox" name="debug" id="debug" value="1">
4199
						<label for="debug">'.$txt['upgrade_debug_info'], '</label>
4200
					</li>
4201
					<li>
4202
						<input type="checkbox" name="empty_error" id="empty_error" value="1">
4203
						<label for="empty_error">', $txt['upgrade_empty_errorlog'], '</label>
4204
					</li>';
4205
4206
	if (!empty($upcontext['karma_installed']['good']) || !empty($upcontext['karma_installed']['bad']))
4207
		echo '
4208
					<li>
4209
						<input type="checkbox" name="delete_karma" id="delete_karma" value="1">
4210
						<label for="delete_karma">', $txt['upgrade_delete_karma'], '</label>
4211
					</li>';
4212
4213
	echo '
4214
					<li>
4215
						<input type="checkbox" name="stats" id="stats" value="1"', empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']) ? '' : ' checked="checked"', '>
4216
						<label for="stat">
4217
							', $txt['upgrade_stats_collection'], '<br>
4218
							<span class="smalltext">', sprintf($txt['upgrade_stats_info'], 'https://www.simplemachines.org/about/stats.php'), '</a></span>
4219
						</label>
4220
					</li>
4221
					<li>
4222
						<input type="checkbox" name="migrateSettings" id="migrateSettings" value="1"', empty($upcontext['migrate_settings_recommended']) ? '' : ' checked="checked"', '>
4223
						<label for="migrateSettings">
4224
							', $txt['upgrade_migrate_settings_file'], '
4225
						</label>
4226
					</li>
4227
				</ul>
4228
				<input type="hidden" name="upcont" value="1">';
4229
4230
	// We need a normal continue button here!
4231
	$upcontext['continue'] = 1;
4232
}
4233
4234
// Template for the database backup tool/
4235
function template_backup_database()
4236
{
4237
	global $upcontext, $support_js, $is_debug, $txt;
4238
4239
	echo '
4240
				<h3>', $txt['upgrade_wait'], '</h3>';
4241
4242
	echo '
4243
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4244
					<input type="hidden" name="backup_done" id="backup_done" value="0">
4245
					<strong>', sprintf($txt['upgrade_completedtables_outof'], $upcontext['cur_table_num'], $upcontext['table_count']), '</strong>
4246
					<div id="debug_section">
4247
						<span id="debuginfo"></span>
4248
					</div>';
4249
4250
	// Dont any tables so far?
4251
	if (!empty($upcontext['previous_tables']))
4252
		foreach ($upcontext['previous_tables'] as $table)
4253
			echo '
4254
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4255
4256
	echo '
4257
					<h3 id="current_tab">
4258
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4259
					</h3>
4260
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_backup_complete'], '</p>';
4261
4262
	// Continue please!
4263
	$upcontext['continue'] = $support_js ? 2 : 1;
4264
4265
	// If javascript allows we want to do this using XML.
4266
	if ($support_js)
4267
	{
4268
		echo '
4269
					<script>
4270
						var lastTable = ', $upcontext['cur_table_num'], ';
4271
						function getNextTables()
4272
						{
4273
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4274
						}
4275
4276
						// Got an update!
4277
						function onBackupUpdate(oXMLDoc)
4278
						{
4279
							var sCurrentTableName = "";
4280
							var iTableNum = 0;
4281
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4282
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4283
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4284
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4285
4286
							// Update the page.
4287
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4288
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4289
							lastTable = iTableNum;
4290
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4291
4292
		// If debug flood the screen.
4293
		if ($is_debug)
4294
			echo '
4295
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4296
4297
							if (document.getElementById(\'debug_section\').scrollHeight)
4298
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4299
4300
		echo '
4301
							// Get the next update...
4302
							if (iTableNum == ', $upcontext['table_count'], ')
4303
							{
4304
								document.getElementById(\'commess\').classList.remove("hidden");
4305
								document.getElementById(\'current_tab\').classList.add("hidden");
4306
								document.getElementById(\'contbutt\').disabled = 0;
4307
								document.getElementById(\'backup_done\').value = 1;
4308
							}
4309
							else
4310
								getNextTables();
4311
						}
4312
						getNextTables();
4313
					//# sourceURL=dynamicScript-bkup.js
4314
					</script>';
4315
	}
4316
}
4317
4318
function template_backup_xml()
4319
{
4320
	global $upcontext;
4321
4322
	echo '
4323
		<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4324
}
4325
4326
// Here is the actual "make the changes" template!
4327
function template_database_changes()
4328
{
4329
	global $upcontext, $support_js, $is_debug, $timeLimitThreshold, $txt;
4330
4331
	if (empty($is_debug) && !empty($upcontext['upgrade_status']['debug']))
4332
		$is_debug = true;
4333
4334
	echo '
4335
				<h3>', $txt['upgrade_db_changes'], '</h3>
4336
				<h4><em>', $txt['upgrade_db_patient'], '</em></h4>';
4337
4338
	echo '
4339
				<form action="', $upcontext['form_url'], '&amp;filecount=', $upcontext['file_count'], '" name="upform" id="upform" method="post">
4340
					<input type="hidden" name="database_done" id="database_done" value="0">';
4341
4342
	// No javascript looks rubbish!
4343
	if (!$support_js)
4344
	{
4345
		foreach ($upcontext['actioned_items'] as $num => $item)
4346
		{
4347
			if ($num != 0)
4348
				echo ' Successful!';
4349
			echo '<br>' . $item;
4350
		}
4351
4352
		// Only tell deubbers how much time they wasted waiting for the upgrade because they don't have javascript.
4353
		if (!empty($upcontext['changes_complete']))
4354
		{
4355
			if ($is_debug)
4356
			{
4357
				$active = time() - $upcontext['started'];
4358
				$hours = floor($active / 3600);
4359
				$minutes = intval(($active / 60) % 60);
4360
				$seconds = intval($active % 60);
4361
4362
				echo '', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '<br>';
4363
			}
4364
			else
4365
				echo '', $txt['upgrade_success'], '<br>';
4366
4367
			echo '
4368
					<p id="commess">', $txt['upgrade_db_complete'], '</p>';
4369
		}
4370
	}
4371
	else
4372
	{
4373
		// Tell them how many files we have in total.
4374
		if ($upcontext['file_count'] > 1)
4375
			echo '
4376
					<strong id="info1">', $txt['upgrade_script'], ' <span id="file_done">', $upcontext['cur_file_num'], '</span> of ', $upcontext['file_count'], '.</strong>';
4377
4378
		echo '
4379
					<h3 id="info2">
4380
						<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>
4381
					</h3>
4382
					<p id="commess" class="', !empty($upcontext['changes_complete']) || $upcontext['current_debug_item_num'] == $upcontext['debug_items'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_db_complete2'], '</p>';
4383
4384
		if ($is_debug)
4385
		{
4386
			// Let our debuggers know how much time was spent, but not wasted since JS handled refreshing the page!
4387
			if ($upcontext['current_debug_item_num'] == $upcontext['debug_items'])
4388
			{
4389
				$active = time() - $upcontext['started'];
4390
				$hours = floor($active / 3600);
4391
				$minutes = intval(($active / 60) % 60);
4392
				$seconds = intval($active % 60);
4393
4394
				echo '
4395
					<p id="upgradeCompleted">', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '</p>';
4396
			}
4397
			else
4398
				echo '
4399
					<p id="upgradeCompleted"></p>';
4400
4401
			echo '
4402
					<div id="debug_section">
4403
						<span id="debuginfo"></span>
4404
					</div>';
4405
		}
4406
	}
4407
4408
	// Place for the XML error message.
4409
	echo '
4410
					<div id="error_block" class="errorbox', empty($upcontext['error_message']) ? ' hidden' : '', '">
4411
						<h3>', $txt['upgrade_error'], '</h3>
4412
						<div id="error_message">', isset($upcontext['error_message']) ? $upcontext['error_message'] : $txt['upgrade_unknown_error'], '</div>
4413
					</div>';
4414
4415
	// We want to continue at some point!
4416
	$upcontext['continue'] = $support_js ? 2 : 1;
4417
4418
	// If javascript allows we want to do this using XML.
4419
	if ($support_js)
4420
	{
4421
		echo '
4422
					<script>
4423
						var lastItem = ', $upcontext['current_debug_item_num'], ';
4424
						var sLastString = "', strtr($upcontext['current_debug_item_name'], array('"' => '&quot;')), '";
4425
						var iLastSubStepProgress = -1;
4426
						var curFile = ', $upcontext['cur_file_num'], ';
4427
						var totalItems = 0;
4428
						var prevFile = 0;
4429
						var retryCount = 0;
4430
						var testvar = 0;
4431
						var timeOutID = 0;
4432
						var getData = "";
4433
						var debugItems = ', $upcontext['debug_items'], ';';
4434
4435
		if ($is_debug)
4436
			echo '
4437
						var upgradeStartTime = ' . $upcontext['started'] . ';';
4438
4439
		echo '
4440
						function getNextItem()
4441
						{
4442
							// We want to track this...
4443
							if (timeOutID)
4444
								clearTimeout(timeOutID);
4445
							timeOutID = window.setTimeout("retTimeout()", ', (10 * $timeLimitThreshold), '000);
4446
4447
							getXMLDocument(\'', $upcontext['form_url'], '&xml&filecount=', $upcontext['file_count'], '&substep=\' + lastItem + getData, onItemUpdate);
4448
						}
4449
4450
						// Got an update!
4451
						function onItemUpdate(oXMLDoc)
4452
						{
4453
							var sItemName = "";
4454
							var sDebugName = "";
4455
							var iItemNum = 0;
4456
							var iSubStepProgress = -1;
4457
							var iDebugNum = 0;
4458
							var bIsComplete = 0;
4459
							var bSkipped = 0;
4460
							getData = "";
4461
4462
							// We\'ve got something - so reset the timeout!
4463
							if (timeOutID)
4464
								clearTimeout(timeOutID);
4465
4466
							// Assume no error at this time...
4467
							document.getElementById("error_block").classList.add("hidden");
4468
4469
							// Are we getting some duff info?
4470
							if (!oXMLDoc.getElementsByTagName("item")[0])
4471
							{
4472
								// Too many errors?
4473
								if (retryCount > 15)
4474
								{
4475
									document.getElementById("error_block").classList.remove("hidden");
4476
									setInnerHTML(document.getElementById("error_message"), "Error retrieving information on step: " + (sDebugName == "" ? sLastString : sDebugName));';
4477
4478
		if ($is_debug)
4479
			echo '
4480
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4481
4482
		echo '
4483
								}
4484
								else
4485
								{
4486
									retryCount++;
4487
									getNextItem();
4488
								}
4489
								return false;
4490
							}
4491
4492
							// Never allow loops.
4493
							if (curFile == prevFile)
4494
							{
4495
								retryCount++;
4496
								if (retryCount > 10)
4497
								{
4498
									document.getElementById("error_block").classList.remove("hidden");
4499
									setInnerHTML(document.getElementById("error_message"), "', $txt['upgrade_loop'], '" + sDebugName);';
4500
4501
		if ($is_debug)
4502
			echo '
4503
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4504
4505
		echo '
4506
								}
4507
							}
4508
							retryCount = 0;
4509
4510
							for (var i = 0; i < oXMLDoc.getElementsByTagName("item")[0].childNodes.length; i++)
4511
								sItemName += oXMLDoc.getElementsByTagName("item")[0].childNodes[i].nodeValue;
4512
							for (var i = 0; i < oXMLDoc.getElementsByTagName("debug")[0].childNodes.length; i++)
4513
								sDebugName += oXMLDoc.getElementsByTagName("debug")[0].childNodes[i].nodeValue;
4514
							for (var i = 0; i < oXMLDoc.getElementsByTagName("get").length; i++)
4515
							{
4516
								getData += "&" + oXMLDoc.getElementsByTagName("get")[i].getAttribute("key") + "=";
4517
								for (var j = 0; j < oXMLDoc.getElementsByTagName("get")[i].childNodes.length; j++)
4518
								{
4519
									getData += oXMLDoc.getElementsByTagName("get")[i].childNodes[j].nodeValue;
4520
								}
4521
							}
4522
4523
							iItemNum = oXMLDoc.getElementsByTagName("item")[0].getAttribute("num");
4524
							iDebugNum = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("num"));
4525
							bIsComplete = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("complete"));
4526
							bSkipped = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("skipped"));
4527
							iSubStepProgress = parseFloat(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("percent"));
4528
							sLastString = sDebugName + " (Item: " + iDebugNum + ")";
4529
4530
							curFile = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("num"));
4531
							debugItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("debug_items"));
4532
							totalItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("items"));
4533
4534
							// If we have an error we haven\'t completed!
4535
							if (oXMLDoc.getElementsByTagName("error")[0] && bIsComplete)
4536
								iDebugNum = lastItem;
4537
4538
							// Do we have the additional progress bar?
4539
							if (iSubStepProgress != -1)
4540
							{
4541
								document.getElementById("substep_bar_div").classList.remove("hidden");
4542
								document.getElementById("substep_progress").style.width = iSubStepProgress + "%";
4543
								setInnerHTML(document.getElementById("substep_text"), iSubStepProgress + "%");
4544
								setInnerHTML(document.getElementById("substep_name"), sDebugName.replace(/\./g, ""));
4545
							}
4546
							else
4547
							{
4548
								document.getElementById("substep_bar_div").classList.add("hidden");
4549
							}
4550
4551
							// Move onto the next item?
4552
							if (bIsComplete)
4553
								lastItem = iDebugNum;
4554
							else
4555
								lastItem = iDebugNum - 1;
4556
4557
							// Are we finished?
4558
							if (bIsComplete && iDebugNum == -1 && curFile >= ', $upcontext['file_count'], ')
4559
							{';
4560
4561
		// Database Changes, tell us how much time we spen to do this.  If this gets updated via JS.
4562
		if ($is_debug)
4563
			echo '
4564
								document.getElementById(\'debug_section\').classList.add("hidden");
4565
4566
								var upgradeFinishedTime = parseInt(oXMLDoc.getElementsByTagName("curtime")[0].childNodes[0].nodeValue);
4567
								var diffTime = upgradeFinishedTime - upgradeStartTime;
4568
								var diffHours = Math.floor(diffTime / 3600);
4569
								var diffMinutes = parseInt((diffTime / 60) % 60);
4570
								var diffSeconds = parseInt(diffTime % 60);
4571
4572
								var completedTxt = "', $txt['upgrade_success_time_db'], '";
4573
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4574
4575
								completedTxt = completedTxt.replace("%1$d", diffSeconds).replace("%2$d", diffMinutes).replace("%3$d", diffHours);
4576
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4577
								setInnerHTML(document.getElementById("upgradeCompleted"), completedTxt);';
4578
4579
		echo '
4580
4581
								document.getElementById(\'commess\').classList.remove("hidden");
4582
								document.getElementById(\'contbutt\').disabled = 0;
4583
								document.getElementById(\'database_done\').value = 1;';
4584
4585
		if ($upcontext['file_count'] > 1)
4586
			echo '
4587
								document.getElementById(\'info1\').classList.add(\'hidden\');';
4588
4589
		echo '
4590
								document.getElementById(\'info2\').classList.add(\'hidden\');
4591
								updateStepProgress(100, 100, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4592
								return true;
4593
							}
4594
							// Was it the last step in the file?
4595
							else if (bIsComplete && iDebugNum == -1)
4596
							{
4597
								lastItem = 0;
4598
								prevFile = curFile;';
4599
4600
		if ($is_debug)
4601
			echo '
4602
								setOuterHTML(document.getElementById(\'debuginfo\'), \'Moving to next script file...done<br><span id="debuginfo"><\' + \'/span>\');';
4603
4604
		echo '
4605
								getNextItem();
4606
								return true;
4607
							}';
4608
4609
		// If debug scroll the screen.
4610
		if ($is_debug)
4611
			echo '
4612
							if (iLastSubStepProgress == -1)
4613
							{
4614
								// Give it consistent dots.
4615
								dots = sDebugName.match(/\./g);
4616
								numDots = dots ? dots.length : 0;
4617
								for (var i = numDots; i < 3; i++)
4618
									sDebugName += ".";
4619
								setOuterHTML(document.getElementById(\'debuginfo\'), sDebugName + \'<span id="debuginfo"><\' + \'/span>\');
4620
							}
4621
							iLastSubStepProgress = iSubStepProgress;
4622
4623
							if (bIsComplete && bSkipped)
4624
								setOuterHTML(document.getElementById(\'debuginfo\'), \'skipped<br><span id="debuginfo"><\' + \'/span>\');
4625
							else if (bIsComplete)
4626
								setOuterHTML(document.getElementById(\'debuginfo\'), \'done<br><span id="debuginfo"><\' + \'/span>\');
4627
							else
4628
								setOuterHTML(document.getElementById(\'debuginfo\'), \'...<span id="debuginfo"><\' + \'/span>\');
4629
4630
							if (document.getElementById(\'debug_section\').scrollHeight)
4631
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4632
4633
		echo '
4634
							// Update the page.
4635
							setInnerHTML(document.getElementById(\'item_num\'), iItemNum);
4636
							setInnerHTML(document.getElementById(\'cur_item_name\'), sItemName);';
4637
4638
		if ($upcontext['file_count'] > 1)
4639
		{
4640
			echo '
4641
							setInnerHTML(document.getElementById(\'file_done\'), curFile);
4642
							setInnerHTML(document.getElementById(\'item_count\'), totalItems);';
4643
		}
4644
4645
		echo '
4646
							// Is there an error?
4647
							if (oXMLDoc.getElementsByTagName("error")[0])
4648
							{
4649
								var sErrorMsg = "";
4650
								for (var i = 0; i < oXMLDoc.getElementsByTagName("error")[0].childNodes.length; i++)
4651
									sErrorMsg += oXMLDoc.getElementsByTagName("error")[0].childNodes[i].nodeValue;
4652
								document.getElementById("error_block").classList.remove("hidden");
4653
								setInnerHTML(document.getElementById("error_message"), sErrorMsg);
4654
								return false;
4655
							}
4656
4657
							// Get the progress bar right.
4658
							barTotal = debugItems * ', $upcontext['file_count'], ';
4659
							barDone = (debugItems * (curFile - 1)) + lastItem;
4660
4661
							updateStepProgress(barDone, barTotal, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4662
4663
							// Finally - update the time here as it shows the server is responding!
4664
							curTime = new Date();
4665
							iElapsed = (curTime.getTime() / 1000 - ', $upcontext['started'], ');
4666
							mins = parseInt(iElapsed / 60);
4667
							secs = parseInt(iElapsed - mins * 60);
4668
							setInnerHTML(document.getElementById("mins_elapsed"), mins);
4669
							setInnerHTML(document.getElementById("secs_elapsed"), secs);
4670
4671
							getNextItem();
4672
							return true;
4673
						}
4674
4675
						// What if we timeout?!
4676
						function retTimeout(attemptAgain)
4677
						{
4678
							// Oh noes...
4679
							if (!attemptAgain)
4680
							{
4681
								document.getElementById("error_block").classList.remove("hidden");
4682
								setInnerHTML(document.getElementById("error_message"), "', sprintf($txt['upgrade_respondtime'], ($timeLimitThreshold * 10)), '" + "<a href=\"#\" onclick=\"retTimeout(true); return false;\">', $txt['upgrade_respondtime_clickhere'], '</a>");
4683
							}
4684
							else
4685
							{
4686
								document.getElementById("error_block").classList.add("hidden");
4687
								getNextItem();
4688
							}
4689
						}';
4690
4691
		// Start things off assuming we've not errored.
4692
		if (empty($upcontext['error_message']))
4693
			echo '
4694
						getNextItem();';
4695
4696
		echo '
4697
					//# sourceURL=dynamicScript-dbch.js
4698
					</script>';
4699
	}
4700
	return;
4701
}
4702
4703
function template_database_xml()
4704
{
4705
	global $is_debug, $upcontext;
4706
4707
	echo '
4708
	<file num="', $upcontext['cur_file_num'], '" items="', $upcontext['total_items'], '" debug_items="', $upcontext['debug_items'], '">', $upcontext['cur_file_name'], '</file>
4709
	<item num="', $upcontext['current_item_num'], '">', $upcontext['current_item_name'], '</item>
4710
	<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>';
4711
4712
	if (!empty($upcontext['error_message']))
4713
		echo '
4714
	<error>', $upcontext['error_message'], '</error>';
4715
4716
	if (!empty($upcontext['error_string']))
4717
		echo '
4718
	<sql>', $upcontext['error_string'], '</sql>';
4719
4720
	if ($is_debug)
4721
		echo '
4722
	<curtime>', time(), '</curtime>';
4723
}
4724
4725
// Template for the UTF-8 conversion step. Basically a copy of the backup stuff with slight modifications....
4726
function template_convert_utf8()
4727
{
4728
	global $upcontext, $support_js, $is_debug, $txt;
4729
4730
	echo '
4731
				<h3>', $txt['upgrade_wait2'], '</h3>
4732
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4733
					<input type="hidden" name="utf8_done" id="utf8_done" value="0">
4734
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4735
					<div id="debug_section">
4736
						<span id="debuginfo"></span>
4737
					</div>';
4738
4739
	// Done any tables so far?
4740
	if (!empty($upcontext['previous_tables']))
4741
		foreach ($upcontext['previous_tables'] as $table)
4742
			echo '
4743
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4744
4745
	echo '
4746
					<h3 id="current_tab">
4747
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4748
					</h3>';
4749
4750
	// If we dropped their index, let's let them know
4751
	if ($upcontext['dropping_index'])
4752
		echo '
4753
					<p id="indexmsg" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '>', $txt['upgrade_fulltext'], '</p>';
4754
4755
	// Completion notification
4756
	echo '
4757
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_conversion_proceed'], '</p>';
4758
4759
	// Continue please!
4760
	$upcontext['continue'] = $support_js ? 2 : 1;
4761
4762
	// If javascript allows we want to do this using XML.
4763
	if ($support_js)
4764
	{
4765
		echo '
4766
					<script>
4767
						var lastTable = ', $upcontext['cur_table_num'], ';
4768
						function getNextTables()
4769
						{
4770
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onConversionUpdate);
4771
						}
4772
4773
						// Got an update!
4774
						function onConversionUpdate(oXMLDoc)
4775
						{
4776
							var sCurrentTableName = "";
4777
							var iTableNum = 0;
4778
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4779
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4780
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4781
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4782
4783
							// Update the page.
4784
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4785
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4786
							lastTable = iTableNum;
4787
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4788
4789
		// If debug flood the screen.
4790
		if ($is_debug)
4791
			echo '
4792
						setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4793
4794
						if (document.getElementById(\'debug_section\').scrollHeight)
4795
							document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4796
4797
		echo '
4798
						// Get the next update...
4799
						if (iTableNum == ', $upcontext['table_count'], ')
4800
						{
4801
							document.getElementById(\'commess\').classList.remove(\'hidden\');
4802
							if (document.getElementById(\'indexmsg\') != null) {
4803
								document.getElementById(\'indexmsg\').classList.remove(\'hidden\');
4804
							}
4805
							document.getElementById(\'current_tab\').classList.add(\'hidden\');
4806
							document.getElementById(\'contbutt\').disabled = 0;
4807
							document.getElementById(\'utf8_done\').value = 1;
4808
						}
4809
						else
4810
							getNextTables();
4811
					}
4812
					getNextTables();
4813
				//# sourceURL=dynamicScript-conv.js
4814
				</script>';
4815
	}
4816
}
4817
4818
function template_convert_xml()
4819
{
4820
	global $upcontext;
4821
4822
	echo '
4823
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4824
}
4825
4826
// Template for the database backup tool/
4827
function template_serialize_json()
4828
{
4829
	global $upcontext, $support_js, $is_debug, $txt;
4830
4831
	echo '
4832
				<h3>', $txt['upgrade_convert_datajson'], '</h3>
4833
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4834
					<input type="hidden" name="json_done" id="json_done" value="0">
4835
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4836
					<div id="debug_section">
4837
						<span id="debuginfo"></span>
4838
					</div>';
4839
4840
	// Dont any tables so far?
4841
	if (!empty($upcontext['previous_tables']))
4842
		foreach ($upcontext['previous_tables'] as $table)
4843
			echo '
4844
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4845
4846
	echo '
4847
					<h3 id="current_tab">
4848
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4849
					</h3>
4850
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_json_completed'], '</p>';
4851
4852
	// Try to make sure substep was reset.
4853
	if ($upcontext['cur_table_num'] == $upcontext['table_count'])
4854
		echo '
4855
					<input type="hidden" name="substep" id="substep" value="0">';
4856
4857
	// Continue please!
4858
	$upcontext['continue'] = $support_js ? 2 : 1;
4859
4860
	// If javascript allows we want to do this using XML.
4861
	if ($support_js)
4862
	{
4863
		echo '
4864
					<script>
4865
						var lastTable = ', $upcontext['cur_table_num'], ';
4866
						function getNextTables()
4867
						{
4868
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4869
						}
4870
4871
						// Got an update!
4872
						function onBackupUpdate(oXMLDoc)
4873
						{
4874
							var sCurrentTableName = "";
4875
							var iTableNum = 0;
4876
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4877
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4878
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4879
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4880
4881
							// Update the page.
4882
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4883
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4884
							lastTable = iTableNum;
4885
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4886
4887
		// If debug flood the screen.
4888
		if ($is_debug)
4889
			echo '
4890
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4891
4892
							if (document.getElementById(\'debug_section\').scrollHeight)
4893
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4894
4895
		echo '
4896
							// Get the next update...
4897
							if (iTableNum == ', $upcontext['table_count'], ')
4898
							{
4899
								document.getElementById(\'commess\').classList.remove("hidden");
4900
								document.getElementById(\'current_tab\').classList.add("hidden");
4901
								document.getElementById(\'contbutt\').disabled = 0;
4902
								document.getElementById(\'json_done\').value = 1;
4903
							}
4904
							else
4905
								getNextTables();
4906
						}
4907
						getNextTables();
4908
					//# sourceURL=dynamicScript-json.js
4909
					</script>';
4910
	}
4911
}
4912
4913
function template_serialize_json_xml()
4914
{
4915
	global $upcontext;
4916
4917
	echo '
4918
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4919
}
4920
4921
function template_upgrade_complete()
4922
{
4923
	global $upcontext, $upgradeurl, $settings, $boardurl, $is_debug, $txt;
4924
4925
	echo '
4926
				<h3>', $txt['upgrade_done'], ' <a href="', $boardurl, '/index.php">', $txt['upgrade_done2'], '</a>.  ', $txt['upgrade_done3'], '</h3>
4927
				<form action="', $boardurl, '/index.php">';
4928
4929
	if (!empty($upcontext['can_delete_script']))
4930
		echo '
4931
					<label>
4932
						<input type="checkbox" id="delete_self" onclick="doTheDelete(this);"> ', $txt['upgrade_delete_now'], '
4933
					</label>
4934
					<em>', $txt['upgrade_delete_server'], '</em>
4935
					<script>
4936
						function doTheDelete(theCheck)
4937
						{
4938
							var theImage = document.getElementById ? document.getElementById("delete_upgrader") : document.all.delete_upgrader;
4939
							theImage.src = "', $upgradeurl, '?delete=1&ts_" + (new Date().getTime());
4940
							theCheck.disabled = true;
4941
						}
4942
					</script>
4943
					<img src="', $settings['default_theme_url'], '/images/blank.png" alt="" id="delete_upgrader"><br>';
4944
4945
	// Show Upgrade time in debug mode when we completed the upgrade process totally
4946
	if ($is_debug)
4947
	{
4948
		$active = time() - $upcontext['started'];
4949
		$hours = floor($active / 3600);
4950
		$minutes = intval(($active / 60) % 60);
4951
		$seconds = intval($active % 60);
4952
4953
		if ($hours > 0)
4954
			echo '', sprintf($txt['upgrade_completed_time_hms'], $seconds, $minutes, $hours), '';
4955
		elseif ($minutes > 0)
4956
			echo '', sprintf($txt['upgrade_completed_time_ms'], $seconds, $minutes), '';
4957
		elseif ($seconds > 0)
4958
			echo '', sprintf($txt['upgrade_completed_time_s'], $seconds), '';
4959
	}
4960
4961
	echo '
4962
					<p>
4963
						', sprintf($txt['upgrade_problems'], 'http://simplemachines.org'), '
4964
						<br>
4965
						', $txt['upgrade_luck'], '<br>
4966
						Simple Machines
4967
					</p>';
4968
}
4969
4970
/**
4971
 * Convert MySQL (var)char ip col to binary
4972
 *
4973
 * @param string $targetTable The table to perform the operation on
4974
 * @param string $oldCol The old column to gather data from
4975
 * @param string $newCol The new column to put data in
4976
 * @param int $limit The amount of entries to handle at once.
4977
 * @param int $setSize The amount of entries after which to update the database.
4978
 *
4979
 * newCol needs to be a varbinary(16) null able field
4980
 * @return bool
4981
 */
4982
function MySQLConvertOldIp($targetTable, $oldCol, $newCol, $limit = 50000, $setSize = 100)
4983
{
4984
	global $smcFunc, $step_progress;
4985
4986
	$current_substep = !isset($_GET['substep']) ? 0 : (int) $_GET['substep'];
4987
4988
	if (empty($_GET['a']))
4989
		$_GET['a'] = 0;
4990
	$step_progress['name'] = 'Converting ips';
4991
	$step_progress['current'] = $_GET['a'];
4992
4993
	// Skip this if we don't have the column
4994
	$request = $smcFunc['db_query']('', '
4995
		SHOW FIELDS
4996
		FROM {db_prefix}{raw:table}
4997
		WHERE Field = {string:name}',
4998
		array(
4999
			'table' => $targetTable,
5000
			'name' => $oldCol,
5001
		)
5002
	);
5003
	if ($smcFunc['db_num_rows']($request) !== 1)
5004
	{
5005
		$smcFunc['db_free_result']($request);
5006
		return;
5007
	}
5008
	$smcFunc['db_free_result']($request);
5009
5010
	$is_done = false;
5011
	while (!$is_done)
5012
	{
5013
		// Keep looping at the current step.
5014
		nextSubstep($current_substep);
5015
5016
		// mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html
5017
		$arIp = array();
5018
5019
		$request = $smcFunc['db_query']('', '
5020
			SELECT DISTINCT {raw:old_col}
5021
			FROM {db_prefix}{raw:table_name}
5022
			WHERE {raw:new_col} IS NULL AND
5023
				{raw:old_col} != {string:unknown} AND
5024
				{raw:old_col} != {string:empty}
5025
			LIMIT {int:limit}',
5026
			array(
5027
				'old_col' => $oldCol,
5028
				'new_col' => $newCol,
5029
				'table_name' => $targetTable,
5030
				'empty' => '',
5031
				'limit' => $limit,
5032
				'unknown' => 'unknown',
5033
			)
5034
		);
5035
		while ($row = $smcFunc['db_fetch_assoc']($request))
5036
			$arIp[] = $row[$oldCol];
5037
5038
		$smcFunc['db_free_result']($request);
5039
5040
		// Special case, null ip could keep us in a loop.
5041
		if (!isset($arIp[0]))
5042
			unset($arIp[0]);
5043
5044
		if (empty($arIp))
5045
			$is_done = true;
5046
5047
		$updates = array();
5048
		$cases = array();
5049
		$count = count($arIp);
5050
		for ($i = 0; $i < $count; $i++)
5051
		{
5052
			$arIp[$i] = trim($arIp[$i]);
5053
5054
			if (empty($arIp[$i]))
5055
				continue;
5056
5057
			$updates['ip' . $i] = $arIp[$i];
5058
			$cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:ip' . $i . '}';
5059
5060
			if ($setSize > 0 && $i % $setSize === 0)
5061
			{
5062
				if (count($updates) == 1)
5063
					continue;
5064
5065
				$updates['whereSet'] = array_values($updates);
5066
				$smcFunc['db_query']('', '
5067
					UPDATE {db_prefix}' . $targetTable . '
5068
					SET ' . $newCol . ' = CASE ' .
5069
					implode('
5070
						', $cases) . '
5071
						ELSE NULL
5072
					END
5073
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
5074
					$updates
5075
				);
5076
5077
				$updates = array();
5078
				$cases = array();
5079
			}
5080
		}
5081
5082
		// Incase some extras made it through.
5083
		if (!empty($updates))
5084
		{
5085
			if (count($updates) == 1)
5086
			{
5087
				foreach ($updates as $key => $ip)
5088
				{
5089
					$smcFunc['db_query']('', '
5090
						UPDATE {db_prefix}' . $targetTable . '
5091
						SET ' . $newCol . ' = {inet:ip}
5092
						WHERE ' . $oldCol . ' = {string:ip}',
5093
						array(
5094
							'ip' => $ip
5095
						)
5096
					);
5097
				}
5098
			}
5099
			else
5100
			{
5101
				$updates['whereSet'] = array_values($updates);
5102
				$smcFunc['db_query']('', '
5103
					UPDATE {db_prefix}' . $targetTable . '
5104
					SET ' . $newCol . ' = CASE ' .
5105
					implode('
5106
						', $cases) . '
5107
						ELSE NULL
5108
					END
5109
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
5110
					$updates
5111
				);
5112
			}
5113
		}
5114
		else
5115
			$is_done = true;
5116
5117
		$_GET['a'] += $limit;
5118
		$step_progress['current'] = $_GET['a'];
5119
	}
5120
5121
	unset($_GET['a']);
5122
}
5123
5124
/**
5125
 * Get the column info. This is basically the same as smf_db_list_columns but we get 1 column, force detail and other checks.
5126
 *
5127
 * @param string $targetTable The table to perform the operation on
5128
 * @param string $column The column we are looking for.
5129
 *
5130
 * @return array Info on the table.
5131
 */
5132
function upgradeGetColumnInfo($targetTable, $column)
5133
{
5134
	global $smcFunc;
5135
5136
	// This should already be here, but be safe.
5137
	db_extend('packages');
5138
5139
	$columns = $smcFunc['db_list_columns']($targetTable, true);
5140
5141
	if (isset($columns[$column]))
5142
		return $columns[$column];
5143
	else
5144
		return null;
5145
}
5146
5147
?>