Issues (1065)

other/upgrade.php (45 issues)

1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines https://www.simplemachines.org
8
 * @copyright 2025 Simple Machines and individual contributors
9
 * @license https://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1.6
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1.6');
16
define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION);
17
define('SMF_SOFTWARE_YEAR', '2025');
18
define('SMF_LANG_VERSION', '2.1.5');
19
define('SMF_INSTALLING', 1);
20
21
define('JQUERY_VERSION', '3.6.3');
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'] = '7.1.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.6.0',
44
		'version_check' => function() {
45
			global $db_connection;
46
			if (!function_exists('mysqli_fetch_row'))
47
				return false;
48
			return mysqli_fetch_row(mysqli_query($db_connection, 'SELECT VERSION();'))[0];
0 ignored issues
show
It seems like mysqli_query($db_connection, 'SELECT VERSION();') can also be of type true; however, parameter $result of mysqli_fetch_row() does only seem to accept mysqli_result, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

Loading history...
807
		}
808
809
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
810
			$smcFunc['db_query']('', '
811
				SET NAMES {string:db_character_set}',
812
				array(
813
					'db_error_skip' => true,
814
					'db_character_set' => $db_character_set,
815
				)
816
			);
817
818
		// Load the modSettings data...
819
		$request = $smcFunc['db_query']('', '
820
			SELECT variable, value
821
			FROM {db_prefix}settings',
822
			array(
823
				'db_error_skip' => true,
824
			)
825
		);
826
		$modSettings = array();
827
		while ($row = $smcFunc['db_fetch_assoc']($request))
828
			$modSettings[$row['variable']] = $row['value'];
829
		$smcFunc['db_free_result']($request);
830
	}
831
	else
832
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
0 ignored issues
show
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

832
		return /** @scrutinizer ignore-call */ throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
Loading history...
833
834
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
835
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
836
	{
837
		require_once($sourcedir . '/QueryString.php');
838
		cleanRequest();
839
	}
840
841
	if (!isset($_GET['substep']))
842
		$_GET['substep'] = 0;
843
}
844
845
function initialize_inputs()
846
{
847
	global $start_time, $db_type, $upgrade_path;
848
849
	$start_time = time();
850
851
	umask(0);
852
853
	ob_start();
854
855
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
856
	ignore_user_abort(true);
857
858
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
859
	if (isset($_GET['delete']))
860
	{
861
		deleteFile(__FILE__);
0 ignored issues
show
The function deleteFile was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

861
		/** @scrutinizer ignore-call */ 
862
  deleteFile(__FILE__);
Loading history...
862
863
		// And the extra little files ;).
864
		deleteFile(dirname(__FILE__) . '/upgrade_1-0.sql');
865
		deleteFile(dirname(__FILE__) . '/upgrade_1-1.sql');
866
		deleteFile(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
867
		deleteFile(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
868
		deleteFile(dirname(__FILE__) . '/upgrade-helper.php');
869
870
		$dh = opendir(dirname(__FILE__));
871
		while ($file = readdir($dh))
872
		{
873
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
874
				deleteFile(dirname(__FILE__) . '/' . $file);
875
		}
876
		closedir($dh);
877
878
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
879
		// 1.1 Sources files not in 2.0+
880
		deleteFile($upgrade_path . '/Sources/ModSettings.php');
881
		// 1.1 Templates that don't exist any more (e.g. renamed)
882
		deleteFile($upgrade_path . '/Themes/default/Combat.template.php');
883
		deleteFile($upgrade_path . '/Themes/default/Modlog.template.php');
884
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
885
		deleteFile($upgrade_path . '/Themes/default/fader.js');
886
		deleteFile($upgrade_path . '/Themes/default/script.js');
887
		deleteFile($upgrade_path . '/Themes/default/spellcheck.js');
888
		deleteFile($upgrade_path . '/Themes/default/xml_board.js');
889
		deleteFile($upgrade_path . '/Themes/default/xml_topic.js');
890
891
		// 2.0 Sources files not in 2.1+
892
		deleteFile($upgrade_path . '/Sources/DumpDatabase.php');
893
		deleteFile($upgrade_path . '/Sources/LockTopic.php');
894
895
		header('location: http' . (httpsOn() ? 's' : '') . '://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
896
		exit;
897
	}
898
899
	// Something is causing this to happen, and it's annoying.  Stop it.
900
	$temp = 'upgrade_php?step';
901
	while (strlen($temp) > 4)
902
	{
903
		if (isset($_GET[$temp]))
904
			unset($_GET[$temp]);
905
		$temp = substr($temp, 1);
906
	}
907
908
	// Force a step, defaulting to 0.
909
	$_GET['step'] = (int) @$_GET['step'];
910
	$_GET['substep'] = (int) @$_GET['substep'];
911
}
912
913
// Step 0 - Let's welcome them in and ask them to login!
914
function WelcomeLogin()
915
{
916
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
917
	global $smcFunc, $db_type, $databases, $boardurl, $upgrade_path;
918
919
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
920
	global $txt;
921
922
	$upcontext['sub_template'] = 'welcome_message';
923
924
	// Check for some key files - one template, one language, and a new and an old source file.
925
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
926
		&& @file_exists($sourcedir . '/QueryString.php')
927
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
928
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
929
930
	// Need legacy scripts?
931
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
932
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
933
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
934
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
935
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
936
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
937
938
	// We don't need "-utf8" files anymore...
939
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
940
941
	if (!$check)
942
		// 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.
943
		return throw_error($txt['error_upgrade_files_missing']);
0 ignored issues
show
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

943
		return /** @scrutinizer ignore-call */ throw_error($txt['error_upgrade_files_missing']);
Loading history...
944
945
	// Do they meet the install requirements?
946
	if (!php_version_check())
947
		return throw_error($txt['error_php_too_low']);
948
949
	if (!db_version_check())
950
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
951
952
	// Do some checks to make sure they have proper privileges
953
	db_extend('packages');
954
955
	// CREATE
956
	$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');
957
958
	// ALTER
959
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
960
961
	// DROP
962
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
963
964
	// Sorry... we need CREATE, ALTER and DROP
965
	if (!$create || !$alter || !$drop)
966
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
967
968
	// Do a quick version spot check.
969
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
0 ignored issues
show
It seems like @file($boarddir . '/index.php') can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

969
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
970
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
971
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
972
		return throw_error($txt['error_upgrade_old_files']);
973
974
	// What absolutely needs to be writable?
975
	$writable_files = array(
976
		$boarddir . '/Settings.php',
977
		$boarddir . '/Settings_bak.php',
978
	);
979
980
	// Only check for minified writable files if we have it enabled or not set.
981
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
982
		$writable_files += array(
983
			$modSettings['theme_dir'] . '/css/minified.css',
984
			$modSettings['theme_dir'] . '/scripts/minified.js',
985
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
986
		);
987
988
	// Do we need to add this setting?
989
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
990
991
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
992
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
993
994
	// This little fellow has to cooperate...
995
	quickFileWritable($custom_av_dir);
0 ignored issues
show
The function quickFileWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

995
	/** @scrutinizer ignore-call */ 
996
 quickFileWritable($custom_av_dir);
Loading history...
996
997
	// Are we good now?
998
	if (!is_writable($custom_av_dir))
999
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
1000
	elseif ($need_settings_update)
1001
	{
1002
		if (!function_exists('cache_put_data'))
1003
			require_once($sourcedir . '/Load.php');
1004
1005
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
1006
		updateSettings(array('custom_avatar_url' => $custom_av_url));
1007
	}
1008
1009
	require_once($sourcedir . '/Security.php');
1010
1011
	// Check the cache directory.
1012
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
1013
	if (!file_exists($cachedir_temp))
1014
		@mkdir($cachedir_temp);
1015
1016
	if (!file_exists($cachedir_temp))
1017
		return throw_error($txt['error_cache_not_found']);
1018
1019
	quickFileWritable($cachedir_temp . '/db_last_error.php');
1020
1021
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
1022
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
1023
	elseif (!isset($_GET['skiplang']))
1024
	{
1025
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
1026
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1027
1028
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1029
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
1030
	}
1031
1032
	if (!makeFilesWritable($writable_files))
0 ignored issues
show
The function makeFilesWritable was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1032
	if (!/** @scrutinizer ignore-call */ makeFilesWritable($writable_files))
Loading history...
1033
		return false;
1034
1035
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
1036
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
1037
		return throw_error($txt['error_agreement_not_writable']);
1038
1039
	// Upgrade the agreement.
1040
	elseif (isset($modSettings['agreement']))
1041
	{
1042
		$fp = fopen($boarddir . '/agreement.txt', 'w');
1043
		fwrite($fp, $modSettings['agreement']);
1044
		fclose($fp);
1045
	}
1046
1047
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
1048
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr($upgrade_path, array('/' => '', '\\' => '')))
1049
		$upcontext['warning'] = '
1050
			' . sprintf($txt['upgrade_forumdir_settings'], $boarddir, $upgrade_path) . '<br>
1051
			<ul>
1052
				<li>' . $txt['upgrade_forumdir'] . '  ' . $boarddir . '</li>
1053
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
1054
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
1055
			</ul>
1056
			' . $txt['upgrade_incorrect_settings'] . '';
1057
1058
	// Confirm mbstring is loaded...
1059
	if (!extension_loaded('mbstring'))
1060
		return throw_error($txt['install_no_mbstring']);
1061
1062
	// Confirm fileinfo is loaded...
1063
	if (!extension_loaded('fileinfo'))
1064
		return throw_error($txt['install_no_fileinfo']);
1065
1066
	// Check for https stream support.
1067
	$supported_streams = stream_get_wrappers();
1068
	if (!in_array('https', $supported_streams))
1069
		$upcontext['custom_warning'] = $txt['install_no_https'];
1070
1071
	// Make sure attachment & avatar folders exist.  Big problem if folks move or restructure sites upon upgrade.
1072
	checkFolders();
1073
1074
	// Either we're logged in or we're going to present the login.
1075
	if (checkLogin())
1076
		return true;
1077
1078
	$upcontext += createToken('login');
1079
1080
	return false;
1081
}
1082
1083
// Do a number of attachment & avatar folder checks.
1084
// Display a warning if issues found.  Does not force a hard stop.
1085
function checkFolders()
1086
{
1087
	global $modSettings, $upcontext, $txt, $command_line;
1088
1089
	$warnings = '';
1090
1091
	// First, check the avatar directory...
1092
	// Note it wasn't specified in yabbse, but there was no smfVersion either.
1093
	if (!empty($modSettings['smfVersion']) && !is_dir($modSettings['avatar_directory']))
1094
		$warnings .= $txt['warning_av_missing'];
1095
1096
	// Next, check the custom avatar directory...  Note this is optional in 2.0.
1097
	if (!empty($modSettings['custom_avatar_dir']) && !is_dir($modSettings['custom_avatar_dir']))
1098
	{
1099
		if (empty($warnings))
1100
			$warnings = $txt['warning_custom_av_missing'];
1101
		else
1102
			$warnings .= '<br><br>' . $txt['warning_custom_av_missing'];
1103
	}
1104
1105
	// Finally, attachment folders.
1106
	// A bit more complex, since it may be json or serialized, and it may be an array or just a string...
1107
1108
	// PHP currently has a terrible handling with unserialize in which errors are fatal and not catchable.  Lets borrow some code from the RFC that intends to fix this
1109
	// https://wiki.php.net/rfc/improve_unserialize_error_handling
1110
	try {
1111
    	set_error_handler(static function ($severity, $message, $file, $line) {
1112
			throw new \ErrorException($message, 0, $severity, $file, $line);
1113
		});
1114
		$ser_test = @unserialize($modSettings['attachmentUploadDir']);
1115
	} catch (\Throwable $e) {
1116
		$ser_test = false;
1117
	}
1118
	finally {
1119
	 	restore_error_handler();
1120
	}
1121
1122
	// Json is simple, it can be caught.
1123
	try {
1124
		$json_test = @json_decode($modSettings['attachmentUploadDir'], true);
1125
	} catch (\Throwable $e) {
1126
		$json_test = null;
1127
	}
1128
1129
	$string_test = !empty($modSettings['attachmentUploadDir']) && is_string($modSettings['attachmentUploadDir']) && is_dir($modSettings['attachmentUploadDir']);
1130
1131
	// String?
1132
	$attdr_problem_found = false;
1133
	if ($string_test === true)
1134
	{
1135
		// OK...
1136
	}
1137
	// An array already?
1138
	elseif (is_array($modSettings['attachmentUploadDir']))
1139
	{
1140
		foreach($modSettings['attachmentUploadDir'] AS $dir)
1141
			if (!empty($dir) && !is_dir($dir))
1142
				$attdr_problem_found = true;
1143
	}
1144
	// Serialized?
1145
	elseif ($ser_test !== false)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $ser_test does not seem to be defined for all execution paths leading up to this point.
Loading history...
1146
	{
1147
		if (is_array($ser_test))
1148
		{
1149
			foreach($ser_test AS $dir)
1150
			{
1151
				if (!empty($dir) && !is_dir($dir))
1152
					$attdr_problem_found = true;
1153
			}
1154
		}
1155
		else
1156
		{
1157
			if (!empty($ser_test) && !is_dir($ser_test))
1158
				$attdr_problem_found = true;
1159
		}
1160
	}
1161
	// Json?  Note the test returns null if encoding was unsuccessful
1162
	elseif ($json_test !== null)
1163
	{
1164
		if (is_array($json_test))
1165
		{
1166
			foreach($json_test AS $dir)
1167
			{
1168
				if (!is_dir($dir))
1169
					$attdr_problem_found = true;
1170
			}
1171
		}
1172
		else
1173
		{
1174
			if (!is_dir($json_test))
1175
				$attdr_problem_found = true;
1176
		}
1177
	}
1178
	// Unclear, needs a look...
1179
	else
1180
	{
1181
		$attdr_problem_found = true;
1182
	}
1183
1184
	if ($attdr_problem_found)
1185
	{
1186
		if (empty($warnings))
1187
			$warnings = $txt['warning_att_dir_missing'];
1188
		else
1189
			$warnings .= '<br><br>' . $txt['warning_att_dir_missing'];
1190
	}
1191
1192
	// Might be using CLI
1193
	if ($command_line)
1194
	{
1195
		// Change brs to new lines & display
1196
		if (!empty($warnings))
1197
		{
1198
			$warnings = str_replace('<br>', "\n", $warnings);
1199
			echo "\n\n" . $warnings . "\n\n";
1200
		}
1201
	}
1202
	else
1203
	{
1204
		// Might be adding to an existing warning...
1205
		if (!empty($warnings))
1206
		{
1207
			if (empty($upcontext['custom_warning']))
1208
				$upcontext['custom_warning'] = $warnings;
1209
			else
1210
				$upcontext['custom_warning'] .= '<br><br>' . $warnings;
1211
		}
1212
	}
1213
}
1214
1215
// Step 0.5: Does the login work?
1216
function checkLogin()
1217
{
1218
	global $modSettings, $upcontext, $disable_security;
1219
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
1220
1221
	// Are we trying to login?
1222
	if (isset($_POST['contbutt']) && (!empty($_POST['user']) || $disable_security))
1223
	{
1224
		// If we've disabled security pick a suitable name!
1225
		if (empty($_POST['user']))
1226
			$_POST['user'] = 'Administrator';
1227
1228
		// Before 2.0 these column names were different!
1229
		$oldDB = false;
1230
		if (empty($db_type) || $db_type == 'mysql')
1231
		{
1232
			$request = $smcFunc['db_query']('', '
1233
				SHOW COLUMNS
1234
				FROM {db_prefix}members
1235
				LIKE {string:member_name}',
1236
				array(
1237
					'member_name' => 'memberName',
1238
					'db_error_skip' => true,
1239
				)
1240
			);
1241
			if ($smcFunc['db_num_rows']($request) != 0)
1242
				$oldDB = true;
1243
			$smcFunc['db_free_result']($request);
1244
		}
1245
1246
		// Get what we believe to be their details.
1247
		if (!$disable_security)
1248
		{
1249
			if ($oldDB)
1250
				$request = $smcFunc['db_query']('', '
1251
					SELECT id_member, memberName AS member_name, passwd, id_group,
1252
						additionalGroups AS additional_groups, lngfile
1253
					FROM {db_prefix}members
1254
					WHERE memberName = {string:member_name}',
1255
					array(
1256
						'member_name' => $_POST['user'],
1257
						'db_error_skip' => true,
1258
					)
1259
				);
1260
			else
1261
				$request = $smcFunc['db_query']('', '
1262
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
1263
					FROM {db_prefix}members
1264
					WHERE member_name = {string:member_name}',
1265
					array(
1266
						'member_name' => $_POST['user'],
1267
						'db_error_skip' => true,
1268
					)
1269
				);
1270
			if ($smcFunc['db_num_rows']($request) != 0)
1271
			{
1272
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1273
1274
				$groups = explode(',', $addGroups);
1275
				$groups[] = $id_group;
1276
1277
				foreach ($groups as $k => $v)
1278
					$groups[$k] = (int) $v;
1279
1280
				$sha_passwd = sha1(strtolower($name) . $_REQUEST['passwrd']);
1281
1282
				// We don't use "-utf8" anymore...
1283
				$user_language = str_ireplace('-utf8', '', $user_language);
1284
			}
1285
			else
1286
				$upcontext['username_incorrect'] = true;
1287
1288
			$smcFunc['db_free_result']($request);
1289
		}
1290
		$upcontext['username'] = $_POST['user'];
1291
1292
		// Track whether javascript works!
1293
		if (isset($_POST['js_works']))
1294
		{
1295
			if (!empty($_POST['js_works']))
1296
			{
1297
				$upcontext['upgrade_status']['js'] = 1;
1298
				$support_js = 1;
1299
			}
1300
			else
1301
				$support_js = 0;
1302
		}
1303
1304
		// Note down the version we are coming from.
1305
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1306
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1307
1308
		// Didn't get anywhere?
1309
		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']))
1310
		{
1311
			// MD5?
1312
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1313
			if ($md5pass != $password)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.
Loading history...
1314
			{
1315
				$upcontext['password_failed'] = true;
1316
				// Disable the hashing this time.
1317
				$upcontext['disable_login_hashing'] = true;
1318
			}
1319
		}
1320
1321
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1322
		{
1323
			// Set the password.
1324
			if (!$disable_security)
1325
			{
1326
				// Do we actually have permission?
1327
				if (!in_array(1, $groups))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $groups does not seem to be defined for all execution paths leading up to this point.
Loading history...
1328
				{
1329
					$request = $smcFunc['db_query']('', '
1330
						SELECT permission
1331
						FROM {db_prefix}permissions
1332
						WHERE id_group IN ({array_int:groups})
1333
							AND permission = {string:admin_forum}',
1334
						array(
1335
							'groups' => $groups,
1336
							'admin_forum' => 'admin_forum',
1337
							'db_error_skip' => true,
1338
						)
1339
					);
1340
					if ($smcFunc['db_num_rows']($request) == 0)
1341
						return throw_error($txt['error_not_admin']);
0 ignored issues
show
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1341
						return /** @scrutinizer ignore-call */ throw_error($txt['error_not_admin']);
Loading history...
1342
					$smcFunc['db_free_result']($request);
1343
				}
1344
1345
				$upcontext['user']['id'] = $id_member;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_member does not seem to be defined for all execution paths leading up to this point.
Loading history...
1346
				$upcontext['user']['name'] = $name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name does not seem to be defined for all execution paths leading up to this point.
Loading history...
1347
			}
1348
			else
1349
			{
1350
				$upcontext['user']['id'] = 1;
1351
				$upcontext['user']['name'] = 'Administrator';
1352
			}
1353
1354
			if (!is_callable('random_int'))
1355
				require_once('Sources/random_compat/random.php');
1356
1357
			$upcontext['user']['pass'] = random_int(0, 60000);
1358
			// This basically is used to match the GET variables to Settings.php.
1359
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1360
1361
			// Set the language to that of the user?
1362
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1363
			{
1364
				$user_language = basename($user_language, '.lng');
1365
				$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
0 ignored issues
show
It seems like @file($modSettings['them...user_language . '.php') can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1365
				$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
Loading history...
1366
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1367
1368
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1369
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1370
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1371
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1372
				else
1373
				{
1374
					// Set this as the new language.
1375
					$upcontext['language'] = $user_language;
1376
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1377
1378
					// Include the file.
1379
					load_lang_file();
1380
				}
1381
			}
1382
1383
			// If we're resuming set the step and substep to be correct.
1384
			if (isset($_POST['cont']))
1385
			{
1386
				$upcontext['current_step'] = $upcontext['user']['step'];
1387
				$_GET['substep'] = $upcontext['user']['substep'];
1388
			}
1389
1390
			return true;
1391
		}
1392
	}
1393
1394
	return false;
1395
}
1396
1397
// Step 1: Do the maintenance and backup.
1398
function UpgradeOptions()
1399
{
1400
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1401
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1402
	global $auth_secret;
1403
1404
	$upcontext['sub_template'] = 'upgrade_options';
1405
	$upcontext['page_title'] = $txt['upgrade_options'];
1406
1407
	db_extend('packages');
1408
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1409
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1410
1411
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1412
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1413
1414
	$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', '<');
1415
1416
	unset($member_columns);
1417
1418
	// If we've not submitted then we're done.
1419
	if (empty($_POST['upcont']))
1420
		return false;
1421
1422
	// We cannot execute this step in strict mode - strict mode data fixes are not applied yet
1423
	setSqlMode(false);
1424
1425
	// Firstly, if they're enabling SM stat collection just do it.
1426
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1427
	{
1428
		$upcontext['allow_sm_stats'] = true;
1429
1430
		// Don't register if we still have a key.
1431
		if (empty($modSettings['sm_stats_key']))
1432
		{
1433
			// Attempt to register the site etc.
1434
			$fp = @fsockopen('www.simplemachines.org', 443, $errno, $errstr);
1435
			if (!$fp)
0 ignored issues
show
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1436
				$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1437
			if ($fp)
0 ignored issues
show
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1438
			{
1439
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1440
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1441
				$out .= 'Connection: Close' . "\r\n\r\n";
1442
				fwrite($fp, $out);
1443
1444
				$return_data = '';
1445
				while (!feof($fp))
1446
					$return_data .= fgets($fp, 128);
1447
1448
				fclose($fp);
1449
1450
				// Get the unique site ID.
1451
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1452
1453
				if (!empty($ID[1]))
1454
					$smcFunc['db_insert']('replace',
1455
						$db_prefix . 'settings',
1456
						array('variable' => 'string', 'value' => 'string'),
1457
						array(
1458
							array('sm_stats_key', $ID[1]),
1459
							array('enable_sm_stats', 1),
1460
						),
1461
						array('variable')
1462
					);
1463
			}
1464
		}
1465
		else
1466
		{
1467
			$smcFunc['db_insert']('replace',
1468
				$db_prefix . 'settings',
1469
				array('variable' => 'string', 'value' => 'string'),
1470
				array('enable_sm_stats', 1),
1471
				array('variable')
1472
			);
1473
		}
1474
	}
1475
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1476
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1477
		$smcFunc['db_query']('', '
1478
			DELETE FROM {db_prefix}settings
1479
			WHERE variable = {string:enable_sm_stats}',
1480
			array(
1481
				'enable_sm_stats' => 'enable_sm_stats',
1482
				'db_error_skip' => true,
1483
			)
1484
		);
1485
1486
	// Deleting old karma stuff?
1487
	$_SESSION['delete_karma'] = !empty($_POST['delete_karma']);
1488
1489
	// Emptying the error log?
1490
	$_SESSION['empty_error'] = !empty($_POST['empty_error']);
1491
1492
	// Reprocessing attachments?
1493
	$_SESSION['reprocess_attachments'] = !empty($_POST['reprocess_attachments']);
1494
1495
	$changes = array();
1496
1497
	// Add proxy settings.
1498
	if (!isset($GLOBALS['image_proxy_secret']) || $GLOBALS['image_proxy_secret'] == 'smfisawesome')
1499
		$changes['image_proxy_secret'] = substr(sha1(mt_rand()), 0, 20);
1500
	if (!isset($GLOBALS['image_proxy_maxsize']))
1501
		$changes['image_proxy_maxsize'] = 5190;
1502
	if (!isset($GLOBALS['image_proxy_enabled']))
1503
		$changes['image_proxy_enabled'] = false;
1504
1505
	// If $boardurl reflects https, set force_ssl
1506
	if (!function_exists('cache_put_data'))
1507
		require_once($sourcedir . '/Load.php');
1508
	if (stripos($boardurl, 'https://') !== false && !isset($modSettings['force_ssl']))
1509
		updateSettings(array('force_ssl' => '1'));
1510
1511
	// If we're overriding the language follow it through.
1512
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1513
		$changes['language'] = $upcontext['lang'];
1514
1515
	if (!empty($_POST['maint']))
1516
	{
1517
		$changes['maintenance'] = 2;
1518
		// Remember what it was...
1519
		$upcontext['user']['main'] = $maintenance;
1520
1521
		if (!empty($_POST['maintitle']))
1522
		{
1523
			$changes['mtitle'] = $_POST['maintitle'];
1524
			$changes['mmessage'] = $_POST['mainmessage'];
1525
		}
1526
		else
1527
		{
1528
			$changes['mtitle'] = $txt['mtitle'];
1529
			$changes['mmessage'] = $txt['mmessage'];
1530
		}
1531
	}
1532
1533
	if ($command_line)
1534
		echo ' * Updating Settings.php...';
1535
1536
	// Fix some old paths.
1537
	if (substr($boarddir, 0, 1) == '.')
1538
		$changes['boarddir'] = fixRelativePath($boarddir);
1539
1540
	if (substr($sourcedir, 0, 1) == '.')
1541
		$changes['sourcedir'] = fixRelativePath($sourcedir);
1542
1543
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1544
		$changes['cachedir'] = fixRelativePath($boarddir) . '/cache';
1545
1546
	// Migrate cache settings.
1547
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1548
	if (!isset($GLOBALS['cache_enable']))
1549
		$changes += array(
1550
			'cache_accelerator' => upgradeCacheSettings(),
0 ignored issues
show
The function upgradeCacheSettings was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1550
			'cache_accelerator' => /** @scrutinizer ignore-call */ upgradeCacheSettings(),
Loading history...
1551
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1552
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? $modSettings['cache_memcached'] : '',
1553
		);
1554
1555
	// If they have a "host:port" setup for the host, split that into separate values
1556
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1557
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1558
	{
1559
		list ($db_server, $db_port) = explode(':', $db_server);
1560
1561
		$changes['db_server'] = $db_server;
1562
1563
		// Only set this if we're not using the default port
1564
		if ($db_port != ini_get('mysqli.default_port'))
1565
			$changes['db_port'] = (int) $db_port;
1566
	}
1567
1568
	// If db_port is set and is the same as the default, set it to 0.
1569
	if (!empty($db_port))
1570
	{
1571
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1572
			$changes['db_port'] = 0;
1573
1574
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1575
			$changes['db_port'] = 0;
1576
	}
1577
1578
	// Maybe we haven't had this option yet?
1579
	if (empty($packagesdir))
1580
		$changes['packagesdir'] = fixRelativePath($boarddir) . '/Packages';
1581
1582
	// Add support for $tasksdir var.
1583
	if (empty($tasksdir))
1584
		$changes['tasksdir'] = fixRelativePath($sourcedir) . '/tasks';
1585
1586
	// Make sure we fix the language as well.
1587
	if (stristr($language, '-utf8'))
1588
		$changes['language'] = str_ireplace('-utf8', '', $language);
1589
1590
	// @todo Maybe change the cookie name if going to 1.1, too?
1591
1592
	// Ensure this doesn't get lost in translation.
1593
	$changes['upgradeData'] = base64_encode(json_encode($upcontext['user']));
1594
1595
	// Update Settings.php with the new settings, and rebuild if they selected that option.
1596
	require_once($sourcedir . '/Subs.php');
1597
	require_once($sourcedir . '/Subs-Admin.php');
1598
	$res = updateSettingsFile($changes, false, !empty($_POST['migrateSettings']));
1599
1600
	if ($command_line && $res)
1601
		echo ' Successful.' . "\n";
1602
	elseif ($command_line && !$res)
1603
	{
1604
		echo ' FAILURE.' . "\n";
1605
		die;
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...
1606
	}
1607
1608
	// Are we doing debug?
1609
	if (isset($_POST['debug']))
1610
	{
1611
		$upcontext['upgrade_status']['debug'] = true;
1612
		$is_debug = true;
1613
	}
1614
1615
	// If we're not backing up then jump one.
1616
	if (empty($_POST['backup']))
1617
		$upcontext['current_step']++;
1618
1619
	// If we've got here then let's proceed to the next step!
1620
	return true;
1621
}
1622
1623
// Backup the database - why not...
1624
function BackupDatabase()
1625
{
1626
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1627
1628
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1629
	$upcontext['page_title'] = $txt['backup_database'];
1630
1631
	// Done it already - js wise?
1632
	if (!empty($_POST['backup_done']))
1633
		return true;
1634
1635
	// We cannot execute this step in strict mode - strict mode data fixes are not applied yet
1636
	setSqlMode(false);
1637
1638
	// Some useful stuff here.
1639
	db_extend();
1640
1641
	// Might need this as well
1642
	db_extend('packages');
1643
1644
	// Get all the table names.
1645
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1646
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1647
	$tables = $smcFunc['db_list_tables']($db, $filter);
1648
1649
	$table_names = array();
1650
	foreach ($tables as $table)
1651
		if (substr($table, 0, 7) !== 'backup_')
1652
			$table_names[] = $table;
1653
1654
	$upcontext['table_count'] = count($table_names);
1655
	$upcontext['cur_table_num'] = $_GET['substep'];
1656
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1657
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1658
	// For non-java auto submit...
1659
	$file_steps = $upcontext['table_count'];
1660
1661
	// What ones have we already done?
1662
	foreach ($table_names as $id => $table)
1663
		if ($id < $_GET['substep'])
1664
			$upcontext['previous_tables'][] = $table;
1665
1666
	if ($command_line)
1667
		echo 'Backing Up Tables.';
1668
1669
	// If we don't support javascript we backup here.
1670
	if (!$support_js || isset($_GET['xml']))
1671
	{
1672
		// Backup each table!
1673
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1674
		{
1675
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1676
			$upcontext['cur_table_num'] = $substep + 1;
1677
1678
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1679
1680
			// Do we need to pause?
1681
			nextSubstep($substep);
1682
1683
			backupTable($table_names[$substep]);
1684
1685
			// If this is XML to keep it nice for the user do one table at a time anyway!
1686
			if (isset($_GET['xml']))
1687
				return upgradeExit();
0 ignored issues
show
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1688
		}
1689
1690
		if ($command_line)
1691
		{
1692
			echo "\n" . ' Successful.\'' . "\n";
1693
			flush();
1694
		}
1695
		$upcontext['step_progress'] = 100;
1696
1697
		$_GET['substep'] = 0;
1698
		// Make sure we move on!
1699
		return true;
1700
	}
1701
1702
	// Either way next place to post will be database changes!
1703
	$_GET['substep'] = 0;
1704
	return false;
1705
}
1706
1707
// Backup one table...
1708
function backupTable($table)
1709
{
1710
	global $command_line, $db_prefix, $smcFunc;
1711
1712
	if ($command_line)
1713
	{
1714
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1715
		flush();
1716
	}
1717
1718
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1719
1720
	if ($command_line)
1721
		echo ' done.';
1722
}
1723
1724
// Step 2: Everything.
1725
function DatabaseChanges()
1726
{
1727
	global $db_prefix, $modSettings, $smcFunc, $txt;
1728
	global $upcontext, $support_js, $db_type, $boarddir;
1729
1730
	// Have we just completed this?
1731
	if (!empty($_POST['database_done']))
1732
		return true;
1733
1734
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1735
	$upcontext['page_title'] = $txt['database_changes'];
1736
1737
	$upcontext['delete_karma'] = !empty($_SESSION['delete_karma']);
1738
	$upcontext['empty_error'] = !empty($_SESSION['empty_error']);
1739
	$upcontext['reprocess_attachments'] = !empty($_SESSION['reprocess_attachments']);
1740
1741
	// All possible files.
1742
	// Name, < version, insert_on_complete
1743
	// Last entry in array indicates whether to use sql_mode of STRICT or not.
1744
	$files = array(
1745
		array('upgrade_1-0.sql', '1.1', '1.1 RC0', false),
1746
		array('upgrade_1-1.sql', '2.0', '2.0 a', false),
1747
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0', false),
1748
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION, true),
1749
	);
1750
1751
	// How many files are there in total?
1752
	if (isset($_GET['filecount']))
1753
		$upcontext['file_count'] = (int) $_GET['filecount'];
1754
	else
1755
	{
1756
		$upcontext['file_count'] = 0;
1757
		foreach ($files as $file)
1758
		{
1759
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1760
				$upcontext['file_count']++;
1761
		}
1762
	}
1763
1764
	// Do each file!
1765
	$did_not_do = count($files) - $upcontext['file_count'];
1766
	$upcontext['step_progress'] = 0;
1767
	$upcontext['cur_file_num'] = 0;
1768
	foreach ($files as $file)
1769
	{
1770
		if ($did_not_do)
1771
			$did_not_do--;
1772
		else
1773
		{
1774
			$upcontext['cur_file_num']++;
1775
			$upcontext['cur_file_name'] = $file[0];
1776
			// Do we actually need to do this still?
1777
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1778
			{
1779
				// Use STRICT mode on more recent steps
1780
				setSqlMode($file[3]);
1781
1782
				// Reload modSettings to capture any adds/updates made along the way
1783
				$request = $smcFunc['db_query']('', '
1784
					SELECT variable, value
1785
					FROM {db_prefix}settings',
1786
					array(
1787
						'db_error_skip' => true,
1788
					)
1789
				);
1790
1791
				$modSettings = array();
1792
				while ($row = $smcFunc['db_fetch_assoc']($request))
1793
					$modSettings[$row['variable']] = $row['value'];
1794
1795
				$smcFunc['db_free_result']($request);
1796
1797
				// Some theme settings are in $modSettings
1798
				// Note we still might be doing yabbse (no smf ver)
1799
				if (isset($modSettings['smfVersion']))
1800
				{
1801
					$request = $smcFunc['db_query']('', '
1802
						SELECT variable, value
1803
						FROM {db_prefix}themes
1804
						WHERE id_theme = {int:id_theme}
1805
							AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
1806
						array(
1807
							'id_theme' => 1,
1808
							'theme_url' => 'theme_url',
1809
							'theme_dir' => 'theme_dir',
1810
							'images_url' => 'images_url',
1811
							'db_error_skip' => true,
1812
						)
1813
					);
1814
1815
					while ($row = $smcFunc['db_fetch_assoc']($request))
1816
						$modSettings[$row['variable']] = $row['value'];
1817
1818
					$smcFunc['db_free_result']($request);
1819
				}
1820
1821
				if (!isset($modSettings['theme_url']))
1822
				{
1823
					$modSettings['theme_dir'] = $boarddir . '/Themes/default';
1824
					$modSettings['theme_url'] = 'Themes/default';
1825
					$modSettings['images_url'] = 'Themes/default/images';
1826
				}
1827
1828
				// Now process the file...
1829
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1830
				if ($nextFile)
1831
				{
1832
					// Only update the version of this if complete.
1833
					$smcFunc['db_insert']('replace',
1834
						$db_prefix . 'settings',
1835
						array('variable' => 'string', 'value' => 'string'),
1836
						array('smfVersion', $file[2]),
1837
						array('variable')
1838
					);
1839
1840
					$modSettings['smfVersion'] = $file[2];
1841
				}
1842
1843
				// If this is XML we only do this stuff once.
1844
				if (isset($_GET['xml']))
1845
				{
1846
					// Flag to move on to the next.
1847
					$upcontext['completed_step'] = true;
1848
					// Did we complete the whole file?
1849
					if ($nextFile)
1850
						$upcontext['current_debug_item_num'] = -1;
1851
					return upgradeExit();
0 ignored issues
show
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1852
				}
1853
				elseif ($support_js)
1854
					break;
1855
			}
1856
			// Set the progress bar to be right as if we had - even if we hadn't...
1857
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1858
		}
1859
	}
1860
1861
	$_GET['substep'] = 0;
1862
	// So the template knows we're done.
1863
	if (!$support_js)
1864
	{
1865
		$upcontext['changes_complete'] = true;
1866
1867
		return true;
1868
	}
1869
	return false;
1870
}
1871
1872
// Different versions of the files use different sql_modes
1873
function setSqlMode($strict = true)
1874
{
1875
	global $db_type, $db_connection;
1876
1877
	if ($db_type != 'mysql')
1878
		return;
1879
1880
	if ($strict)
1881
		$mode = 'ONLY_FULL_GROUP_BY,STRICT_TRANS_TABLES,NO_ZERO_IN_DATE,NO_ZERO_DATE,ERROR_FOR_DIVISION_BY_ZERO,NO_ENGINE_SUBSTITUTION,PIPES_AS_CONCAT';
1882
	else
1883
		$mode = '';
1884
1885
	mysqli_query($db_connection, 'SET SESSION sql_mode = \'' . $mode . '\'');
1886
1887
	return;
1888
}
1889
1890
// Delete the damn thing!
1891
function DeleteUpgrade()
1892
{
1893
	global $command_line, $language, $upcontext, $sourcedir;
1894
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1895
1896
	// Now it's nice to have some of the basic SMF source files.
1897
	if (!isset($_GET['ssi']) && !$command_line)
1898
		redirectLocation('&ssi=1');
1899
1900
	$upcontext['sub_template'] = 'upgrade_complete';
1901
	$upcontext['page_title'] = $txt['upgrade_complete'];
1902
1903
	$endl = $command_line ? "\n" : '<br>' . "\n";
1904
1905
	$changes = array(
1906
		'language' => (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language),
1907
		'db_error_send' => true,
1908
		'upgradeData' => null,
1909
	);
1910
1911
	// Are we in maintenance mode?
1912
	if (isset($upcontext['user']['main']))
1913
	{
1914
		if ($command_line)
1915
			echo ' * ';
1916
		$upcontext['removed_maintenance'] = true;
1917
		$changes['maintenance'] = $upcontext['user']['main'];
1918
	}
1919
	// Otherwise if somehow we are in 2 let's go to 1.
1920
	elseif (!empty($maintenance) && $maintenance == 2)
1921
		$changes['maintenance'] = 1;
1922
1923
	// Wipe this out...
1924
	$upcontext['user'] = array();
1925
1926
	require_once($sourcedir . '/Subs.php');
1927
	require_once($sourcedir . '/Subs-Admin.php');
1928
	updateSettingsFile($changes);
1929
1930
	// Clean any old cache files away.
1931
	upgrade_clean_cache();
0 ignored issues
show
The function upgrade_clean_cache was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

1931
	/** @scrutinizer ignore-call */ 
1932
 upgrade_clean_cache();
Loading history...
1932
1933
	// Can we delete the file?
1934
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1935
1936
	// Now is the perfect time to fetch the SM files.
1937
	if ($command_line)
1938
		cli_scheduled_fetchSMfiles();
1939
	else
1940
	{
1941
		require_once($sourcedir . '/ScheduledTasks.php');
1942
		scheduled_fetchSMfiles(); // Now go get those files!
1943
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1944
		if (httpsOn())
1945
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1946
	}
1947
1948
	// Log what we've done.
1949
	if (empty($user_info['id']))
1950
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1951
1952
	// Log the action manually, so CLI still works.
1953
	$smcFunc['db_insert']('',
1954
		'{db_prefix}log_actions',
1955
		array(
1956
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1957
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1958
		),
1959
		array(
1960
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1961
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1962
		),
1963
		array('id_action')
1964
	);
1965
	$user_info['id'] = 0;
1966
1967
	if ($command_line)
1968
	{
1969
		echo $endl;
1970
		echo 'Upgrade Complete!', $endl;
1971
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1972
		exit;
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...
1973
	}
1974
1975
	// Make sure it says we're done.
1976
	$upcontext['overall_percent'] = 100;
1977
	if (isset($upcontext['step_progress']))
1978
		unset($upcontext['step_progress']);
1979
1980
	$_GET['substep'] = 0;
1981
	return false;
1982
}
1983
1984
// Just like the built in one, but setup for CLI to not use themes.
1985
function cli_scheduled_fetchSMfiles()
1986
{
1987
	global $sourcedir, $language, $modSettings, $smcFunc;
1988
1989
	if (empty($modSettings['time_format']))
1990
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1991
1992
	// What files do we want to get
1993
	$request = $smcFunc['db_query']('', '
1994
		SELECT id_file, filename, path, parameters
1995
		FROM {db_prefix}admin_info_files',
1996
		array(
1997
		)
1998
	);
1999
2000
	$js_files = array();
2001
	while ($row = $smcFunc['db_fetch_assoc']($request))
2002
	{
2003
		$js_files[$row['id_file']] = array(
2004
			'filename' => $row['filename'],
2005
			'path' => $row['path'],
2006
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
2007
		);
2008
	}
2009
	$smcFunc['db_free_result']($request);
2010
2011
	// We're gonna need fetch_web_data() to pull this off.
2012
	require_once($sourcedir . '/Subs.php');
2013
2014
	foreach ($js_files as $ID_FILE => $file)
2015
	{
2016
		// Create the url
2017
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
2018
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
2019
2020
		// Get the file
2021
		$file_data = fetch_web_data($url);
2022
2023
		// If we got an error - give up - the site might be down.
2024
		if ($file_data === false)
2025
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
0 ignored issues
show
The function throw_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2025
			return /** @scrutinizer ignore-call */ throw_error(sprintf('Could not retrieve the file %1$s.', $url));
Loading history...
2026
2027
		// Save the file to the database.
2028
		$smcFunc['db_query']('substring', '
2029
			UPDATE {db_prefix}admin_info_files
2030
			SET data = SUBSTRING({string:file_data}, 1, 65534)
2031
			WHERE id_file = {int:id_file}',
2032
			array(
2033
				'id_file' => $ID_FILE,
2034
				'file_data' => $file_data,
2035
			)
2036
		);
2037
	}
2038
	return true;
2039
}
2040
2041
function convertSettingsToTheme()
2042
{
2043
	global $db_prefix, $modSettings, $smcFunc;
2044
2045
	$values = array(
2046
		'show_latest_member' => @$GLOBALS['showlatestmember'],
2047
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
2048
		'show_modify' => @$GLOBALS['showmodify'],
2049
		'show_user_images' => @$GLOBALS['showuserpic'],
2050
		'show_blurb' => @$GLOBALS['showusertext'],
2051
		'show_gender' => @$GLOBALS['showgenderimage'],
2052
		'show_newsfader' => @$GLOBALS['shownewsfader'],
2053
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
2054
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
2055
		'linktree_link' => @$GLOBALS['curposlinks'],
2056
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
2057
		'show_mark_read' => @$GLOBALS['showmarkread'],
2058
		'show_board_desc' => @$GLOBALS['ShowBDescrip'],
2059
		'newsfader_time' => @$GLOBALS['fadertime'],
2060
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
2061
		'enable_news' => @$GLOBALS['enable_news'],
2062
		'linktree_inline' => @$modSettings['enableInlineLinks'],
2063
		'return_to_post' => @$modSettings['returnToPost'],
2064
	);
2065
2066
	$themeData = array();
2067
	foreach ($values as $variable => $value)
2068
	{
2069
		if (!isset($value) || $value === null)
2070
			$value = 0;
2071
2072
		$themeData[] = array(0, 1, $variable, $value);
2073
	}
2074
	if (!empty($themeData))
2075
	{
2076
		$smcFunc['db_insert']('ignore',
2077
			$db_prefix . 'themes',
2078
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
2079
			$themeData,
2080
			array('id_member', 'id_theme', 'variable')
2081
		);
2082
	}
2083
}
2084
2085
// This function only works with MySQL but that's fine as it is only used for v1.0.
2086
function convertSettingstoOptions()
2087
{
2088
	global $modSettings, $smcFunc;
2089
2090
	// Format: new_setting -> old_setting_name.
2091
	$values = array(
2092
		'calendar_start_day' => 'cal_startmonday',
2093
		'view_newest_first' => 'viewNewestFirst',
2094
		'view_newest_pm_first' => 'viewNewestFirst',
2095
	);
2096
2097
	foreach ($values as $variable => $value)
2098
	{
2099
		if (empty($modSettings[$value[0]]))
2100
			continue;
2101
2102
		$smcFunc['db_query']('', '
2103
			INSERT IGNORE INTO {db_prefix}themes
2104
				(id_member, id_theme, variable, value)
2105
			SELECT id_member, 1, {string:variable}, {string:value}
2106
			FROM {db_prefix}members',
2107
			array(
2108
				'variable' => $variable,
2109
				'value' => $modSettings[$value[0]],
2110
				'db_error_skip' => true,
2111
			)
2112
		);
2113
2114
		$smcFunc['db_query']('', '
2115
			INSERT IGNORE INTO {db_prefix}themes
2116
				(id_member, id_theme, variable, value)
2117
			VALUES (-1, 1, {string:variable}, {string:value})',
2118
			array(
2119
				'variable' => $variable,
2120
				'value' => $modSettings[$value[0]],
2121
				'db_error_skip' => true,
2122
			)
2123
		);
2124
	}
2125
}
2126
2127
function php_version_check()
2128
{
2129
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
2130
}
2131
2132
function db_version_check()
2133
{
2134
	global $db_type, $databases;
2135
2136
	$curver = $databases[$db_type]['version_check']();
2137
	$curver = preg_replace('~\-.+?$~', '', $curver);
2138
2139
	return version_compare($databases[$db_type]['version'], $curver, '<=');
2140
}
2141
2142
function fixRelativePath($path)
2143
{
2144
	global $install_path;
2145
2146
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
2147
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
2148
}
2149
2150
function parse_sql($filename)
2151
{
2152
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
2153
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set, $smcFunc;
2154
2155
/*
2156
	Failure allowed on:
2157
		- INSERT INTO but not INSERT IGNORE INTO.
2158
		- UPDATE IGNORE but not UPDATE.
2159
		- ALTER TABLE and ALTER IGNORE TABLE.
2160
		- DROP TABLE.
2161
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
2162
2163
	If a comment...
2164
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
2165
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
2166
		- is only ---#, it is "done." and then a break - only shown in debug.
2167
		- begins with ---{ it is a code block terminating at ---}.
2168
2169
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
2170
2171
	Replaces the following variables:
2172
		- {$boarddir}
2173
		- {$boardurl}
2174
		- {$db_prefix}
2175
		- {$db_collation}
2176
*/
2177
2178
	// May want to use extended functionality.
2179
	db_extend();
2180
	db_extend('packages');
2181
2182
	// Our custom error handler - does nothing but does stop public errors from XML!
2183
	// Note that php error suppression - @ - used heavily in the upgrader, calls the error handler
2184
	// but error_reporting() will return 0 as it does so (pre php8).
2185
	// Note error handling in php8+ no longer fails silently on many errors, but error_reporting()
2186
	// will return 4437 (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE)
2187
	// as it does so.
2188
	set_error_handler(
2189
		function($errno, $errstr, $errfile, $errline) use ($support_js)
2190
		{
2191
			if ($support_js)
2192
				return true;
2193
			elseif ((error_reporting() != 0) && (error_reporting() != (E_ERROR | E_CORE_ERROR | E_COMPILE_ERROR | E_USER_ERROR | E_RECOVERABLE_ERROR | E_PARSE)))
2194
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
2195
		}
2196
	);
2197
2198
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
2199
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
2200
	if ($db_type == 'mysql')
2201
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
2202
	else
2203
		$db_collation = '';
2204
2205
	$endl = $command_line ? "\n" : '<br>' . "\n";
2206
2207
	$lines = file($filename);
2208
2209
	$current_type = 'sql';
2210
	$current_data = '';
2211
	$substep = 0;
2212
	$last_step = '';
2213
2214
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
2215
	$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
2216
2217
	// Count the total number of steps within this file - for progress.
2218
	$file_steps = substr_count(implode('', $lines), '---#');
2219
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
2220
	$upcontext['debug_items'] = $file_steps;
2221
	$upcontext['current_item_num'] = 0;
2222
	$upcontext['current_item_name'] = '';
2223
	$upcontext['current_debug_item_num'] = 0;
2224
	$upcontext['current_debug_item_name'] = '';
2225
	// This array keeps a record of what we've done in case java is dead...
2226
	$upcontext['actioned_items'] = array();
2227
2228
	$done_something = false;
2229
2230
	foreach ($lines as $line_number => $line)
2231
	{
2232
		$do_current = $substep >= $_GET['substep'];
2233
2234
		// Get rid of any comments in the beginning of the line...
2235
		if (substr(trim($line), 0, 2) === '/*')
2236
			$line = preg_replace('~/\*.+?\*/~', '', $line);
2237
2238
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
2239
		if ($is_debug && !$support_js && $command_line)
2240
			flush();
2241
2242
		if (trim($line) === '')
2243
			continue;
2244
2245
		if (trim(substr($line, 0, 3)) === '---')
2246
		{
2247
			$type = substr($line, 3, 1);
2248
2249
			// An error??
2250
			if (trim($current_data) != '' && $type !== '}')
2251
			{
2252
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
2253
				if ($command_line)
2254
					echo $upcontext['error_message'];
2255
			}
2256
2257
			if ($type == ' ')
2258
			{
2259
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
2260
				{
2261
					echo ' Successful.', $endl;
2262
					flush();
2263
				}
2264
2265
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
2266
				$upcontext['current_item_num']++;
2267
				$upcontext['current_item_name'] = $last_step;
2268
2269
				if ($do_current)
2270
				{
2271
					$upcontext['actioned_items'][] = $last_step;
2272
					if ($command_line)
2273
						echo ' * ';
2274
2275
					// Starting a new main step in our DB changes, so it's time to reset this.
2276
					$upcontext['skip_db_substeps'] = false;
2277
				}
2278
			}
2279
			elseif ($type == '#')
2280
			{
2281
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
2282
2283
				$upcontext['current_debug_item_num']++;
2284
				if (trim($line) != '---#')
2285
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
2286
2287
				// Have we already done something?
2288
				if (isset($_GET['xml']) && $done_something)
2289
				{
2290
					restore_error_handler();
2291
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
2292
				}
2293
2294
				if ($do_current)
2295
				{
2296
					if (trim($line) == '---#' && $command_line)
2297
						echo ' done.', $endl;
2298
					elseif ($command_line)
2299
						echo ' +++ ', rtrim(substr($line, 4));
2300
					elseif (trim($line) != '---#')
2301
					{
2302
						if ($is_debug)
2303
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
2304
					}
2305
				}
2306
2307
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
2308
				{
2309
					if ($command_line)
2310
						echo ' * ';
2311
					else
2312
						$upcontext['actioned_items'][] = $last_step;
2313
				}
2314
2315
				// Small step - only if we're actually doing stuff.
2316
				if ($do_current)
2317
					nextSubstep(++$substep);
2318
				else
2319
					$substep++;
2320
			}
2321
			elseif ($type == '{')
2322
				$current_type = 'code';
2323
			elseif ($type == '}')
2324
			{
2325
				$current_type = 'sql';
2326
2327
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2328
				{
2329
					$current_data = '';
2330
2331
					// Avoid confusion when skipping something we normally would have done
2332
					if ($do_current)
2333
						$done_something = true;
2334
2335
					continue;
2336
				}
2337
2338
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2339
				if (eval('global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false)
0 ignored issues
show
The use of eval() is discouraged.
Loading history...
2340
				{
2341
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2342
					if ($command_line)
2343
						echo $upcontext['error_message'];
2344
				}
2345
2346
				// Done with code!
2347
				$current_data = '';
2348
				$done_something = true;
2349
			}
2350
2351
			continue;
2352
		}
2353
2354
		$current_data .= $line;
2355
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2356
		{
2357
			if ((!$support_js || isset($_GET['xml'])))
2358
			{
2359
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2360
				{
2361
					$current_data = '';
2362
2363
					if ($do_current)
2364
						$done_something = true;
2365
2366
					continue;
2367
				}
2368
2369
				// {$sboarddir} is deprecated, but blah blah backward compatibility blah...
2370
				$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));
2371
2372
				upgrade_query($current_data);
2373
2374
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2375
				/*
2376
				$result = $smcFunc['db_query']('', $current_data, false, false);
2377
				// Went wrong?
2378
				if (!$result)
2379
				{
2380
					// Bit of a bodge - do we want the error?
2381
					if (!empty($upcontext['return_error']))
2382
					{
2383
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2384
						return false;
2385
					}
2386
				}*/
2387
				$done_something = true;
2388
			}
2389
			$current_data = '';
2390
		}
2391
		// If this is xml based and we're just getting the item name then that's grand.
2392
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2393
		{
2394
			restore_error_handler();
2395
			return false;
2396
		}
2397
2398
		// Clean up by cleaning any step info.
2399
		$step_progress = array();
2400
		$custom_warning = '';
2401
	}
2402
2403
	// Put back the error handler.
2404
	restore_error_handler();
2405
2406
	if ($command_line)
2407
	{
2408
		echo ' Successful.' . "\n";
2409
		flush();
2410
	}
2411
2412
	$_GET['substep'] = 0;
2413
	return true;
2414
}
2415
2416
function upgrade_query($string, $unbuffered = false)
2417
{
2418
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2419
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2420
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2421
2422
	// Get the query result - working around some SMF specific security - just this once!
2423
	$modSettings['disableQueryCheck'] = true;
2424
	$db_unbuffered = $unbuffered;
2425
	$ignore_insert_error = false;
2426
2427
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2428
	$db_unbuffered = false;
2429
2430
	// Failure?!
2431
	if ($result !== false)
2432
		return $result;
2433
2434
	$db_error_message = $smcFunc['db_error']($db_connection);
2435
	// If MySQL we do something more clever.
2436
	if ($db_type == 'mysql')
2437
	{
2438
		$mysqli_errno = mysqli_errno($db_connection);
2439
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR', 'INSERT IGNO'));
2440
2441
		// Error numbers:
2442
		//    1016: Can't open file '....MYI'
2443
		//    1050: Table already exists.
2444
		//    1054: Unknown column name.
2445
		//    1060: Duplicate column name.
2446
		//    1061: Duplicate key name.
2447
		//    1062: Duplicate entry for unique key.
2448
		//    1068: Multiple primary keys.
2449
		//    1072: Key column '%s' doesn't exist in table.
2450
		//    1091: Can't drop key, doesn't exist.
2451
		//    1146: Table doesn't exist.
2452
		//    2013: Lost connection to server during query.
2453
2454
		if ($mysqli_errno == 1016)
2455
		{
2456
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2457
			{
2458
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2459
				$result = mysqli_query($db_connection, $string);
2460
				if ($result !== false)
2461
					return $result;
2462
			}
2463
		}
2464
		elseif ($mysqli_errno == 2013)
2465
		{
2466
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
0 ignored issues
show
The call to mysqli_connect() has too few arguments starting with database. ( Ignorable by Annotation )

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

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

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

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

Loading history...
2467
			mysqli_select_db($db_connection, $db_name);
2468
			if ($db_connection)
2469
			{
2470
				$result = mysqli_query($db_connection, $string);
2471
				if ($result !== false)
2472
					return $result;
2473
			}
2474
		}
2475
		// Duplicate column name... should be okay ;).
2476
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2477
			return false;
2478
		// Duplicate insert... make sure it's the proper type of query ;).
2479
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2480
			return false;
2481
		// Creating an index on a non-existent column.
2482
		elseif ($mysqli_errno == 1072)
2483
			return false;
2484
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2485
			return false;
2486
		// Testing for legacy tables or columns? Needed for 1.0 & 1.1 scripts.
2487
		elseif (in_array($mysqli_errno, array(1054, 1146)) && in_array(substr(trim($string), 0, 7), array('SELECT ', 'SHOW CO')))
2488
			return false;
2489
	}
2490
	// If a table already exists don't go potty.
2491
	else
2492
	{
2493
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2494
		{
2495
			if (strpos($db_error_message, 'exist') !== false)
2496
				return true;
2497
		}
2498
		elseif (strpos(trim($string), 'INSERT ') !== false)
2499
		{
2500
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2501
				return true;
2502
		}
2503
	}
2504
2505
	// Get the query string so we pass everything.
2506
	$query_string = '';
2507
	foreach ($_GET as $k => $v)
2508
		$query_string .= ';' . $k . '=' . $v;
2509
	if (strlen($query_string) != 0)
2510
		$query_string = '?' . substr($query_string, 1);
2511
2512
	if ($command_line)
2513
	{
2514
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2515
		die;
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...
2516
	}
2517
2518
	// Bit of a bodge - do we want the error?
2519
	if (!empty($upcontext['return_error']))
2520
	{
2521
		$upcontext['error_message'] = $db_error_message;
2522
		$upcontext['error_string'] = $string;
2523
		return false;
2524
	}
2525
2526
	// Otherwise we have to display this somewhere appropriate if possible.
2527
	$upcontext['forced_error_message'] = '
2528
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2529
2530
			<div style="margin: 2ex;">
2531
				' . $txt['upgrade_thisquery'] . '
2532
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2533
2534
				' . $txt['upgrade_causerror'] . '
2535
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2536
			</div>
2537
2538
			<form action="' . $upgradeurl . $query_string . '" method="post">
2539
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2540
			</form>
2541
		</div>';
2542
2543
	upgradeExit();
2544
}
2545
2546
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2547
function protected_alter($change, $substep, $is_test = false)
2548
{
2549
	global $db_prefix, $smcFunc;
2550
2551
	db_extend('packages');
2552
2553
	// Firstly, check whether the current index/column exists.
2554
	$found = false;
2555
	if ($change['type'] === 'column')
2556
	{
2557
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2558
		foreach ($columns as $column)
2559
		{
2560
			// Found it?
2561
			if ($column['name'] === $change['name'])
2562
			{
2563
				$found |= true;
2564
				// Do some checks on the data if we have it set.
2565
				if (isset($change['col_type']))
2566
					$found &= $change['col_type'] === $column['type'];
2567
				if (isset($change['null_allowed']))
2568
					$found &= $column['null'] == $change['null_allowed'];
2569
				if (isset($change['default']))
2570
					$found &= $change['default'] === $column['default'];
2571
			}
2572
		}
2573
	}
2574
	elseif ($change['type'] === 'index')
2575
	{
2576
		$request = upgrade_query('
2577
			SHOW INDEX
2578
			FROM ' . $db_prefix . $change['table']);
2579
		if ($request !== false)
2580
		{
2581
			$cur_index = array();
2582
2583
			while ($row = $smcFunc['db_fetch_assoc']($request))
2584
				if ($row['Key_name'] === $change['name'])
2585
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2586
2587
			ksort($cur_index, SORT_NUMERIC);
2588
			$found = array_values($cur_index) === $change['target_columns'];
2589
2590
			$smcFunc['db_free_result']($request);
2591
		}
2592
	}
2593
2594
	// If we're trying to add and it's added, we're done.
2595
	if ($found && in_array($change['method'], array('add', 'change')))
0 ignored issues
show
Bug Best Practice introduced by
The expression $found of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

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

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2599
		return true;
2600
	// Otherwise is it just a test?
2601
	elseif ($is_test)
2602
		return false;
2603
2604
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2605
	$running = false;
2606
	$found = false;
2607
	while (1 == 1)
2608
	{
2609
		$request = upgrade_query('
2610
			SHOW FULL PROCESSLIST');
2611
		while ($row = $smcFunc['db_fetch_assoc']($request))
2612
		{
2613
			if ($row['Info'] !== null && (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false))
2614
				$found = true;
2615
		}
2616
2617
		// Can't find it? Then we need to run it fools!
2618
		if (!$found && !$running)
2619
		{
2620
			$smcFunc['db_free_result']($request);
2621
2622
			$success = upgrade_query('
2623
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2624
				' . $change['text'], true) !== false;
2625
2626
			if (!$success)
2627
				return false;
2628
2629
			// Return
2630
			$running = true;
2631
		}
2632
		// What if we've not found it, but we'd ran it already? Must of completed.
2633
		elseif (!$found)
2634
		{
2635
			$smcFunc['db_free_result']($request);
2636
			return true;
2637
		}
2638
2639
		// Pause execution for a sec or three.
2640
		sleep(3);
2641
2642
		// Can never be too well protected.
2643
		nextSubstep($substep);
2644
	}
2645
2646
	// Protect it.
2647
	nextSubstep($substep);
2648
}
2649
2650
/**
2651
 * Alter a text column definition preserving its character set.
2652
 *
2653
 * @param array $change
2654
 * @param int $substep
2655
 */
2656
function textfield_alter($change, $substep)
2657
{
2658
	global $db_prefix, $smcFunc;
2659
2660
	$request = $smcFunc['db_query']('', '
2661
		SHOW FULL COLUMNS
2662
		FROM {db_prefix}' . $change['table'] . '
2663
		LIKE {string:column}',
2664
		array(
2665
			'column' => $change['column'],
2666
			'db_error_skip' => true,
2667
		)
2668
	);
2669
	if ($smcFunc['db_num_rows']($request) === 0)
2670
		die('Unable to find column ' . $change['column'] . ' inside table ' . $db_prefix . $change['table']);
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...
2671
	$table_row = $smcFunc['db_fetch_assoc']($request);
2672
	$smcFunc['db_free_result']($request);
2673
2674
	// If something of the current column definition is different, fix it.
2675
	$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']);
2676
2677
	// Columns that previously allowed null, need to be converted first.
2678
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2679
2680
	// Get the character set that goes with the collation of the column.
2681
	if ($column_fix && !empty($table_row['Collation']))
2682
	{
2683
		$request = $smcFunc['db_query']('', '
2684
			SHOW COLLATION
2685
			LIKE {string:collation}',
2686
			array(
2687
				'collation' => $table_row['Collation'],
2688
				'db_error_skip' => true,
2689
			)
2690
		);
2691
		// No results? Just forget it all together.
2692
		if ($smcFunc['db_num_rows']($request) === 0)
2693
			unset($table_row['Collation']);
2694
		else
2695
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2696
		$smcFunc['db_free_result']($request);
2697
	}
2698
2699
	if ($column_fix)
2700
	{
2701
		// Make sure there are no NULL's left.
2702
		if ($null_fix)
2703
			$smcFunc['db_query']('', '
2704
				UPDATE {db_prefix}' . $change['table'] . '
2705
				SET ' . $change['column'] . ' = {string:default}
2706
				WHERE ' . $change['column'] . ' IS NULL',
2707
				array(
2708
					'default' => isset($change['default']) ? $change['default'] : '',
2709
					'db_error_skip' => true,
2710
				)
2711
			);
2712
2713
		// Do the actual alteration.
2714
		$smcFunc['db_query']('', '
2715
			ALTER TABLE {db_prefix}' . $change['table'] . '
2716
			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}' : ''),
2717
			array(
2718
				'default' => isset($change['default']) ? $change['default'] : '',
2719
				'db_error_skip' => true,
2720
			)
2721
		);
2722
	}
2723
	nextSubstep($substep);
2724
}
2725
2726
// The next substep.
2727
function nextSubstep($substep)
2728
{
2729
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2730
	global $step_progress, $is_debug, $upcontext;
2731
2732
	if ($_GET['substep'] < $substep)
2733
		$_GET['substep'] = $substep;
2734
2735
	if ($command_line)
2736
	{
2737
		if (time() - $start_time > 1 && empty($is_debug))
2738
		{
2739
			echo '.';
2740
			$start_time = time();
2741
		}
2742
		return;
2743
	}
2744
2745
	@set_time_limit(300);
2746
	if (function_exists('apache_reset_timeout'))
2747
		@apache_reset_timeout();
2748
2749
	if (time() - $start_time <= $timeLimitThreshold)
2750
		return;
2751
2752
	// Do we have some custom step progress stuff?
2753
	if (!empty($step_progress))
2754
	{
2755
		$upcontext['substep_progress'] = 0;
2756
		$upcontext['substep_progress_name'] = $step_progress['name'];
2757
		if ($step_progress['current'] > $step_progress['total'])
2758
			$upcontext['substep_progress'] = 99.9;
2759
		else
2760
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2761
2762
		// Make it nicely rounded.
2763
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2764
	}
2765
2766
	// If this is XML we just exit right away!
2767
	if (isset($_GET['xml']))
2768
		return upgradeExit();
0 ignored issues
show
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
2769
2770
	// We're going to pause after this!
2771
	$upcontext['pause'] = true;
2772
2773
	$upcontext['query_string'] = '';
2774
	foreach ($_GET as $k => $v)
2775
	{
2776
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2777
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2778
	}
2779
2780
	// Custom warning?
2781
	if (!empty($custom_warning))
2782
		$upcontext['custom_warning'] = $custom_warning;
2783
2784
	upgradeExit();
2785
}
2786
2787
function cmdStep0()
2788
{
2789
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2790
	global $is_debug, $boardurl, $txt;
2791
	$start_time = time();
2792
2793
	while (ob_get_level() > 0)
2794
		ob_end_clean();
2795
	ob_implicit_flush(1);
2796
2797
	if (!isset($_SERVER['argv']))
2798
		$_SERVER['argv'] = array();
2799
	$_GET['maint'] = 1;
2800
2801
	foreach ($_SERVER['argv'] as $i => $arg)
2802
	{
2803
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2804
			$upcontext['lang'] = $match[1];
2805
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2806
			continue;
2807
		elseif ($arg == '--no-maintenance')
2808
			$_GET['maint'] = 0;
2809
		elseif ($arg == '--debug')
2810
			$is_debug = true;
2811
		elseif ($arg == '--backup')
2812
			$_POST['backup'] = 1;
2813
		elseif ($arg == '--rebuild-settings')
2814
			$_POST['migrateSettings'] = 1;
2815
		elseif ($arg == '--allow-stats')
2816
			$_POST['stats'] = 1;
2817
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2818
			$_GET['conv'] = 1;
2819
		elseif ($i != 0)
2820
		{
2821
			echo 'SMF Command-line Upgrader
2822
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2823
2824
	--path=/path/to/SMF     Specify custom SMF root directory.
2825
	--language=LANG         Reset the forum\'s language to LANG.
2826
	--no-maintenance        Don\'t put the forum into maintenance mode.
2827
	--debug                 Output debugging information.
2828
	--backup                Create backups of tables with "backup_" prefix.
2829
	--allow-stats           Allow Simple Machines stat collection
2830
	--rebuild-settings      Rebuild the Settings.php file';
2831
			echo "\n";
2832
			exit;
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...
2833
		}
2834
	}
2835
2836
	if (!php_version_check())
2837
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
0 ignored issues
show
The function print_error was not found. Maybe you did not declare it correctly or list all dependencies? ( Ignorable by Annotation )

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

2837
		/** @scrutinizer ignore-call */ 
2838
  print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
Loading history...
2838
	if (!db_version_check())
2839
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2840
2841
	// Do some checks to make sure they have proper privileges
2842
	db_extend('packages');
2843
2844
	// CREATE
2845
	$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');
2846
2847
	// ALTER
2848
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2849
2850
	// DROP
2851
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2852
2853
	// Sorry... we need CREATE, ALTER and DROP
2854
	if (!$create || !$alter || !$drop)
2855
		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);
2856
2857
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2858
		&& @file_exists($sourcedir . '/QueryString.php')
2859
		&& @file_exists($sourcedir . '/ManageBoards.php');
2860
	if (!$check && !isset($modSettings['smfVersion']))
2861
		print_error('Error: Some files are missing or out-of-date.', true);
2862
2863
	// Do a quick version spot check.
2864
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
0 ignored issues
show
It seems like @file($boarddir . '/index.php') can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

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

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

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

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

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

}

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

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

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

Loading history...
3430
		}
3431
3432
		$prev_charset = empty($translation_tables[$upcontext['charset_detected']]) ? $charsets[$upcontext['charset_detected']] : $translation_tables[$upcontext['charset_detected']];
3433
3434
		$smcFunc['db_insert']('replace',
3435
			'{db_prefix}settings',
3436
			array('variable' => 'string', 'value' => 'string'),
3437
			array(array('global_character_set', 'UTF-8'), array('previousCharacterSet', $prev_charset)),
3438
			array('variable')
3439
		);
3440
3441
		// Store it in Settings.php too because it's needed before db connection.
3442
		// Hopefully this works...
3443
		require_once($sourcedir . '/Subs.php');
3444
		require_once($sourcedir . '/Subs-Admin.php');
3445
		updateSettingsFile(array('db_character_set' => 'utf8'));
3446
3447
		// The conversion might have messed up some serialized strings. Fix them!
3448
		$request = $smcFunc['db_query']('', '
3449
			SELECT id_action, extra
3450
			FROM {db_prefix}log_actions
3451
			WHERE action IN ({string:remove}, {string:delete})',
3452
			array(
3453
				'remove' => 'remove',
3454
				'delete' => 'delete',
3455
			)
3456
		);
3457
		while ($row = $smcFunc['db_fetch_assoc']($request))
3458
		{
3459
			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)
3460
				$smcFunc['db_query']('', '
3461
					UPDATE {db_prefix}log_actions
3462
					SET extra = {string:extra}
3463
					WHERE id_action = {int:current_action}',
3464
					array(
3465
						'current_action' => $row['id_action'],
3466
						'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4],
3467
					)
3468
				);
3469
		}
3470
		$smcFunc['db_free_result']($request);
3471
3472
		if ($upcontext['dropping_index'] && $command_line)
3473
		{
3474
			echo "\n" . '', $txt['upgrade_fulltext_error'], '';
3475
			flush();
3476
		}
3477
	}
3478
3479
	// Make sure we move on!
3480
	if ($command_line)
3481
		return DeleteUpgrade();
3482
3483
	$_GET['substep'] = 0;
3484
	return false;
3485
}
3486
3487
/**
3488
 * Wrapper for unserialize that attempts to repair corrupted serialized data strings
3489
 *
3490
 * @param string $string Serialized data that may or may not have been corrupted
3491
 * @return string|bool The unserialized data, or false if the repair failed
3492
 */
3493
function upgrade_unserialize($string)
3494
{
3495
	if (!is_string($string))
0 ignored issues
show
The condition is_string($string) is always true.
Loading history...
3496
	{
3497
		$data = false;
3498
	}
3499
	// Might be JSON already.
3500
	elseif (strpos($string, '{') === 0)
3501
	{
3502
		$data = @json_decode($string, true);
3503
3504
		if (is_null($data))
3505
			$data = false;
3506
	}
3507
	elseif (in_array(substr($string, 0, 2), array('b:', 'i:', 'd:', 's:', 'a:', 'N;')))
3508
	{
3509
		$data = @safe_unserialize($string);
3510
3511
		// The serialized data is broken.
3512
		if ($data === false)
3513
		{
3514
			// This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8)
3515
			$new_string = preg_replace_callback(
0 ignored issues
show
The assignment to $new_string is dead and can be removed.
Loading history...
3516
				'~\bs:(\d+):"(.*?)";(?=$|[bidsaO]:|[{}}]|N;)~s',
3517
				function ($matches)
3518
				{
3519
					return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';
3520
				},
3521
				$string
3522
			);
3523
3524
			// @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc.
3525
3526
			// Did it work?
3527
			$data = @safe_unserialize($string);
3528
		}
3529
	}
3530
	// Just a plain string, then.
3531
	else
3532
		$data = false;
3533
3534
	return $data;
3535
}
3536
3537
function serialize_to_json()
3538
{
3539
	global $command_line, $smcFunc, $modSettings, $sourcedir, $upcontext, $support_js, $txt;
3540
3541
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json';
3542
	// First thing's first - did we already do this?
3543
	if (!empty($modSettings['json_done']))
3544
	{
3545
		if ($command_line)
3546
			return ConvertUtf8();
3547
		else
3548
			return true;
3549
	}
3550
3551
	// Needed when writing settings
3552
	if (!function_exists('cache_put_data'))
3553
		require_once($sourcedir . '/Load.php');
3554
3555
	// Done it already - js wise?
3556
	if (!empty($_POST['json_done']))
3557
	{
3558
		updateSettings(array('json_done' => true));
3559
		return true;
3560
	}
3561
3562
	// List of tables affected by this function
3563
	// name => array('key', col1[,col2|true[,col3]])
3564
	// If 3rd item in array is true, it indicates that col1 could be empty...
3565
	$tables = array(
3566
		'background_tasks' => array('id_task', 'task_data'),
3567
		'log_actions' => array('id_action', 'extra'),
3568
		'log_online' => array('session', 'url'),
3569
		'log_packages' => array('id_install', 'db_changes', 'failed_steps', 'credits'),
3570
		'log_spider_hits' => array('id_hit', 'url'),
3571
		'log_subscribed' => array('id_sublog', 'pending_details'),
3572
		'pm_rules' => array('id_rule', 'criteria', 'actions'),
3573
		'qanda' => array('id_question', 'answers'),
3574
		'subscriptions' => array('id_subscribe', 'cost'),
3575
		'user_alerts' => array('id_alert', 'extra', true),
3576
		'user_drafts' => array('id_draft', 'to_list', true),
3577
		// These last two are a bit different - we'll handle those separately
3578
		'settings' => array(),
3579
		'themes' => array()
3580
	);
3581
3582
	// Set up some context stuff...
3583
	// Because we're not using numeric indices, we need this to figure out the current table name...
3584
	$keys = array_keys($tables);
3585
3586
	$upcontext['page_title'] = $txt['converting_json'];
3587
	$upcontext['table_count'] = count($keys);
3588
	$upcontext['cur_table_num'] = $_GET['substep'];
3589
	$upcontext['cur_table_name'] = isset($keys[$_GET['substep']]) ? $keys[$_GET['substep']] : $keys[0];
3590
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3591
3592
	foreach ($keys as $id => $table)
3593
		if ($id < $_GET['substep'])
3594
			$upcontext['previous_tables'][] = $table;
3595
3596
	if ($command_line)
3597
		echo 'Converting data from serialize() to json_encode().';
3598
3599
	if (!$support_js || isset($_GET['xml']))
3600
	{
3601
		// Fix the data in each table
3602
		for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++)
3603
		{
3604
			$upcontext['cur_table_name'] = isset($keys[$substep + 1]) ? $keys[$substep + 1] : $keys[$substep];
3605
			$upcontext['cur_table_num'] = $substep + 1;
3606
3607
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3608
3609
			// Do we need to pause?
3610
			nextSubstep($substep);
3611
3612
			// Initialize a few things...
3613
			$where = '';
3614
			$vars = array();
3615
			$table = $keys[$substep];
3616
			$info = $tables[$table];
3617
3618
			// Now the fun - build our queries and all that fun stuff
3619
			if ($table == 'settings')
3620
			{
3621
				// Now a few settings...
3622
				$serialized_settings = array(
3623
					'attachment_basedirectories',
3624
					'attachmentUploadDir',
3625
					'cal_today_birthday',
3626
					'cal_today_event',
3627
					'cal_today_holiday',
3628
					'displayFields',
3629
					'last_attachments_directory',
3630
					'memberlist_cache',
3631
					'search_custom_index_config',
3632
					'spider_name_cache'
3633
				);
3634
3635
				// Loop through and fix these...
3636
				$new_settings = array();
3637
				if ($command_line)
3638
					echo "\n" . 'Fixing some settings...';
3639
3640
				foreach ($serialized_settings as $var)
3641
				{
3642
					if (isset($modSettings[$var]))
3643
					{
3644
						// Attempt to unserialize the setting
3645
						$temp = upgrade_unserialize($modSettings[$var]);
3646
3647
						if (!$temp && $command_line)
3648
							echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping.";
3649
						elseif ($temp !== false)
3650
							$new_settings[$var] = json_encode($temp);
3651
					}
3652
				}
3653
3654
				// Update everything at once
3655
				updateSettings($new_settings, true);
3656
3657
				if ($command_line)
3658
					echo ' done.';
3659
			}
3660
			elseif ($table == 'themes')
3661
			{
3662
				// Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point...
3663
				$query = $smcFunc['db_query']('', '
3664
					SELECT id_member, id_theme, value FROM {db_prefix}themes
3665
					WHERE variable = {string:admin_prefs}',
3666
					array(
3667
						'admin_prefs' => 'admin_preferences'
3668
					)
3669
				);
3670
3671
				if ($smcFunc['db_num_rows']($query) != 0)
3672
				{
3673
					while ($row = $smcFunc['db_fetch_assoc']($query))
3674
					{
3675
						$temp = upgrade_unserialize($row['value']);
3676
3677
						if ($command_line)
3678
						{
3679
							if ($temp === false)
3680
								echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.';
3681
							else
3682
								echo "\n" . 'Fixing admin preferences...';
3683
						}
3684
3685
						if ($temp !== false)
3686
						{
3687
							$row['value'] = json_encode($temp);
3688
3689
							// Even though we have all values from the table, UPDATE is still faster than REPLACE
3690
							$smcFunc['db_query']('', '
3691
								UPDATE {db_prefix}themes
3692
								SET value = {string:prefs}
3693
								WHERE id_theme = {int:theme}
3694
									AND id_member = {int:member}
3695
									AND variable = {string:admin_prefs}',
3696
								array(
3697
									'prefs' => $row['value'],
3698
									'theme' => $row['id_theme'],
3699
									'member' => $row['id_member'],
3700
									'admin_prefs' => 'admin_preferences'
3701
								)
3702
							);
3703
3704
							if ($command_line)
3705
								echo ' done.';
3706
						}
3707
					}
3708
3709
					$smcFunc['db_free_result']($query);
3710
				}
3711
			}
3712
			else
3713
			{
3714
				// First item is always the key...
3715
				$key = $info[0];
3716
				unset($info[0]);
3717
3718
				// Now we know what columns we have and such...
3719
				if (count($info) == 2 && $info[2] === true)
3720
				{
3721
					$col_select = $info[1];
3722
					$where = ' WHERE ' . $info[1] . ' != {empty}';
3723
				}
3724
				else
3725
				{
3726
					$col_select = implode(', ', $info);
3727
				}
3728
3729
				$query = $smcFunc['db_query']('', '
3730
					SELECT ' . $key . ', ' . $col_select . '
3731
					FROM {db_prefix}' . $table . $where,
3732
					array()
3733
				);
3734
3735
				if ($smcFunc['db_num_rows']($query) != 0)
3736
				{
3737
					if ($command_line)
3738
					{
3739
						echo "\n" . ' +++ Fixing the "' . $table . '" table...';
3740
						flush();
3741
					}
3742
3743
					while ($row = $smcFunc['db_fetch_assoc']($query))
3744
					{
3745
						$update = '';
3746
3747
						// We already know what our key is...
3748
						foreach ($info as $col)
3749
						{
3750
							if ($col !== true && $row[$col] != '')
3751
							{
3752
								$temp = upgrade_unserialize($row[$col]);
3753
3754
								// Oh well...
3755
								if ($temp === false)
3756
								{
3757
									$temp = array();
3758
3759
									if ($command_line)
3760
										echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n";
3761
								}
3762
3763
								$row[$col] = json_encode($temp);
3764
3765
								// Build our SET string and variables array
3766
								$update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}';
3767
								$vars[$col] = $row[$col];
3768
							}
3769
						}
3770
3771
						$vars[$key] = $row[$key];
3772
3773
						// In a few cases, we might have empty data, so don't try to update in those situations...
3774
						if (!empty($update))
3775
						{
3776
							$smcFunc['db_query']('', '
3777
								UPDATE {db_prefix}' . $table . '
3778
								SET ' . $update . '
3779
								WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}',
3780
								$vars
3781
							);
3782
						}
3783
					}
3784
3785
					if ($command_line)
3786
						echo ' done.';
3787
3788
					// Free up some memory...
3789
					$smcFunc['db_free_result']($query);
3790
				}
3791
			}
3792
			// If this is XML to keep it nice for the user do one table at a time anyway!
3793
			if (isset($_GET['xml']))
3794
				return upgradeExit();
0 ignored issues
show
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

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