Issues (1061)

other/upgrade.php (4 issues)

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

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

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