Issues (1027)

other/upgrade.php (2 issues)

1
<?php
2
3
/**
4
 * Simple Machines Forum (SMF)
5
 *
6
 * @package SMF
7
 * @author Simple Machines http://www.simplemachines.org
8
 * @copyright 2019 Simple Machines and individual contributors
9
 * @license http://www.simplemachines.org/about/smf/license.php BSD
10
 *
11
 * @version 2.1 RC2
12
 */
13
14
// Version information...
15
define('SMF_VERSION', '2.1 RC2');
16
define('SMF_FULL_VERSION', 'SMF ' . SMF_VERSION);
17
define('SMF_SOFTWARE_YEAR', '2019');
18
define('SMF_LANG_VERSION', '2.1 RC2');
19
define('SMF_INSTALLING', 1);
20
define('JQUERY_VERSION', '3.4.1');
21
22
/**
23
 * The minimum required PHP version.
24
 *
25
 * @var string
26
 */
27
$GLOBALS['required_php_version'] = '5.4.0';
28
29
/**
30
 * A list of supported database systems.
31
 *
32
 * @var array
33
 */
34
$databases = array(
35
	'mysql' => array(
36
		'name' => 'MySQL',
37
		'version' => '5.0.22',
38
		'version_check' => 'global $db_connection; return min(mysqli_get_server_info($db_connection), mysqli_get_client_info());',
39
		'utf8_support' => true,
40
		'utf8_version' => '5.0.22',
41
		'utf8_version_check' => 'global $db_connection; return mysqli_get_server_info($db_connection);',
42
		'alter_support' => true,
43
	),
44
	'postgresql' => array(
45
		'name' => 'PostgreSQL',
46
		'version' => '9.4',
47
		'version_check' => '$version = pg_version(); return $version[\'client\'];',
48
		'always_has_db' => true,
49
	),
50
);
51
52
/**
53
 * The maximum time a single substep may take, in seconds.
54
 *
55
 * @var int
56
 */
57
$timeLimitThreshold = 3;
58
59
/**
60
 * The current path to the upgrade.php file.
61
 *
62
 * @var string
63
 */
64
$upgrade_path = dirname(__FILE__);
65
66
/**
67
 * The URL of the current page.
68
 *
69
 * @var string
70
 */
71
$upgradeurl = $_SERVER['PHP_SELF'];
72
73
/**
74
 * Flag to disable the required administrator login.
75
 *
76
 * @var bool
77
 */
78
$disable_security = false;
79
80
/**
81
 * The amount of seconds allowed between logins.
82
 * If the first user to login is inactive for this amount of seconds, a second login is allowed.
83
 *
84
 * @var int
85
 */
86
$upcontext['inactive_timeout'] = 10;
87
88
global $txt;
89
90
// All the steps in detail.
91
// Number,Name,Function,Progress Weight.
92
$upcontext['steps'] = array(
93
	0 => array(1, 'upgrade_step_login', 'WelcomeLogin', 2),
94
	1 => array(2, 'upgrade_step_options', 'UpgradeOptions', 2),
95
	2 => array(3, 'upgrade_step_backup', 'BackupDatabase', 10),
96
	3 => array(4, 'upgrade_step_database', 'DatabaseChanges', 50),
97
	4 => array(5, 'upgrade_step_convertjson', 'serialize_to_json', 10),
98
	5 => array(6, 'upgrade_step_convertutf', 'ConvertUtf8', 20),
99
	6 => array(7, 'upgrade_step_delete', 'DeleteUpgrade', 1),
100
);
101
// Just to remember which one has files in it.
102
$upcontext['database_step'] = 3;
103
@set_time_limit(600);
104
if (!ini_get('safe_mode'))
105
{
106
	ini_set('mysql.connect_timeout', -1);
107
	ini_set('default_socket_timeout', 900);
108
}
109
// Clean the upgrade path if this is from the client.
110
if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
111
	for ($i = 1; $i < $_SERVER['argc']; $i++)
112
	{
113
		if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0)
114
			$upgrade_path = substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1];
115
	}
116
117
// Are we from the client?
118
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
119
{
120
	$command_line = true;
121
	$disable_security = true;
122
}
123
else
124
	$command_line = false;
125
126
// We can't do anything without these files.
127
foreach (array('upgrade-helper.php', 'Settings.php') as $required_file)
128
{
129
	if (!file_exists($upgrade_path . '/' . $required_file))
130
		die($required_file . ' was not found where it was expected: ' . $upgrade_path . '/' . $required_file . '! Make sure you have uploaded ALL files from the upgrade package to your forum\'s root directory. The upgrader cannot continue.');
131
132
	require_once($upgrade_path . '/' . $required_file);
133
}
134
135
// We don't use "-utf8" anymore...  Tweak the entry that may have been loaded by Settings.php
136
if (isset($language))
137
	$language = str_ireplace('-utf8', '', basename($language, '.lng'));
138
139
// Figure out a valid language request (if any)
140
// 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.
141
if (isset($_SERVER['QUERY_STRING']) && preg_match('~\blang=(\w+)~', $_SERVER['QUERY_STRING'], $matches))
142
	$upcontext['lang'] = $matches[1];
143
144
// Are we logged in?
145
if (isset($upgradeData))
146
{
147
	$upcontext['user'] = json_decode(base64_decode($upgradeData), true);
148
149
	// Check for sensible values.
150
	if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400)
151
		$upcontext['user']['started'] = time();
152
	if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400)
153
		$upcontext['user']['updated'] = 0;
154
155
	$upcontext['started'] = $upcontext['user']['started'];
156
	$upcontext['updated'] = $upcontext['user']['updated'];
157
158
	$is_debug = !empty($upcontext['user']['debug']) ? true : false;
159
160
	$upcontext['skip_db_substeps'] = !empty($upcontext['user']['skip_db_substeps']);
161
}
162
163
// Nothing sensible?
164
if (empty($upcontext['updated']))
165
{
166
	$upcontext['started'] = time();
167
	$upcontext['updated'] = 0;
168
	$upcontext['skip_db_substeps'] = false;
169
	$upcontext['user'] = array(
170
		'id' => 0,
171
		'name' => 'Guest',
172
		'pass' => 0,
173
		'started' => $upcontext['started'],
174
		'updated' => $upcontext['updated'],
175
	);
176
}
177
178
// Try to load the language file... or at least define a few necessary strings for now.
179
load_lang_file();
180
181
// Load up some essential data...
182
loadEssentialData();
183
184
// Are we going to be mimic'ing SSI at this point?
185
if (isset($_GET['ssi']))
186
{
187
	require_once($sourcedir . '/Errors.php');
188
	require_once($sourcedir . '/Logging.php');
189
	require_once($sourcedir . '/Load.php');
190
	require_once($sourcedir . '/Security.php');
191
	require_once($sourcedir . '/Subs-Package.php');
192
193
	// SMF isn't started up properly, but loadUserSettings calls our cookies.
194
	if (!isset($smcFunc['json_encode']))
195
	{
196
		$smcFunc['json_encode'] = 'json_encode';
197
		$smcFunc['json_decode'] = 'smf_json_decode';
198
	}
199
200
	loadUserSettings();
201
	loadPermissions();
202
}
203
204
// Include our helper functions.
205
require_once($sourcedir . '/Subs.php');
206
require_once($sourcedir . '/LogInOut.php');
207
require_once($sourcedir . '/Subs-Editor.php');
208
209
// This only exists if we're on SMF ;)
210
if (isset($modSettings['smfVersion']))
211
{
212
	$request = $smcFunc['db_query']('', '
213
		SELECT variable, value
214
		FROM {db_prefix}themes
215
		WHERE id_theme = {int:id_theme}
216
			AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
217
		array(
218
			'id_theme' => 1,
219
			'theme_url' => 'theme_url',
220
			'theme_dir' => 'theme_dir',
221
			'images_url' => 'images_url',
222
			'db_error_skip' => true,
223
		)
224
	);
225
	while ($row = $smcFunc['db_fetch_assoc']($request))
226
		$modSettings[$row['variable']] = $row['value'];
227
	$smcFunc['db_free_result']($request);
228
}
229
230
if (!isset($modSettings['theme_url']))
231
{
232
	$modSettings['theme_dir'] = $boarddir . '/Themes/default';
233
	$modSettings['theme_url'] = 'Themes/default';
234
	$modSettings['images_url'] = 'Themes/default/images';
235
}
236
if (!isset($settings['default_theme_url']))
237
	$settings['default_theme_url'] = $modSettings['theme_url'];
238
if (!isset($settings['default_theme_dir']))
239
	$settings['default_theme_dir'] = $modSettings['theme_dir'];
240
241
// This is needed in case someone invokes the upgrader using https when upgrading an http forum
242
if (httpsOn())
243
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
244
245
$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000;
246
247
// Have we got tracking data - if so use it (It will be clean!)
248
if (isset($_GET['data']))
249
{
250
	global $is_debug;
251
252
	$upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true);
253
	$upcontext['current_step'] = $upcontext['upgrade_status']['curstep'];
254
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
255
	$upcontext['rid'] = $upcontext['upgrade_status']['rid'];
256
	$support_js = $upcontext['upgrade_status']['js'];
257
258
	// Only set this if the upgrader status says so.
259
	if (empty($is_debug))
260
		$is_debug = $upcontext['upgrade_status']['debug'];
261
}
262
// Set the defaults.
263
else
264
{
265
	$upcontext['current_step'] = 0;
266
	$upcontext['rid'] = mt_rand(0, 5000);
267
	$upcontext['upgrade_status'] = array(
268
		'curstep' => 0,
269
		'lang' => isset($upcontext['lang']) ? $upcontext['lang'] : basename($language, '.lng'),
270
		'rid' => $upcontext['rid'],
271
		'pass' => 0,
272
		'debug' => 0,
273
		'js' => 0,
274
	);
275
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
276
}
277
278
// Now that we have the necessary info, make sure we loaded the right language file.
279
load_lang_file();
280
281
// Default title...
282
$upcontext['page_title'] = $txt['updating_smf_installation'];
283
284
// If this isn't the first stage see whether they are logging in and resuming.
285
if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step']))
286
	checkLogin();
287
288
if ($command_line)
289
	cmdStep0();
290
291
// Don't error if we're using xml.
292
if (isset($_GET['xml']))
293
	$upcontext['return_error'] = true;
294
295
// Loop through all the steps doing each one as required.
296
$upcontext['overall_percent'] = 0;
297
foreach ($upcontext['steps'] as $num => $step)
298
{
299
	if ($num >= $upcontext['current_step'])
300
	{
301
		// The current weight of this step in terms of overall progress.
302
		$upcontext['step_weight'] = $step[3];
303
		// Make sure we reset the skip button.
304
		$upcontext['skip'] = false;
305
306
		// We cannot proceed if we're not logged in.
307
		if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass'])
308
		{
309
			$upcontext['steps'][0][2]();
310
			break;
311
		}
312
313
		// Call the step and if it returns false that means pause!
314
		if (function_exists($step[2]) && $step[2]() === false)
315
			break;
316
		elseif (function_exists($step[2]))
317
		{
318
			//Start each new step with this unset, so the 'normal' template is called first
319
			unset($_GET['xml']);
320
			//Clear out warnings at the start of each step
321
			unset($upcontext['custom_warning']);
322
			$_GET['substep'] = 0;
323
			$upcontext['current_step']++;
324
		}
325
	}
326
	$upcontext['overall_percent'] += $step[3];
327
}
328
329
upgradeExit();
330
331
// Exit the upgrade script.
332
function upgradeExit($fallThrough = false)
333
{
334
	global $upcontext, $upgradeurl, $sourcedir, $command_line, $is_debug, $txt;
335
336
	// Save where we are...
337
	if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id']))
338
	{
339
		$upcontext['user']['step'] = $upcontext['current_step'];
340
		$upcontext['user']['substep'] = $_GET['substep'];
341
		$upcontext['user']['updated'] = time();
342
		$upcontext['user']['skip_db_substeps'] = !empty($upcontext['skip_db_substeps']);
343
		$upcontext['debug'] = $is_debug;
344
		$upgradeData = base64_encode(json_encode($upcontext['user']));
345
		require_once($sourcedir . '/Subs-Admin.php');
346
		updateSettingsFile(array('upgradeData' => '"' . $upgradeData . '"'));
347
		updateDbLastError(0);
348
	}
349
350
	// Handle the progress of the step, if any.
351
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
352
	{
353
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
354
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
355
	}
356
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
357
358
	// We usually dump our templates out.
359
	if (!$fallThrough)
360
	{
361
		// This should not happen my dear... HELP ME DEVELOPERS!!
362
		if (!empty($command_line))
363
		{
364
			if (function_exists('debug_print_backtrace'))
365
				debug_print_backtrace();
366
367
			printf($txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
368
			flush();
369
			die();
370
		}
371
372
		if (!isset($_GET['xml']))
373
			template_upgrade_above();
374
		else
375
		{
376
			header('content-type: text/xml; charset=UTF-8');
377
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
378
			$upcontext['get_data'] = array();
379
			foreach ($_GET as $k => $v)
380
			{
381
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
382
				{
383
					$upcontext['get_data'][$k] = $v;
384
				}
385
			}
386
			template_xml_above();
387
		}
388
389
		// Call the template.
390
		if (isset($upcontext['sub_template']))
391
		{
392
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
393
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
394
395
			// Custom stuff to pass back?
396
			if (!empty($upcontext['query_string']))
397
				$upcontext['form_url'] .= $upcontext['query_string'];
398
399
			// Call the appropriate subtemplate
400
			if (is_callable('template_' . $upcontext['sub_template']))
401
				call_user_func('template_' . $upcontext['sub_template']);
402
			else
403
				die(sprintf($txt['error_invalid_template'], $upcontext['sub_template']));
404
		}
405
406
		// Was there an error?
407
		if (!empty($upcontext['forced_error_message']))
408
			echo $upcontext['forced_error_message'];
409
410
		// Show the footer.
411
		if (!isset($_GET['xml']))
412
			template_upgrade_below();
413
		else
414
			template_xml_below();
415
	}
416
417
	// Show the upgrade time for CLI when we are completely done, if in debug mode.
418
	if (!empty($command_line) && $is_debug)
419
	{
420
		$active = time() - $upcontext['started'];
421
		$hours = floor($active / 3600);
422
		$minutes = intval(($active / 60) % 60);
423
		$seconds = intval($active % 60);
424
425
		if ($hours > 0)
426
			echo "\n" . '', sprintf($txt['upgrade_completed_time_hms'], $hours, $minutes, $seconds), '' . "\n";
427
		elseif ($minutes > 0)
428
			echo "\n" . '', sprintf($txt['upgrade_completed_time_ms'], $minutes, $seconds), '' . "\n";
429
		elseif ($seconds > 0)
430
			echo "\n" . '', sprintf($txt['upgrade_completed_time_s'], $seconds), '' . "\n";
431
	}
432
433
	// Bang - gone!
434
	die();
435
}
436
437
// Load the list of language files, and the current language file.
438
function load_lang_file()
439
{
440
	global $txt, $upcontext, $language, $modSettings;
441
442
	static $lang_dir = '', $detected_languages = array(), $loaded_langfile = '';
443
444
	// Do we know where to look for the language files, or shall we just guess for now?
445
	$temp = isset($modSettings['theme_dir']) ? $modSettings['theme_dir'] . '/languages' : dirname(__FILE__) . '/Themes/default/languages';
446
447
	if ($lang_dir != $temp)
448
	{
449
		$lang_dir = $temp;
450
		$detected_languages = array();
451
	}
452
453
	// Override the language file?
454
	if (isset($upcontext['language']))
455
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['language'] . '.php';
456
	elseif (isset($upcontext['lang']))
457
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['lang'] . '.php';
458
	elseif (isset($language))
459
		$_SESSION['upgrader_langfile'] = 'Install.' . $language . '.php';
460
461
	// Avoid pointless repetition
462
	if (isset($_SESSION['upgrader_langfile']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_langfile'])
463
		return;
464
465
	// Now try to find the language files
466
	if (empty($detected_languages))
467
	{
468
		// Make sure the languages directory actually exists.
469
		if (file_exists($lang_dir))
470
		{
471
			// Find all the "Install" language files in the directory.
472
			$dir = dir($lang_dir);
473
			while ($entry = $dir->read())
474
			{
475
				// Skip any old '-utf8' language files that might be lying around
476
				if (strpos($entry, '-utf8') !== false)
477
					continue;
478
479
				if (substr($entry, 0, 8) == 'Install.' && substr($entry, -4) == '.php')
480
					$detected_languages[$entry] = ucfirst(substr($entry, 8, strlen($entry) - 12));
481
			}
482
			$dir->close();
483
		}
484
		// Our guess was wrong, but that's fine. We'll try again after $modSettings['theme_dir'] is defined.
485
		elseif (!isset($modSettings['theme_dir']))
486
		{
487
			// Define a few essential strings for now.
488
			$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.';
489
			$txt['error_sourcefile_missing'] = 'Unable to find the Sources/%1$s file. Please make sure it was uploaded properly, and then try again.';
490
491
			$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.';
492
			$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.';
493
494
			return;
495
		}
496
	}
497
498
	// Didn't find any, show an error message!
499
	if (empty($detected_languages))
500
	{
501
		$from = explode('/', $_SERVER['PHP_SELF']);
502
		$to = explode('/', $lang_dir);
503
		$relPath = $to;
504
505
		foreach($from as $depth => $dir)
506
		{
507
			if ($dir === $to[$depth])
508
				array_shift($relPath);
509
			else
510
			{
511
				$remaining = count($from) - $depth;
512
				if ($remaining > 1)
513
				{
514
					$padLength = (count($relPath) + $remaining - 1) * -1;
515
					$relPath = array_pad($relPath, $padLength, '..');
516
					break;
517
				}
518
				else
519
					$relPath[0] = './' . $relPath[0];
520
			}
521
		}
522
		$relPath = implode(DIRECTORY_SEPARATOR, $relPath);
523
524
		// Let's not cache this message, eh?
525
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
526
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
527
		header('Cache-Control: no-cache');
528
529
		echo '<!DOCTYPE html>
530
			<html>
531
				<head>
532
					<title>SMF Upgrader: Error!</title>
533
						<style>
534
							body {
535
								font-family: sans-serif;
536
								max-width: 700px; }
537
538
								h1 {
539
									font-size: 14pt; }
540
541
								.directory {
542
									margin: 0.3em;
543
									font-family: monospace;
544
									font-weight: bold; }
545
						</style>
546
				</head>
547
				<body>
548
					<h1>A critical error has occurred.</h1>
549
						<p>This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:</p>
550
						<div class="directory">', $relPath, '</div>
551
						<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>
552
						<p>If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.</p>
553
						<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>
554
				</body>
555
			</html>';
556
		die;
557
	}
558
559
	// Make sure it exists. If it doesn't, reset it.
560
	if (!isset($_SESSION['upgrader_langfile']) || preg_match('~[^\w.-]~', $_SESSION['upgrader_langfile']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_langfile']))
561
	{
562
		// Use the first one...
563
		list ($_SESSION['upgrader_langfile']) = array_keys($detected_languages);
564
565
		// If we have English and some other language, use the other language.
566
		if ($_SESSION['upgrader_langfile'] == 'Install.english.php' && count($detected_languages) > 1)
567
			list (, $_SESSION['upgrader_langfile']) = array_keys($detected_languages);
568
	}
569
570
	// For backup we load English at first, then the second language will overwrite it.
571
	if ($_SESSION['upgrader_langfile'] != 'Install.english.php')
572
		require_once($lang_dir . '/Install.english.php');
573
574
	// And now include the actual language file itself.
575
	require_once($lang_dir . '/' . $_SESSION['upgrader_langfile']);
576
577
	// Remember what we've done
578
	$loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_langfile'];
579
}
580
581
// Used to direct the user to another location.
582
function redirectLocation($location, $addForm = true)
583
{
584
	global $upgradeurl, $upcontext, $command_line;
585
586
	// Command line users can't be redirected.
587
	if ($command_line)
588
		upgradeExit(true);
589
590
	// Are we providing the core info?
591
	if ($addForm)
592
	{
593
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
594
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
595
	}
596
597
	while (@ob_end_clean())
598
		header('location: ' . strtr($location, array('&amp;' => '&')));
599
600
	// Exit - saving status as we go.
601
	upgradeExit(true);
602
}
603
604
// Load all essential data and connect to the DB as this is pre SSI.php
605
function loadEssentialData()
606
{
607
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection;
608
	global $db_prefix, $db_character_set, $db_type, $db_port;
609
	global $db_mb4, $modSettings, $sourcedir, $smcFunc, $txt;
610
611
	error_reporting(E_ALL);
612
	define('SMF', 1);
613
614
	// Start the session.
615
	if (@ini_get('session.save_handler') == 'user')
616
		@ini_set('session.save_handler', 'files');
617
	@session_start();
618
619
	if (empty($smcFunc))
620
		$smcFunc = array();
621
622
	$smcFunc['random_int'] = function($min = 0, $max = PHP_INT_MAX)
623
	{
624
		global $sourcedir;
625
626
		// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
627
		if (!is_callable('random_int'))
628
			require_once($sourcedir . '/random_compat/random.php');
629
630
		return random_int($min, $max);
631
	};
632
633
	// We need this for authentication and some upgrade code
634
	require_once($sourcedir . '/Subs-Auth.php');
635
	require_once($sourcedir . '/Class-Package.php');
636
637
	$smcFunc['strtolower'] = 'smf_strtolower';
638
639
	// Initialize everything...
640
	initialize_inputs();
641
642
	// Get the database going!
643
	if (empty($db_type) || $db_type == 'mysqli')
644
	{
645
		$db_type = 'mysql';
646
		// If overriding $db_type, need to set its settings.php entry too
647
		$changes = array();
648
		$changes['db_type'] = '\'mysql\'';
649
		require_once($sourcedir . '/Subs-Admin.php');
650
		updateSettingsFile($changes);
651
	}
652
653
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
654
	{
655
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
656
657
		// Make the connection...
658
		if (empty($db_connection))
659
		{
660
			$options = array('non_fatal' => true);
661
			// Add in the port if needed
662
			if (!empty($db_port))
663
				$options['port'] = $db_port;
664
665
			if (!empty($db_mb4))
666
				$options['db_mb4'] = $db_mb4;
667
668
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
669
		}
670
		else
671
			// If we've returned here, ping/reconnect to be safe
672
			$smcFunc['db_ping']($db_connection);
673
674
		// Oh dear god!!
675
		if ($db_connection === null)
676
			die($txt['error_db_connect_settings']);
677
678
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
679
			$smcFunc['db_query']('', '
680
				SET NAMES {string:db_character_set}',
681
				array(
682
					'db_error_skip' => true,
683
					'db_character_set' => $db_character_set,
684
				)
685
			);
686
687
		// Load the modSettings data...
688
		$request = $smcFunc['db_query']('', '
689
			SELECT variable, value
690
			FROM {db_prefix}settings',
691
			array(
692
				'db_error_skip' => true,
693
			)
694
		);
695
		$modSettings = array();
696
		while ($row = $smcFunc['db_fetch_assoc']($request))
697
			$modSettings[$row['variable']] = $row['value'];
698
		$smcFunc['db_free_result']($request);
699
	}
700
	else
701
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
702
703
	require_once($sourcedir . '/Subs.php');
704
705
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
706
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
707
	{
708
		require_once($sourcedir . '/QueryString.php');
709
		cleanRequest();
710
	}
711
712
	if (!isset($_GET['substep']))
713
		$_GET['substep'] = 0;
714
}
715
716
function initialize_inputs()
717
{
718
	global $start_time, $db_type;
719
720
	$start_time = time();
721
722
	umask(0);
723
724
	ob_start();
725
726
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
727
	ignore_user_abort(true);
728
729
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
730
	if (isset($_GET['delete']))
731
	{
732
		@unlink(__FILE__);
733
734
		// And the extra little files ;).
735
		@unlink(dirname(__FILE__) . '/upgrade_1-0.sql');
736
		@unlink(dirname(__FILE__) . '/upgrade_1-1.sql');
737
		@unlink(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
738
		@unlink(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
739
		@unlink(dirname(__FILE__) . '/upgrade-helper.php');
740
741
		$dh = opendir(dirname(__FILE__));
742
		while ($file = readdir($dh))
743
		{
744
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
745
				@unlink(dirname(__FILE__) . '/' . $file);
746
		}
747
		closedir($dh);
748
749
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
750
		// 1.1 Sources files not in 2.0+
751
		@unlink(dirname(__FILE__) . '/Sources/ModSettings.php');
752
		// 1.1 Templates that don't exist any more (e.g. renamed)
753
		@unlink(dirname(__FILE__) . '/Themes/default/Combat.template.php');
754
		@unlink(dirname(__FILE__) . '/Themes/default/Modlog.template.php');
755
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
756
		@unlink(dirname(__FILE__) . '/Themes/default/fader.js');
757
		@unlink(dirname(__FILE__) . '/Themes/default/script.js');
758
		@unlink(dirname(__FILE__) . '/Themes/default/spellcheck.js');
759
		@unlink(dirname(__FILE__) . '/Themes/default/xml_board.js');
760
		@unlink(dirname(__FILE__) . '/Themes/default/xml_topic.js');
761
762
		// 2.0 Sources files not in 2.1+
763
		@unlink(dirname(__FILE__) . '/Sources/DumpDatabase.php');
764
		@unlink(dirname(__FILE__) . '/Sources/LockTopic.php');
765
766
		header('location: http://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
767
		exit;
768
	}
769
770
	// Something is causing this to happen, and it's annoying.  Stop it.
771
	$temp = 'upgrade_php?step';
772
	while (strlen($temp) > 4)
773
	{
774
		if (isset($_GET[$temp]))
775
			unset($_GET[$temp]);
776
		$temp = substr($temp, 1);
777
	}
778
779
	// Force a step, defaulting to 0.
780
	$_GET['step'] = (int) @$_GET['step'];
781
	$_GET['substep'] = (int) @$_GET['substep'];
782
}
783
784
// Step 0 - Let's welcome them in and ask them to login!
785
function WelcomeLogin()
786
{
787
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
788
	global $smcFunc, $db_type, $databases, $boardurl;
789
790
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
791
	global $txt;
792
793
	$upcontext['sub_template'] = 'welcome_message';
794
795
	// Check for some key files - one template, one language, and a new and an old source file.
796
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
797
		&& @file_exists($sourcedir . '/QueryString.php')
798
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
799
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
800
801
	// Need legacy scripts?
802
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
803
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
804
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
805
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
806
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
807
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
808
809
	// We don't need "-utf8" files anymore...
810
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
811
812
	if (!$check)
813
		// 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.
814
		return throw_error($txt['error_upgrade_files_missing']);
815
816
	// Do they meet the install requirements?
817
	if (!php_version_check())
818
		return throw_error($txt['error_php_too_low']);
819
820
	if (!db_version_check())
821
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
822
823
	// Do some checks to make sure they have proper privileges
824
	db_extend('packages');
825
826
	// CREATE
827
	$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');
828
829
	// ALTER
830
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
831
832
	// DROP
833
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
834
835
	// Sorry... we need CREATE, ALTER and DROP
836
	if (!$create || !$alter || !$drop)
837
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
838
839
	// Do a quick version spot check.
840
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
841
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
842
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
843
		return throw_error($txt['error_upgrade_old_files']);
844
845
	// What absolutely needs to be writable?
846
	$writable_files = array(
847
		$boarddir . '/Settings.php',
848
		$boarddir . '/Settings_bak.php',
849
	);
850
851
	// Only check for minified writable files if we have it enabled or not set.
852
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
853
		$writable_files += array(
854
			$modSettings['theme_dir'] . '/css/minified.css',
855
			$modSettings['theme_dir'] . '/scripts/minified.js',
856
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
857
		);
858
859
	// Do we need to add this setting?
860
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
861
862
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
863
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
864
865
	// This little fellow has to cooperate...
866
	quickFileWritable($custom_av_dir);
867
868
	// Are we good now?
869
	if (!is_writable($custom_av_dir))
870
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
871
	elseif ($need_settings_update)
872
	{
873
		if (!function_exists('cache_put_data'))
874
			require_once($sourcedir . '/Load.php');
875
876
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
877
		updateSettings(array('custom_avatar_url' => $custom_av_url));
878
	}
879
880
	require_once($sourcedir . '/Security.php');
881
882
	// Check the cache directory.
883
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
884
	if (!file_exists($cachedir_temp))
885
		@mkdir($cachedir_temp);
886
887
	if (!file_exists($cachedir_temp))
888
		return throw_error($txt['error_cache_not_found']);
889
890
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
891
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
892
	elseif (!isset($_GET['skiplang']))
893
	{
894
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
895
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
896
897
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
898
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
899
	}
900
901
	if (!makeFilesWritable($writable_files))
902
		return false;
903
904
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
905
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
906
		return throw_error($txt['error_agreement_not_writable']);
907
908
	// Upgrade the agreement.
909
	elseif (isset($modSettings['agreement']))
910
	{
911
		$fp = fopen($boarddir . '/agreement.txt', 'w');
912
		fwrite($fp, $modSettings['agreement']);
913
		fclose($fp);
914
	}
915
916
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
917
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr(dirname(__FILE__), array('/' => '', '\\' => '')))
918
		$upcontext['warning'] = '
919
			' . sprintf($txt['upgrade_boarddir_settings'], $boarddir, dirname(__FILE__)) . '<br>
920
			<ul>
921
				<li>' . $txt['upgrade_boarddir'] . '  ' . $boarddir . '</li>
922
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
923
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
924
			</ul>
925
			' . $txt['upgrade_incorrect_settings'] . '';
926
927
	// Confirm mbstring is loaded...
928
	if (!extension_loaded('mbstring'))
929
		return throw_error($txt['install_no_mbstring']);
930
931
	// Check for https stream support.
932
	$supported_streams = stream_get_wrappers();
933
	if (!in_array('https', $supported_streams))
934
		$upcontext['custom_warning'] = $txt['install_no_https'];
935
936
	// Either we're logged in or we're going to present the login.
937
	if (checkLogin())
938
		return true;
939
940
	$upcontext += createToken('login');
941
942
	return false;
943
}
944
945
// Step 0.5: Does the login work?
946
function checkLogin()
947
{
948
	global $modSettings, $upcontext, $disable_security;
949
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
950
951
	// Don't bother if the security is disabled.
952
	if ($disable_security)
953
		return true;
954
955
	// Are we trying to login?
956
	if (isset($_POST['contbutt']) && (!empty($_POST['user'])))
957
	{
958
		// If we've disabled security pick a suitable name!
959
		if (empty($_POST['user']))
960
			$_POST['user'] = 'Administrator';
961
962
		// Before 2.0 these column names were different!
963
		$oldDB = false;
964
		if (empty($db_type) || $db_type == 'mysql')
965
		{
966
			$request = $smcFunc['db_query']('', '
967
				SHOW COLUMNS
968
				FROM {db_prefix}members
969
				LIKE {string:member_name}',
970
				array(
971
					'member_name' => 'memberName',
972
					'db_error_skip' => true,
973
				)
974
			);
975
			if ($smcFunc['db_num_rows']($request) != 0)
976
				$oldDB = true;
977
			$smcFunc['db_free_result']($request);
978
		}
979
980
		// Get what we believe to be their details.
981
		if (!$disable_security)
982
		{
983
			if ($oldDB)
984
				$request = $smcFunc['db_query']('', '
985
					SELECT id_member, memberName AS member_name, passwd, id_group,
986
						additionalGroups AS additional_groups, lngfile
987
					FROM {db_prefix}members
988
					WHERE memberName = {string:member_name}',
989
					array(
990
						'member_name' => $_POST['user'],
991
						'db_error_skip' => true,
992
					)
993
				);
994
			else
995
				$request = $smcFunc['db_query']('', '
996
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
997
					FROM {db_prefix}members
998
					WHERE member_name = {string:member_name}',
999
					array(
1000
						'member_name' => $_POST['user'],
1001
						'db_error_skip' => true,
1002
					)
1003
				);
1004
			if ($smcFunc['db_num_rows']($request) != 0)
1005
			{
1006
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1007
1008
				$groups = explode(',', $addGroups);
1009
				$groups[] = $id_group;
1010
1011
				foreach ($groups as $k => $v)
1012
					$groups[$k] = (int) $v;
1013
1014
				$sha_passwd = sha1(strtolower($name) . un_htmlspecialchars($_REQUEST['passwrd']));
1015
1016
				// We don't use "-utf8" anymore...
1017
				$user_language = str_ireplace('-utf8', '', $user_language);
1018
			}
1019
			else
1020
				$upcontext['username_incorrect'] = true;
1021
1022
			$smcFunc['db_free_result']($request);
1023
		}
1024
		$upcontext['username'] = $_POST['user'];
1025
1026
		// Track whether javascript works!
1027
		if (!empty($_POST['js_works']))
1028
		{
1029
			$upcontext['upgrade_status']['js'] = 1;
1030
			$support_js = 1;
1031
		}
1032
		else
1033
			$support_js = 0;
1034
1035
		// Note down the version we are coming from.
1036
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1037
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1038
1039
		// Didn't get anywhere?
1040
		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']))
1041
		{
1042
			// MD5?
1043
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1044
			if ($md5pass != $password)
1045
			{
1046
				$upcontext['password_failed'] = true;
1047
				// Disable the hashing this time.
1048
				$upcontext['disable_login_hashing'] = true;
1049
			}
1050
		}
1051
1052
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1053
		{
1054
			// Set the password.
1055
			if (!$disable_security)
1056
			{
1057
				// Do we actually have permission?
1058
				if (!in_array(1, $groups))
1059
				{
1060
					$request = $smcFunc['db_query']('', '
1061
						SELECT permission
1062
						FROM {db_prefix}permissions
1063
						WHERE id_group IN ({array_int:groups})
1064
							AND permission = {string:admin_forum}',
1065
						array(
1066
							'groups' => $groups,
1067
							'admin_forum' => 'admin_forum',
1068
							'db_error_skip' => true,
1069
						)
1070
					);
1071
					if ($smcFunc['db_num_rows']($request) == 0)
1072
						return throw_error($txt['error_not_admin']);
1073
					$smcFunc['db_free_result']($request);
1074
				}
1075
1076
				$upcontext['user']['id'] = $id_member;
1077
				$upcontext['user']['name'] = $name;
1078
			}
1079
			else
1080
			{
1081
				$upcontext['user']['id'] = 1;
1082
				$upcontext['user']['name'] = 'Administrator';
1083
			}
1084
1085
			if (!is_callable('random_int'))
1086
				require_once('Sources/random_compat/random.php');
1087
1088
			$upcontext['user']['pass'] = random_int(0, 60000);
1089
			// This basically is used to match the GET variables to Settings.php.
1090
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1091
1092
			// Set the language to that of the user?
1093
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1094
			{
1095
				$user_language = basename($user_language, '.lng');
1096
				$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
1097
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1098
1099
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1100
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1101
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1102
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1103
				else
1104
				{
1105
					// Set this as the new language.
1106
					$upcontext['language'] = $user_language;
1107
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1108
1109
					// Include the file.
1110
					load_lang_file();
1111
				}
1112
			}
1113
1114
			// If we're resuming set the step and substep to be correct.
1115
			if (isset($_POST['cont']))
1116
			{
1117
				$upcontext['current_step'] = $upcontext['user']['step'];
1118
				$_GET['substep'] = $upcontext['user']['substep'];
1119
			}
1120
1121
			return true;
1122
		}
1123
	}
1124
1125
	return false;
1126
}
1127
1128
// Step 1: Do the maintenance and backup.
1129
function UpgradeOptions()
1130
{
1131
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1132
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1133
1134
	$upcontext['sub_template'] = 'upgrade_options';
1135
	$upcontext['page_title'] = $txt['upgrade_options'];
1136
1137
	db_extend('packages');
1138
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1139
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1140
1141
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1142
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1143
1144
	unset($member_columns);
1145
1146
	// If these options are missing, we may need to migrate to a new Settings.php
1147
	$upcontext['migrateSettingsNeeded'] = detectSettingsFileMigrationNeeded();
1148
1149
	// If we've not submitted then we're done.
1150
	if (empty($_POST['upcont']))
1151
		return false;
1152
1153
	// Firstly, if they're enabling SM stat collection just do it.
1154
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1155
	{
1156
		$upcontext['allow_sm_stats'] = true;
1157
1158
		// Don't register if we still have a key.
1159
		if (empty($modSettings['sm_stats_key']))
1160
		{
1161
			// Attempt to register the site etc.
1162
			$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1163
			if ($fp)
1164
			{
1165
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1166
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1167
				$out .= 'Connection: Close' . "\r\n\r\n";
1168
				fwrite($fp, $out);
1169
1170
				$return_data = '';
1171
				while (!feof($fp))
1172
					$return_data .= fgets($fp, 128);
1173
1174
				fclose($fp);
1175
1176
				// Get the unique site ID.
1177
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1178
1179
				if (!empty($ID[1]))
1180
					$smcFunc['db_insert']('replace',
1181
						$db_prefix . 'settings',
1182
						array('variable' => 'string', 'value' => 'string'),
1183
						array(
1184
							array('sm_stats_key', $ID[1]),
1185
							array('enable_sm_stats', 1),
1186
						),
1187
						array('variable')
1188
					);
1189
			}
1190
		}
1191
		else
1192
		{
1193
			$smcFunc['db_insert']('replace',
1194
				$db_prefix . 'settings',
1195
				array('variable' => 'string', 'value' => 'string'),
1196
				array('enable_sm_stats', 1),
1197
				array('variable')
1198
			);
1199
		}
1200
	}
1201
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1202
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1203
		$smcFunc['db_query']('', '
1204
			DELETE FROM {db_prefix}settings
1205
			WHERE variable = {string:enable_sm_stats}',
1206
			array(
1207
				'enable_sm_stats' => 'enable_sm_stats',
1208
				'db_error_skip' => true,
1209
			)
1210
		);
1211
1212
	// Deleting old karma stuff?
1213
	if (!empty($_POST['delete_karma']))
1214
	{
1215
		// Delete old settings vars.
1216
		$smcFunc['db_query']('', '
1217
			DELETE FROM {db_prefix}settings
1218
			WHERE variable IN ({array_string:karma_vars})',
1219
			array(
1220
				'karma_vars' => array('karmaMode', 'karmaTimeRestrictAdmins', 'karmaWaitTime', 'karmaMinPosts', 'karmaLabel', 'karmaSmiteLabel', 'karmaApplaudLabel'),
1221
			)
1222
		);
1223
1224
		// Cleaning up old karma member settings.
1225
		if ($upcontext['karma_installed']['good'])
1226
			$smcFunc['db_query']('', '
1227
				ALTER TABLE {db_prefix}members
1228
				DROP karma_good',
1229
				array()
1230
			);
1231
1232
		// Does karma bad was enable?
1233
		if ($upcontext['karma_installed']['bad'])
1234
			$smcFunc['db_query']('', '
1235
				ALTER TABLE {db_prefix}members
1236
				DROP karma_bad',
1237
				array()
1238
			);
1239
1240
		// Cleaning up old karma permissions.
1241
		$smcFunc['db_query']('', '
1242
			DELETE FROM {db_prefix}permissions
1243
			WHERE permission = {string:karma_vars}',
1244
			array(
1245
				'karma_vars' => 'karma_edit',
1246
			)
1247
		);
1248
		// Cleaning up old log_karma table
1249
		$smcFunc['db_query']('', '
1250
			DROP TABLE IF EXISTS {db_prefix}log_karma',
1251
			array()
1252
		);
1253
	}
1254
1255
	// Emptying the error log?
1256
	if (!empty($_POST['empty_error']))
1257
		$smcFunc['db_query']('truncate_table', '
1258
			TRUNCATE {db_prefix}log_errors',
1259
			array(
1260
			)
1261
		);
1262
1263
	$changes = array();
1264
1265
	// Add proxy settings.
1266
	if (!isset($GLOBALS['image_proxy_maxsize']))
1267
		$changes += array(
1268
			'image_proxy_secret' => '\'' . substr(sha1(mt_rand()), 0, 20) . '\'',
1269
			'image_proxy_maxsize' => 5190,
1270
			'image_proxy_enabled' => 0,
1271
		);
1272
1273
	// If $boardurl reflects https, set force_ssl
1274
	if (!function_exists('cache_put_data'))
1275
		require_once($sourcedir . '/Load.php');
1276
	if (stripos($boardurl, 'https://') !== false)
1277
		updateSettings(array('force_ssl' => '1'));
1278
1279
	// If we're overriding the language follow it through.
1280
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1281
		$changes['language'] = '\'' . $upcontext['lang'] . '\'';
1282
1283
	if (!empty($_POST['maint']))
1284
	{
1285
		$changes['maintenance'] = '2';
1286
		// Remember what it was...
1287
		$upcontext['user']['main'] = $maintenance;
1288
1289
		if (!empty($_POST['maintitle']))
1290
		{
1291
			$changes['mtitle'] = '\'' . addslashes($_POST['maintitle']) . '\'';
1292
			$changes['mmessage'] = '\'' . addslashes($_POST['mainmessage']) . '\'';
1293
		}
1294
		else
1295
		{
1296
			$changes['mtitle'] = '\'' . addslashes($txt['mtitle']) . '\'';
1297
			$changes['mmessage'] = '\'' . addslashes($txt['mmessage']) . '\'';
1298
		}
1299
	}
1300
1301
	if ($command_line)
1302
		echo ' * Updating Settings.php...';
1303
1304
	// Fix some old paths.
1305
	if (substr($boarddir, 0, 1) == '.')
1306
		$changes['boarddir'] = '\'' . fixRelativePath($boarddir) . '\'';
1307
1308
	if (substr($sourcedir, 0, 1) == '.')
1309
		$changes['sourcedir'] = '\'' . fixRelativePath($sourcedir) . '\'';
1310
1311
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1312
		$changes['cachedir'] = '\'' . fixRelativePath($boarddir) . '/cache\'';
1313
1314
	// Migrate cache settings.
1315
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1316
	if (!isset($GLOBALS['cache_enable']))
1317
		$changes += array(
1318
			'cache_accelerator' => !empty($modSettings['cache_enable']) ? '\'smf\'' : '\'\'',
1319
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1320
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? '\'' . $modSettings['cache_memcached'] . '\'' : '\'\'',
1321
		);
1322
1323
	// If they have a "host:port" setup for the host, split that into separate values
1324
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1325
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1326
	{
1327
		list ($db_server, $db_port) = explode(':', $db_server);
1328
1329
		$changes['db_server'] = '\'' . $db_server . '\'';
1330
1331
		// Only set this if we're not using the default port
1332
		if ($db_port != ini_get('mysqli.default_port'))
1333
			$changes['db_port'] = (int) $db_port;
1334
	}
1335
1336
	// If db_port is set and is the same as the default, set it to 0.
1337
	if (!empty($db_port))
1338
	{
1339
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1340
			$changes['db_port'] = 0;
1341
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1342
			$changes['db_port'] = 0;
1343
	}
1344
1345
	// Maybe we haven't had this option yet?
1346
	if (empty($packagesdir))
1347
		$changes['packagesdir'] = '\'' . fixRelativePath($boarddir) . '/Packages\'';
1348
1349
	// Add support for $tasksdir var.
1350
	if (empty($tasksdir))
1351
		$changes['tasksdir'] = '\'' . fixRelativePath($sourcedir) . '/tasks\'';
1352
1353
	// Make sure we fix the language as well.
1354
	if (stristr($language, '-utf8'))
1355
		$changes['language'] = '\'' . str_ireplace('-utf8', '', $language) . '\'';
1356
1357
	// @todo Maybe change the cookie name if going to 1.1, too?
1358
1359
	// If we are migrating the settings, get them ready.
1360
	if (!empty($_POST['migrateSettings']))
1361
	{
1362
		// Ensure this doesn't get lost in translation.
1363
		$changes['upgradeData'] = '"' . base64_encode(json_encode($upcontext['user'])) . '"';
1364
1365
		migrateSettingsFile($changes);
1366
	}
1367
	else
1368
	{
1369
		// Update Settings.php with the new settings.
1370
		require_once($sourcedir . '/Subs-Admin.php');
1371
		updateSettingsFile($changes);
1372
1373
		// Tell Settings.php to store db_last_error.php in the cache
1374
		move_db_last_error_to_cachedir();
1375
	}
1376
1377
	if ($command_line)
1378
		echo ' Successful.' . "\n";
1379
1380
	// Are we doing debug?
1381
	if (isset($_POST['debug']))
1382
	{
1383
		$upcontext['upgrade_status']['debug'] = true;
1384
		$is_debug = true;
1385
	}
1386
1387
	// If we're not backing up then jump one.
1388
	if (empty($_POST['backup']))
1389
		$upcontext['current_step']++;
1390
1391
	// If we've got here then let's proceed to the next step!
1392
	return true;
1393
}
1394
1395
// Backup the database - why not...
1396
function BackupDatabase()
1397
{
1398
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1399
1400
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1401
	$upcontext['page_title'] = $txt['backup_database'];
1402
1403
	// Done it already - js wise?
1404
	if (!empty($_POST['backup_done']))
1405
		return true;
1406
1407
	// Some useful stuff here.
1408
	db_extend();
1409
1410
	// Might need this as well
1411
	db_extend('packages');
1412
1413
	// Get all the table names.
1414
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1415
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1416
	$tables = $smcFunc['db_list_tables']($db, $filter);
1417
1418
	$table_names = array();
1419
	foreach ($tables as $table)
1420
		if (substr($table, 0, 7) !== 'backup_')
1421
			$table_names[] = $table;
1422
1423
	$upcontext['table_count'] = count($table_names);
1424
	$upcontext['cur_table_num'] = $_GET['substep'];
1425
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1426
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1427
	// For non-java auto submit...
1428
	$file_steps = $upcontext['table_count'];
1429
1430
	// What ones have we already done?
1431
	foreach ($table_names as $id => $table)
1432
		if ($id < $_GET['substep'])
1433
			$upcontext['previous_tables'][] = $table;
1434
1435
	if ($command_line)
1436
		echo 'Backing Up Tables.';
1437
1438
	// If we don't support javascript we backup here.
1439
	if (!$support_js || isset($_GET['xml']))
1440
	{
1441
		// Backup each table!
1442
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1443
		{
1444
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1445
			$upcontext['cur_table_num'] = $substep + 1;
1446
1447
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1448
1449
			// Do we need to pause?
1450
			nextSubstep($substep);
1451
1452
			backupTable($table_names[$substep]);
1453
1454
			// If this is XML to keep it nice for the user do one table at a time anyway!
1455
			if (isset($_GET['xml']))
1456
				return upgradeExit();
1457
		}
1458
1459
		if ($command_line)
1460
		{
1461
			echo "\n" . ' Successful.\'' . "\n";
1462
			flush();
1463
		}
1464
		$upcontext['step_progress'] = 100;
1465
1466
		$_GET['substep'] = 0;
1467
		// Make sure we move on!
1468
		return true;
1469
	}
1470
1471
	// Either way next place to post will be database changes!
1472
	$_GET['substep'] = 0;
1473
	return false;
1474
}
1475
1476
// Backup one table...
1477
function backupTable($table)
1478
{
1479
	global $command_line, $db_prefix, $smcFunc;
1480
1481
	if ($command_line)
1482
	{
1483
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1484
		flush();
1485
	}
1486
1487
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1488
1489
	if ($command_line)
1490
		echo ' done.';
1491
}
1492
1493
// Step 2: Everything.
1494
function DatabaseChanges()
1495
{
1496
	global $db_prefix, $modSettings, $smcFunc, $txt;
1497
	global $upcontext, $support_js, $db_type;
1498
1499
	// Have we just completed this?
1500
	if (!empty($_POST['database_done']))
1501
		return true;
1502
1503
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1504
	$upcontext['page_title'] = $txt['database_changes'];
1505
1506
	// All possible files.
1507
	// Name, < version, insert_on_complete
1508
	$files = array(
1509
		array('upgrade_1-0.sql', '1.1', '1.1 RC0'),
1510
		array('upgrade_1-1.sql', '2.0', '2.0 a'),
1511
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0'),
1512
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION),
1513
	);
1514
1515
	// How many files are there in total?
1516
	if (isset($_GET['filecount']))
1517
		$upcontext['file_count'] = (int) $_GET['filecount'];
1518
	else
1519
	{
1520
		$upcontext['file_count'] = 0;
1521
		foreach ($files as $file)
1522
		{
1523
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1524
				$upcontext['file_count']++;
1525
		}
1526
	}
1527
1528
	// Do each file!
1529
	$did_not_do = count($files) - $upcontext['file_count'];
1530
	$upcontext['step_progress'] = 0;
1531
	$upcontext['cur_file_num'] = 0;
1532
	foreach ($files as $file)
1533
	{
1534
		if ($did_not_do)
1535
			$did_not_do--;
1536
		else
1537
		{
1538
			$upcontext['cur_file_num']++;
1539
			$upcontext['cur_file_name'] = $file[0];
1540
			// Do we actually need to do this still?
1541
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1542
			{
1543
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1544
				if ($nextFile)
1545
				{
1546
					// Only update the version of this if complete.
1547
					$smcFunc['db_insert']('replace',
1548
						$db_prefix . 'settings',
1549
						array('variable' => 'string', 'value' => 'string'),
1550
						array('smfVersion', $file[2]),
1551
						array('variable')
1552
					);
1553
1554
					$modSettings['smfVersion'] = $file[2];
1555
				}
1556
1557
				// If this is XML we only do this stuff once.
1558
				if (isset($_GET['xml']))
1559
				{
1560
					// Flag to move on to the next.
1561
					$upcontext['completed_step'] = true;
1562
					// Did we complete the whole file?
1563
					if ($nextFile)
1564
						$upcontext['current_debug_item_num'] = -1;
1565
					return upgradeExit();
1566
				}
1567
				elseif ($support_js)
1568
					break;
1569
			}
1570
			// Set the progress bar to be right as if we had - even if we hadn't...
1571
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1572
		}
1573
	}
1574
1575
	$_GET['substep'] = 0;
1576
	// So the template knows we're done.
1577
	if (!$support_js)
1578
	{
1579
		$upcontext['changes_complete'] = true;
1580
1581
		return true;
1582
	}
1583
	return false;
1584
}
1585
1586
// Delete the damn thing!
1587
function DeleteUpgrade()
1588
{
1589
	global $command_line, $language, $upcontext, $sourcedir;
1590
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1591
1592
	// Now it's nice to have some of the basic SMF source files.
1593
	if (!isset($_GET['ssi']) && !$command_line)
1594
		redirectLocation('&ssi=1');
1595
1596
	$upcontext['sub_template'] = 'upgrade_complete';
1597
	$upcontext['page_title'] = $txt['upgrade_complete'];
1598
1599
	$endl = $command_line ? "\n" : '<br>' . "\n";
1600
1601
	$changes = array(
1602
		'language' => '\'' . (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language) . '\'',
1603
		'db_error_send' => '1',
1604
		'upgradeData' => '\'\'',
1605
	);
1606
1607
	// Are we in maintenance mode?
1608
	if (isset($upcontext['user']['main']))
1609
	{
1610
		if ($command_line)
1611
			echo ' * ';
1612
		$upcontext['removed_maintenance'] = true;
1613
		$changes['maintenance'] = $upcontext['user']['main'];
1614
	}
1615
	// Otherwise if somehow we are in 2 let's go to 1.
1616
	elseif (!empty($maintenance) && $maintenance == 2)
1617
		$changes['maintenance'] = 1;
1618
1619
	// Wipe this out...
1620
	$upcontext['user'] = array();
1621
1622
	require_once($sourcedir . '/Subs-Admin.php');
1623
	updateSettingsFile($changes);
1624
1625
	// Clean any old cache files away.
1626
	upgrade_clean_cache();
1627
1628
	// Can we delete the file?
1629
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1630
1631
	// Now is the perfect time to fetch the SM files.
1632
	if ($command_line)
1633
		cli_scheduled_fetchSMfiles();
1634
	else
1635
	{
1636
		require_once($sourcedir . '/ScheduledTasks.php');
1637
		scheduled_fetchSMfiles(); // Now go get those files!
1638
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1639
		if (httpsOn())
1640
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1641
	}
1642
1643
	// Log what we've done.
1644
	if (empty($user_info['id']))
1645
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1646
1647
	// Log the action manually, so CLI still works.
1648
	$smcFunc['db_insert']('',
1649
		'{db_prefix}log_actions',
1650
		array(
1651
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1652
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1653
		),
1654
		array(
1655
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1656
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1657
		),
1658
		array('id_action')
1659
	);
1660
	$user_info['id'] = 0;
1661
1662
	if ($command_line)
1663
	{
1664
		echo $endl;
1665
		echo 'Upgrade Complete!', $endl;
1666
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1667
		exit;
1668
	}
1669
1670
	// Make sure it says we're done.
1671
	$upcontext['overall_percent'] = 100;
1672
	if (isset($upcontext['step_progress']))
1673
		unset($upcontext['step_progress']);
1674
1675
	$_GET['substep'] = 0;
1676
	return false;
1677
}
1678
1679
// Just like the built in one, but setup for CLI to not use themes.
1680
function cli_scheduled_fetchSMfiles()
1681
{
1682
	global $sourcedir, $language, $modSettings, $smcFunc;
1683
1684
	if (empty($modSettings['time_format']))
1685
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1686
1687
	// What files do we want to get
1688
	$request = $smcFunc['db_query']('', '
1689
		SELECT id_file, filename, path, parameters
1690
		FROM {db_prefix}admin_info_files',
1691
		array(
1692
		)
1693
	);
1694
1695
	$js_files = array();
1696
	while ($row = $smcFunc['db_fetch_assoc']($request))
1697
	{
1698
		$js_files[$row['id_file']] = array(
1699
			'filename' => $row['filename'],
1700
			'path' => $row['path'],
1701
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1702
		);
1703
	}
1704
	$smcFunc['db_free_result']($request);
1705
1706
	// We're gonna need fetch_web_data() to pull this off.
1707
	require_once($sourcedir . '/Subs.php');
1708
1709
	foreach ($js_files as $ID_FILE => $file)
1710
	{
1711
		// Create the url
1712
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
1713
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1714
1715
		// Get the file
1716
		$file_data = fetch_web_data($url);
1717
1718
		// If we got an error - give up - the site might be down.
1719
		if ($file_data === false)
1720
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
1721
1722
		// Save the file to the database.
1723
		$smcFunc['db_query']('substring', '
1724
			UPDATE {db_prefix}admin_info_files
1725
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1726
			WHERE id_file = {int:id_file}',
1727
			array(
1728
				'id_file' => $ID_FILE,
1729
				'file_data' => $file_data,
1730
			)
1731
		);
1732
	}
1733
	return true;
1734
}
1735
1736
function convertSettingsToTheme()
1737
{
1738
	global $db_prefix, $modSettings, $smcFunc;
1739
1740
	$values = array(
1741
		'show_latest_member' => @$GLOBALS['showlatestmember'],
1742
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
1743
		'show_modify' => @$GLOBALS['showmodify'],
1744
		'show_user_images' => @$GLOBALS['showuserpic'],
1745
		'show_blurb' => @$GLOBALS['showusertext'],
1746
		'show_gender' => @$GLOBALS['showgenderimage'],
1747
		'show_newsfader' => @$GLOBALS['shownewsfader'],
1748
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
1749
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
1750
		'linktree_link' => @$GLOBALS['curposlinks'],
1751
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
1752
		'show_mark_read' => @$GLOBALS['showmarkread'],
1753
		'newsfader_time' => @$GLOBALS['fadertime'],
1754
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
1755
		'enable_news' => @$GLOBALS['enable_news'],
1756
		'return_to_post' => @$modSettings['returnToPost'],
1757
	);
1758
1759
	$themeData = array();
1760
	foreach ($values as $variable => $value)
1761
	{
1762
		if (!isset($value) || $value === null)
1763
			$value = 0;
1764
1765
		$themeData[] = array(0, 1, $variable, $value);
1766
	}
1767
	if (!empty($themeData))
1768
	{
1769
		$smcFunc['db_insert']('ignore',
1770
			$db_prefix . 'themes',
1771
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
1772
			$themeData,
1773
			array('id_member', 'id_theme', 'variable')
1774
		);
1775
	}
1776
}
1777
1778
// This function only works with MySQL but that's fine as it is only used for v1.0.
1779
function convertSettingstoOptions()
1780
{
1781
	global $modSettings, $smcFunc;
1782
1783
	// Format: new_setting -> old_setting_name.
1784
	$values = array(
1785
		'calendar_start_day' => 'cal_startmonday',
1786
		'view_newest_first' => 'viewNewestFirst',
1787
		'view_newest_pm_first' => 'viewNewestFirst',
1788
	);
1789
1790
	foreach ($values as $variable => $value)
1791
	{
1792
		if (empty($modSettings[$value[0]]))
1793
			continue;
1794
1795
		$smcFunc['db_query']('', '
1796
			INSERT IGNORE INTO {db_prefix}themes
1797
				(id_member, id_theme, variable, value)
1798
			SELECT id_member, 1, {string:variable}, {string:value}
1799
			FROM {db_prefix}members',
1800
			array(
1801
				'variable' => $variable,
1802
				'value' => $modSettings[$value[0]],
1803
				'db_error_skip' => true,
1804
			)
1805
		);
1806
1807
		$smcFunc['db_query']('', '
1808
			INSERT IGNORE INTO {db_prefix}themes
1809
				(id_member, id_theme, variable, value)
1810
			VALUES (-1, 1, {string:variable}, {string:value})',
1811
			array(
1812
				'variable' => $variable,
1813
				'value' => $modSettings[$value[0]],
1814
				'db_error_skip' => true,
1815
			)
1816
		);
1817
	}
1818
}
1819
1820
function php_version_check()
1821
{
1822
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
1823
}
1824
1825
function db_version_check()
1826
{
1827
	global $db_type, $databases;
1828
1829
	$curver = eval($databases[$db_type]['version_check']);
1830
	$curver = preg_replace('~\-.+?$~', '', $curver);
1831
1832
	return version_compare($databases[$db_type]['version'], $curver, '<=');
1833
}
1834
1835
function fixRelativePath($path)
1836
{
1837
	global $install_path;
1838
1839
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
1840
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
1841
}
1842
1843
function parse_sql($filename)
1844
{
1845
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
1846
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set;
1847
1848
/*
1849
	Failure allowed on:
1850
		- INSERT INTO but not INSERT IGNORE INTO.
1851
		- UPDATE IGNORE but not UPDATE.
1852
		- ALTER TABLE and ALTER IGNORE TABLE.
1853
		- DROP TABLE.
1854
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
1855
1856
	If a comment...
1857
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
1858
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
1859
		- is only ---#, it is "done." and then a break - only shown in debug.
1860
		- begins with ---{ it is a code block terminating at ---}.
1861
1862
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
1863
1864
	Replaces the following variables:
1865
		- {$boarddir}
1866
		- {$boardurl}
1867
		- {$db_prefix}
1868
		- {$db_collation}
1869
*/
1870
1871
	// May want to use extended functionality.
1872
	db_extend();
1873
	db_extend('packages');
1874
1875
	// Our custom error handler - does nothing but does stop public errors from XML!
1876
	set_error_handler(
1877
		function($errno, $errstr, $errfile, $errline) use ($support_js)
1878
		{
1879
			if ($support_js)
1880
				return true;
1881
			else
1882
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
1883
		}
1884
	);
1885
1886
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
1887
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
1888
	if ($db_type == 'mysql')
1889
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
1890
	else
1891
		$db_collation = '';
1892
1893
	$endl = $command_line ? "\n" : '<br>' . "\n";
1894
1895
	$lines = file($filename);
1896
1897
	$current_type = 'sql';
1898
	$current_data = '';
1899
	$substep = 0;
1900
	$last_step = '';
1901
1902
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
1903
	if (isset($db_character_set) && $db_character_set === 'utf8')
1904
		$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
1905
1906
	// Count the total number of steps within this file - for progress.
1907
	$file_steps = substr_count(implode('', $lines), '---#');
1908
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
1909
	$upcontext['debug_items'] = $file_steps;
1910
	$upcontext['current_item_num'] = 0;
1911
	$upcontext['current_item_name'] = '';
1912
	$upcontext['current_debug_item_num'] = 0;
1913
	$upcontext['current_debug_item_name'] = '';
1914
	// This array keeps a record of what we've done in case java is dead...
1915
	$upcontext['actioned_items'] = array();
1916
1917
	$done_something = false;
1918
1919
	foreach ($lines as $line_number => $line)
1920
	{
1921
		$do_current = $substep >= $_GET['substep'];
1922
1923
		// Get rid of any comments in the beginning of the line...
1924
		if (substr(trim($line), 0, 2) === '/*')
1925
			$line = preg_replace('~/\*.+?\*/~', '', $line);
1926
1927
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
1928
		if ($is_debug && !$support_js && $command_line)
1929
			flush();
1930
1931
		if (trim($line) === '')
1932
			continue;
1933
1934
		if (trim(substr($line, 0, 3)) === '---')
1935
		{
1936
			$type = substr($line, 3, 1);
1937
1938
			// An error??
1939
			if (trim($current_data) != '' && $type !== '}')
1940
			{
1941
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
1942
				if ($command_line)
1943
					echo $upcontext['error_message'];
1944
			}
1945
1946
			if ($type == ' ')
1947
			{
1948
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
1949
				{
1950
					echo ' Successful.', $endl;
1951
					flush();
1952
				}
1953
1954
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
1955
				$upcontext['current_item_num']++;
1956
				$upcontext['current_item_name'] = $last_step;
1957
1958
				if ($do_current)
1959
				{
1960
					$upcontext['actioned_items'][] = $last_step;
1961
					if ($command_line)
1962
						echo ' * ';
1963
1964
					// Starting a new main step in our DB changes, so it's time to reset this.
1965
					$upcontext['skip_db_substeps'] = false;
1966
				}
1967
			}
1968
			elseif ($type == '#')
1969
			{
1970
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
1971
1972
				$upcontext['current_debug_item_num']++;
1973
				if (trim($line) != '---#')
1974
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
1975
1976
				// Have we already done something?
1977
				if (isset($_GET['xml']) && $done_something)
1978
				{
1979
					restore_error_handler();
1980
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
1981
				}
1982
1983
				if ($do_current)
1984
				{
1985
					if (trim($line) == '---#' && $command_line)
1986
						echo ' done.', $endl;
1987
					elseif ($command_line)
1988
						echo ' +++ ', rtrim(substr($line, 4));
1989
					elseif (trim($line) != '---#')
1990
					{
1991
						if ($is_debug)
1992
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
1993
					}
1994
				}
1995
1996
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
1997
				{
1998
					if ($command_line)
1999
						echo ' * ';
2000
					else
2001
						$upcontext['actioned_items'][] = $last_step;
2002
				}
2003
2004
				// Small step - only if we're actually doing stuff.
2005
				if ($do_current)
2006
					nextSubstep(++$substep);
2007
				else
2008
					$substep++;
2009
			}
2010
			elseif ($type == '{')
2011
				$current_type = 'code';
2012
			elseif ($type == '}')
2013
			{
2014
				$current_type = 'sql';
2015
2016
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2017
				{
2018
					$current_data = '';
2019
2020
					// Avoid confusion when skipping something we normally would have done
2021
					if ($do_current)
2022
						$done_something = true;
2023
2024
					continue;
2025
				}
2026
2027
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2028
				if (eval('global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false)
2029
				{
2030
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2031
					if ($command_line)
2032
						echo $upcontext['error_message'];
2033
				}
2034
2035
				// Done with code!
2036
				$current_data = '';
2037
				$done_something = true;
2038
			}
2039
2040
			continue;
2041
		}
2042
2043
		$current_data .= $line;
2044
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2045
		{
2046
			if ((!$support_js || isset($_GET['xml'])))
2047
			{
2048
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2049
				{
2050
					$current_data = '';
2051
2052
					if ($do_current)
2053
						$done_something = true;
2054
2055
					continue;
2056
				}
2057
2058
				$current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $boarddir, '{$sboarddir}' => addslashes($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation));
2059
2060
				upgrade_query($current_data);
2061
2062
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2063
				/*
2064
				$result = $smcFunc['db_query']('', $current_data, false, false);
2065
				// Went wrong?
2066
				if (!$result)
2067
				{
2068
					// Bit of a bodge - do we want the error?
2069
					if (!empty($upcontext['return_error']))
2070
					{
2071
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2072
						return false;
2073
					}
2074
				}*/
2075
				$done_something = true;
2076
			}
2077
			$current_data = '';
2078
		}
2079
		// If this is xml based and we're just getting the item name then that's grand.
2080
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2081
		{
2082
			restore_error_handler();
2083
			return false;
2084
		}
2085
2086
		// Clean up by cleaning any step info.
2087
		$step_progress = array();
2088
		$custom_warning = '';
2089
	}
2090
2091
	// Put back the error handler.
2092
	restore_error_handler();
2093
2094
	if ($command_line)
2095
	{
2096
		echo ' Successful.' . "\n";
2097
		flush();
2098
	}
2099
2100
	$_GET['substep'] = 0;
2101
	return true;
2102
}
2103
2104
function upgrade_query($string, $unbuffered = false)
2105
{
2106
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2107
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2108
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2109
2110
	// Get the query result - working around some SMF specific security - just this once!
2111
	$modSettings['disableQueryCheck'] = true;
2112
	$db_unbuffered = $unbuffered;
2113
	$ignore_insert_error = false;
2114
2115
	// If we got an old pg version and use a insert ignore query
2116
	if ($db_type == 'postgresql' && !$smcFunc['db_native_replace']() && strpos($string, 'ON CONFLICT DO NOTHING') !== false)
2117
	{
2118
		$ignore_insert_error = true;
2119
		$string = str_replace('ON CONFLICT DO NOTHING', '', $string);
2120
	}
2121
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2122
	$db_unbuffered = false;
2123
2124
	// Failure?!
2125
	if ($result !== false)
2126
		return $result;
2127
2128
	$db_error_message = $smcFunc['db_error']($db_connection);
2129
	// If MySQL we do something more clever.
2130
	if ($db_type == 'mysql')
2131
	{
2132
		$mysqli_errno = mysqli_errno($db_connection);
2133
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR'));
2134
2135
		// Error numbers:
2136
		//    1016: Can't open file '....MYI'
2137
		//    1050: Table already exists.
2138
		//    1054: Unknown column name.
2139
		//    1060: Duplicate column name.
2140
		//    1061: Duplicate key name.
2141
		//    1062: Duplicate entry for unique key.
2142
		//    1068: Multiple primary keys.
2143
		//    1072: Key column '%s' doesn't exist in table.
2144
		//    1091: Can't drop key, doesn't exist.
2145
		//    1146: Table doesn't exist.
2146
		//    2013: Lost connection to server during query.
2147
2148
		if ($mysqli_errno == 1016)
2149
		{
2150
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2151
			{
2152
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2153
				$result = mysqli_query($db_connection, $string);
2154
				if ($result !== false)
2155
					return $result;
2156
			}
2157
		}
2158
		elseif ($mysqli_errno == 2013)
2159
		{
2160
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
2161
			mysqli_select_db($db_connection, $db_name);
0 ignored issues
show
It seems like $db_connection can also be of type false; however, parameter $link of mysqli_select_db() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

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

2161
			mysqli_select_db(/** @scrutinizer ignore-type */ $db_connection, $db_name);
Loading history...
2162
			if ($db_connection)
2163
			{
2164
				$result = mysqli_query($db_connection, $string);
2165
				if ($result !== false)
2166
					return $result;
2167
			}
2168
		}
2169
		// Duplicate column name... should be okay ;).
2170
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2171
			return false;
2172
		// Duplicate insert... make sure it's the proper type of query ;).
2173
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2174
			return false;
2175
		// Creating an index on a non-existent column.
2176
		elseif ($mysqli_errno == 1072)
2177
			return false;
2178
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2179
			return false;
2180
	}
2181
	// If a table already exists don't go potty.
2182
	else
2183
	{
2184
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2185
		{
2186
			if (strpos($db_error_message, 'exist') !== false)
2187
				return true;
2188
		}
2189
		elseif (strpos(trim($string), 'INSERT ') !== false)
2190
		{
2191
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2192
				return true;
2193
		}
2194
	}
2195
2196
	// Get the query string so we pass everything.
2197
	$query_string = '';
2198
	foreach ($_GET as $k => $v)
2199
		$query_string .= ';' . $k . '=' . $v;
2200
	if (strlen($query_string) != 0)
2201
		$query_string = '?' . substr($query_string, 1);
2202
2203
	if ($command_line)
2204
	{
2205
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2206
		die;
2207
	}
2208
2209
	// Bit of a bodge - do we want the error?
2210
	if (!empty($upcontext['return_error']))
2211
	{
2212
		$upcontext['error_message'] = $db_error_message;
2213
		$upcontext['error_string'] = $string;
2214
		return false;
2215
	}
2216
2217
	// Otherwise we have to display this somewhere appropriate if possible.
2218
	$upcontext['forced_error_message'] = '
2219
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2220
2221
			<div style="margin: 2ex;">
2222
				' . $txt['upgrade_thisquery'] . '
2223
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2224
2225
				' . $txt['upgrade_causerror'] . '
2226
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2227
			</div>
2228
2229
			<form action="' . $upgradeurl . $query_string . '" method="post">
2230
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2231
			</form>
2232
		</div>';
2233
2234
	upgradeExit();
2235
}
2236
2237
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2238
function protected_alter($change, $substep, $is_test = false)
2239
{
2240
	global $db_prefix, $smcFunc;
2241
2242
	db_extend('packages');
2243
2244
	// Firstly, check whether the current index/column exists.
2245
	$found = false;
2246
	if ($change['type'] === 'column')
2247
	{
2248
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2249
		foreach ($columns as $column)
2250
		{
2251
			// Found it?
2252
			if ($column['name'] === $change['name'])
2253
			{
2254
				$found |= true;
2255
				// Do some checks on the data if we have it set.
2256
				if (isset($change['col_type']))
2257
					$found &= $change['col_type'] === $column['type'];
2258
				if (isset($change['null_allowed']))
2259
					$found &= $column['null'] == $change['null_allowed'];
2260
				if (isset($change['default']))
2261
					$found &= $change['default'] === $column['default'];
2262
			}
2263
		}
2264
	}
2265
	elseif ($change['type'] === 'index')
2266
	{
2267
		$request = upgrade_query('
2268
			SHOW INDEX
2269
			FROM ' . $db_prefix . $change['table']);
2270
		if ($request !== false)
2271
		{
2272
			$cur_index = array();
2273
2274
			while ($row = $smcFunc['db_fetch_assoc']($request))
2275
				if ($row['Key_name'] === $change['name'])
2276
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2277
2278
			ksort($cur_index, SORT_NUMERIC);
2279
			$found = array_values($cur_index) === $change['target_columns'];
2280
2281
			$smcFunc['db_free_result']($request);
2282
		}
2283
	}
2284
2285
	// If we're trying to add and it's added, we're done.
2286
	if ($found && in_array($change['method'], array('add', 'change')))
2287
		return true;
2288
	// Otherwise if we're removing and it wasn't found we're also done.
2289
	elseif (!$found && in_array($change['method'], array('remove', 'change_remove')))
2290
		return true;
2291
	// Otherwise is it just a test?
2292
	elseif ($is_test)
2293
		return false;
2294
2295
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2296
	$running = false;
2297
	$found = false;
2298
	while (1 == 1)
2299
	{
2300
		$request = upgrade_query('
2301
			SHOW FULL PROCESSLIST');
2302
		while ($row = $smcFunc['db_fetch_assoc']($request))
2303
		{
2304
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2305
				$found = true;
2306
		}
2307
2308
		// Can't find it? Then we need to run it fools!
2309
		if (!$found && !$running)
2310
		{
2311
			$smcFunc['db_free_result']($request);
2312
2313
			$success = upgrade_query('
2314
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2315
				' . $change['text'], true) !== false;
2316
2317
			if (!$success)
2318
				return false;
2319
2320
			// Return
2321
			$running = true;
2322
		}
2323
		// What if we've not found it, but we'd ran it already? Must of completed.
2324
		elseif (!$found)
2325
		{
2326
			$smcFunc['db_free_result']($request);
2327
			return true;
2328
		}
2329
2330
		// Pause execution for a sec or three.
2331
		sleep(3);
2332
2333
		// Can never be too well protected.
2334
		nextSubstep($substep);
2335
	}
2336
2337
	// Protect it.
2338
	nextSubstep($substep);
2339
}
2340
2341
/**
2342
 * Alter a text column definition preserving its character set.
2343
 *
2344
 * @param array $change
2345
 * @param int $substep
2346
 */
2347
function textfield_alter($change, $substep)
2348
{
2349
	global $db_prefix, $smcFunc;
2350
2351
	$request = $smcFunc['db_query']('', '
2352
		SHOW FULL COLUMNS
2353
		FROM {db_prefix}' . $change['table'] . '
2354
		LIKE {string:column}',
2355
		array(
2356
			'column' => $change['column'],
2357
			'db_error_skip' => true,
2358
		)
2359
	);
2360
	if ($smcFunc['db_num_rows']($request) === 0)
2361
		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...
2362
	$table_row = $smcFunc['db_fetch_assoc']($request);
2363
	$smcFunc['db_free_result']($request);
2364
2365
	// If something of the current column definition is different, fix it.
2366
	$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']);
2367
2368
	// Columns that previously allowed null, need to be converted first.
2369
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2370
2371
	// Get the character set that goes with the collation of the column.
2372
	if ($column_fix && !empty($table_row['Collation']))
2373
	{
2374
		$request = $smcFunc['db_query']('', '
2375
			SHOW COLLATION
2376
			LIKE {string:collation}',
2377
			array(
2378
				'collation' => $table_row['Collation'],
2379
				'db_error_skip' => true,
2380
			)
2381
		);
2382
		// No results? Just forget it all together.
2383
		if ($smcFunc['db_num_rows']($request) === 0)
2384
			unset($table_row['Collation']);
2385
		else
2386
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2387
		$smcFunc['db_free_result']($request);
2388
	}
2389
2390
	if ($column_fix)
2391
	{
2392
		// Make sure there are no NULL's left.
2393
		if ($null_fix)
2394
			$smcFunc['db_query']('', '
2395
				UPDATE {db_prefix}' . $change['table'] . '
2396
				SET ' . $change['column'] . ' = {string:default}
2397
				WHERE ' . $change['column'] . ' IS NULL',
2398
				array(
2399
					'default' => isset($change['default']) ? $change['default'] : '',
2400
					'db_error_skip' => true,
2401
				)
2402
			);
2403
2404
		// Do the actual alteration.
2405
		$smcFunc['db_query']('', '
2406
			ALTER TABLE {db_prefix}' . $change['table'] . '
2407
			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}' : ''),
2408
			array(
2409
				'default' => isset($change['default']) ? $change['default'] : '',
2410
				'db_error_skip' => true,
2411
			)
2412
		);
2413
	}
2414
	nextSubstep($substep);
2415
}
2416
2417
// Check if we need to alter this query.
2418
function checkChange(&$change)
2419
{
2420
	global $smcFunc, $db_type, $databases;
2421
	static $database_version, $where_field_support;
2422
2423
	// Attempt to find a database_version.
2424
	if (empty($database_version))
2425
	{
2426
		$database_version = $databases[$db_type]['version_check'];
2427
		$where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version, '<=');
2428
	}
2429
2430
	// Not a column we need to check on?
2431
	if (!in_array($change['name'], array('memberGroups', 'passwordSalt')))
2432
		return;
2433
2434
	// Break it up you (six|seven).
2435
	$temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text']));
2436
2437
	// Can we support a shortcut method?
2438
	if ($where_field_support)
2439
	{
2440
		// Get the details about this change.
2441
		$request = $smcFunc['db_query']('', '
2442
			SHOW FIELDS
2443
			FROM {db_prefix}{raw:table}
2444
			WHERE Field = {string:old_name} OR Field = {string:new_name}',
2445
			array(
2446
				'table' => $change['table'],
2447
				'old_name' => $temp[1],
2448
				'new_name' => $temp[2],
2449
			)
2450
		);
2451
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2452
		if ($smcFunc['db_num_rows'] != 1)
2453
			return;
2454
2455
		list (, $current_type) = $smcFunc['db_fetch_assoc']($request);
2456
		$smcFunc['db_free_result']($request);
2457
	}
2458
	else
2459
	{
2460
		// Do this the old fashion, sure method way.
2461
		$request = $smcFunc['db_query']('', '
2462
			SHOW FIELDS
2463
			FROM {db_prefix}{raw:table}',
2464
			array(
2465
				'table' => $change['table'],
2466
			)
2467
		);
2468
		// Mayday!
2469
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2470
		if ($smcFunc['db_num_rows'] == 0)
2471
			return;
2472
2473
		// Oh where, oh where has my little field gone. Oh where can it be...
2474
		while ($row = $smcFunc['db_query']($request))
2475
			if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2])
2476
			{
2477
				$current_type = $row['Type'];
2478
				break;
2479
			}
2480
	}
2481
2482
	// If this doesn't match, the column may of been altered for a reason.
2483
	if (trim($current_type) != trim($temp[3]))
2484
		$temp[3] = $current_type;
2485
2486
	// Piece this back together.
2487
	$change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp));
2488
}
2489
2490
// The next substep.
2491
function nextSubstep($substep)
2492
{
2493
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2494
	global $step_progress, $is_debug, $upcontext;
2495
2496
	if ($_GET['substep'] < $substep)
2497
		$_GET['substep'] = $substep;
2498
2499
	if ($command_line)
2500
	{
2501
		if (time() - $start_time > 1 && empty($is_debug))
2502
		{
2503
			echo '.';
2504
			$start_time = time();
2505
		}
2506
		return;
2507
	}
2508
2509
	@set_time_limit(300);
2510
	if (function_exists('apache_reset_timeout'))
2511
		@apache_reset_timeout();
2512
2513
	if (time() - $start_time <= $timeLimitThreshold)
2514
		return;
2515
2516
	// Do we have some custom step progress stuff?
2517
	if (!empty($step_progress))
2518
	{
2519
		$upcontext['substep_progress'] = 0;
2520
		$upcontext['substep_progress_name'] = $step_progress['name'];
2521
		if ($step_progress['current'] > $step_progress['total'])
2522
			$upcontext['substep_progress'] = 99.9;
2523
		else
2524
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2525
2526
		// Make it nicely rounded.
2527
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2528
	}
2529
2530
	// If this is XML we just exit right away!
2531
	if (isset($_GET['xml']))
2532
		return upgradeExit();
2533
2534
	// We're going to pause after this!
2535
	$upcontext['pause'] = true;
2536
2537
	$upcontext['query_string'] = '';
2538
	foreach ($_GET as $k => $v)
2539
	{
2540
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2541
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2542
	}
2543
2544
	// Custom warning?
2545
	if (!empty($custom_warning))
2546
		$upcontext['custom_warning'] = $custom_warning;
2547
2548
	upgradeExit();
2549
}
2550
2551
function cmdStep0()
2552
{
2553
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2554
	global $is_debug;
2555
	$start_time = time();
2556
2557
	ob_end_clean();
2558
	ob_implicit_flush(1);
2559
	@set_time_limit(600);
2560
2561
	if (!isset($_SERVER['argv']))
2562
		$_SERVER['argv'] = array();
2563
	$_GET['maint'] = 1;
2564
2565
	foreach ($_SERVER['argv'] as $i => $arg)
2566
	{
2567
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2568
			$upcontext['lang'] = $match[1];
2569
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2570
			continue;
2571
		elseif ($arg == '--no-maintenance')
2572
			$_GET['maint'] = 0;
2573
		elseif ($arg == '--debug')
2574
			$is_debug = true;
2575
		elseif ($arg == '--backup')
2576
			$_POST['backup'] = 1;
2577
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2578
			$_GET['conv'] = 1;
2579
		elseif ($i != 0)
2580
		{
2581
			echo 'SMF Command-line Upgrader
2582
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2583
2584
	--language=LANG         Reset the forum\'s language to LANG.
2585
	--no-maintenance        Don\'t put the forum into maintenance mode.
2586
	--debug                 Output debugging information.
2587
	--backup                Create backups of tables with "backup_" prefix.';
2588
			echo "\n";
2589
			exit;
2590
		}
2591
	}
2592
2593
	if (!php_version_check())
2594
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
2595
	if (!db_version_check())
2596
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2597
2598
	// Do some checks to make sure they have proper privileges
2599
	db_extend('packages');
2600
2601
	// CREATE
2602
	$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');
2603
2604
	// ALTER
2605
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2606
2607
	// DROP
2608
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2609
2610
	// Sorry... we need CREATE, ALTER and DROP
2611
	if (!$create || !$alter || !$drop)
2612
		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);
2613
2614
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2615
		&& @file_exists($sourcedir . '/QueryString.php')
2616
		&& @file_exists($sourcedir . '/ManageBoards.php');
2617
	if (!$check && !isset($modSettings['smfVersion']))
2618
		print_error('Error: Some files are missing or out-of-date.', true);
2619
2620
	// Do a quick version spot check.
2621
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
2622
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2623
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2624
		print_error('Error: Some files have not yet been updated properly.');
2625
2626
	// Make sure Settings.php is writable.
2627
	quickFileWritable($boarddir . '/Settings.php');
2628
	if (!is_writable($boarddir . '/Settings.php'))
2629
		print_error('Error: Unable to obtain write access to "Settings.php".', true);
2630
2631
	// Make sure Settings_bak.php is writable.
2632
	quickFileWritable($boarddir . '/Settings_bak.php');
2633
	if (!is_writable($boarddir . '/Settings_bak.php'))
2634
		print_error('Error: Unable to obtain write access to "Settings_bak.php".');
2635
2636
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
2637
		print_error('Error: Unable to obtain write access to "agreement.txt".');
2638
	elseif (isset($modSettings['agreement']))
2639
	{
2640
		$fp = fopen($boarddir . '/agreement.txt', 'w');
2641
		fwrite($fp, $modSettings['agreement']);
2642
		fclose($fp);
2643
	}
2644
2645
	// Make sure Themes is writable.
2646
	quickFileWritable($modSettings['theme_dir']);
2647
2648
	if (!is_writable($modSettings['theme_dir']) && !isset($modSettings['smfVersion']))
2649
		print_error('Error: Unable to obtain write access to "Themes".');
2650
2651
	// Make sure cache directory exists and is writable!
2652
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
2653
	if (!file_exists($cachedir_temp))
2654
		@mkdir($cachedir_temp);
2655
2656
	// Make sure the cache temp dir is writable.
2657
	quickFileWritable($cachedir_temp);
2658
2659
	if (!is_writable($cachedir_temp))
2660
		print_error('Error: Unable to obtain write access to "cache".', true);
2661
2662
	// Make sure db_last_error.php is writable.
2663
	quickFileWritable($cachedir_temp . '/db_last_error.php');
2664
	if (!is_writable($cachedir_temp . '/db_last_error.php'))
2665
		print_error('Error: Unable to obtain write access to "db_last_error.php".');
2666
2667
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
2668
		print_error('Error: Unable to find language files!', true);
2669
	else
2670
	{
2671
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
2672
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
2673
2674
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
2675
			print_error('Error: Language files out of date.', true);
2676
		if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'))
2677
			print_error('Error: Install language is missing for selected language.', true);
2678
2679
		// Otherwise include it!
2680
		require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php');
2681
	}
2682
2683
	// Make sure we skip the HTML for login.
2684
	$_POST['upcont'] = true;
2685
	$upcontext['current_step'] = 1;
2686
}
2687
2688
/**
2689
 * Handles converting your database to UTF-8
2690
 */
2691
function ConvertUtf8()
2692
{
2693
	global $upcontext, $db_character_set, $sourcedir, $smcFunc, $modSettings, $language;
2694
	global $db_prefix, $db_type, $command_line, $support_js, $txt;
2695
2696
	// Done it already?
2697
	if (!empty($_POST['utf8_done']))
2698
		return true;
2699
2700
	// First make sure they aren't already on UTF-8 before we go anywhere...
2701
	if ($db_type == 'postgresql' || ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8'))
2702
	{
2703
		$smcFunc['db_insert']('replace',
2704
			'{db_prefix}settings',
2705
			array('variable' => 'string', 'value' => 'string'),
2706
			array(array('global_character_set', 'UTF-8')),
2707
			array('variable')
2708
		);
2709
2710
		return true;
2711
	}
2712
	else
2713
	{
2714
		$upcontext['page_title'] = $txt['converting_utf8'];
2715
		$upcontext['sub_template'] = isset($_GET['xml']) ? 'convert_xml' : 'convert_utf8';
2716
2717
		// The character sets used in SMF's language files with their db equivalent.
2718
		$charsets = array(
2719
			// Armenian
2720
			'armscii8' => 'armscii8',
2721
			// Chinese-traditional.
2722
			'big5' => 'big5',
2723
			// Chinese-simplified.
2724
			'gbk' => 'gbk',
2725
			// West European.
2726
			'ISO-8859-1' => 'latin1',
2727
			// Romanian.
2728
			'ISO-8859-2' => 'latin2',
2729
			// Turkish.
2730
			'ISO-8859-9' => 'latin5',
2731
			// Latvian
2732
			'ISO-8859-13' => 'latin7',
2733
			// West European with Euro sign.
2734
			'ISO-8859-15' => 'latin9',
2735
			// Thai.
2736
			'tis-620' => 'tis620',
2737
			// Persian, Chinese, etc.
2738
			'UTF-8' => 'utf8',
2739
			// Russian.
2740
			'windows-1251' => 'cp1251',
2741
			// Greek.
2742
			'windows-1253' => 'utf8',
2743
			// Hebrew.
2744
			'windows-1255' => 'utf8',
2745
			// Arabic.
2746
			'windows-1256' => 'cp1256',
2747
		);
2748
2749
		// Get a list of character sets supported by your MySQL server.
2750
		$request = $smcFunc['db_query']('', '
2751
			SHOW CHARACTER SET',
2752
			array(
2753
			)
2754
		);
2755
		$db_charsets = array();
2756
		while ($row = $smcFunc['db_fetch_assoc']($request))
2757
			$db_charsets[] = $row['Charset'];
2758
2759
		$smcFunc['db_free_result']($request);
2760
2761
		// Character sets supported by both MySQL and SMF's language files.
2762
		$charsets = array_intersect($charsets, $db_charsets);
2763
2764
		// Use the messages.body column as indicator for the database charset.
2765
		$request = $smcFunc['db_query']('', '
2766
			SHOW FULL COLUMNS
2767
			FROM {db_prefix}messages
2768
			LIKE {string:body_like}',
2769
			array(
2770
				'body_like' => 'body',
2771
			)
2772
		);
2773
		$column_info = $smcFunc['db_fetch_assoc']($request);
2774
		$smcFunc['db_free_result']($request);
2775
2776
		// A collation looks like latin1_swedish. We only need the character set.
2777
		list($upcontext['database_charset']) = explode('_', $column_info['Collation']);
2778
		$upcontext['database_charset'] = in_array($upcontext['database_charset'], $charsets) ? array_search($upcontext['database_charset'], $charsets) : $upcontext['database_charset'];
2779
2780
		// Detect whether a fulltext index is set.
2781
		$request = $smcFunc['db_query']('', '
2782
			SHOW INDEX
2783
			FROM {db_prefix}messages',
2784
			array(
2785
			)
2786
		);
2787
2788
		$upcontext['dropping_index'] = false;
2789
2790
		// If there's a fulltext index, we need to drop it first...
2791
		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
2792
		{
2793
			while ($row = $smcFunc['db_fetch_assoc']($request))
2794
				if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
2795
					$upcontext['fulltext_index'][] = $row['Key_name'];
2796
			$smcFunc['db_free_result']($request);
2797
2798
			if (isset($upcontext['fulltext_index']))
2799
				$upcontext['fulltext_index'] = array_unique($upcontext['fulltext_index']);
2800
		}
2801
2802
		// Drop it and make a note...
2803
		if (!empty($upcontext['fulltext_index']))
2804
		{
2805
			$upcontext['dropping_index'] = true;
2806
2807
			$smcFunc['db_query']('', '
2808
				ALTER TABLE {db_prefix}messages
2809
				DROP INDEX ' . implode(',
2810
				DROP INDEX ', $upcontext['fulltext_index']),
2811
				array(
2812
					'db_error_skip' => true,
2813
				)
2814
			);
2815
2816
			// Update the settings table
2817
			$smcFunc['db_insert']('replace',
2818
				'{db_prefix}settings',
2819
				array('variable' => 'string', 'value' => 'string'),
2820
				array('db_search_index', ''),
2821
				array('variable')
2822
			);
2823
		}
2824
2825
		// Figure out what charset we should be converting from...
2826
		$lang_charsets = array(
2827
			'arabic' => 'windows-1256',
2828
			'armenian_east' => 'armscii-8',
2829
			'armenian_west' => 'armscii-8',
2830
			'azerbaijani_latin' => 'ISO-8859-9',
2831
			'bangla' => 'UTF-8',
2832
			'belarusian' => 'ISO-8859-5',
2833
			'bulgarian' => 'windows-1251',
2834
			'cambodian' => 'UTF-8',
2835
			'chinese_simplified' => 'gbk',
2836
			'chinese_traditional' => 'big5',
2837
			'croation' => 'ISO-8859-2',
2838
			'czech' => 'ISO-8859-2',
2839
			'czech_informal' => 'ISO-8859-2',
2840
			'english_pirate' => 'UTF-8',
2841
			'esperanto' => 'ISO-8859-3',
2842
			'estonian' => 'ISO-8859-15',
2843
			'filipino_tagalog' => 'UTF-8',
2844
			'filipino_vasayan' => 'UTF-8',
2845
			'georgian' => 'UTF-8',
2846
			'greek' => 'ISO-8859-3',
2847
			'hebrew' => 'windows-1255',
2848
			'hungarian' => 'ISO-8859-2',
2849
			'irish' => 'UTF-8',
2850
			'japanese' => 'UTF-8',
2851
			'khmer' => 'UTF-8',
2852
			'korean' => 'UTF-8',
2853
			'kurdish_kurmanji' => 'ISO-8859-9',
2854
			'kurdish_sorani' => 'windows-1256',
2855
			'lao' => 'tis-620',
2856
			'latvian' => 'ISO-8859-13',
2857
			'lithuanian' => 'ISO-8859-4',
2858
			'macedonian' => 'UTF-8',
2859
			'malayalam' => 'UTF-8',
2860
			'mongolian' => 'UTF-8',
2861
			'nepali' => 'UTF-8',
2862
			'persian' => 'UTF-8',
2863
			'polish' => 'ISO-8859-2',
2864
			'romanian' => 'ISO-8859-2',
2865
			'russian' => 'windows-1252',
2866
			'sakha' => 'UTF-8',
2867
			'serbian_cyrillic' => 'ISO-8859-5',
2868
			'serbian_latin' => 'ISO-8859-2',
2869
			'sinhala' => 'UTF-8',
2870
			'slovak' => 'ISO-8859-2',
2871
			'slovenian' => 'ISO-8859-2',
2872
			'telugu' => 'UTF-8',
2873
			'thai' => 'tis-620',
2874
			'turkish' => 'ISO-8859-9',
2875
			'turkmen' => 'ISO-8859-9',
2876
			'ukranian' => 'windows-1251',
2877
			'urdu' => 'UTF-8',
2878
			'uzbek_cyrillic' => 'ISO-8859-5',
2879
			'uzbek_latin' => 'ISO-8859-5',
2880
			'vietnamese' => 'UTF-8',
2881
			'yoruba' => 'UTF-8'
2882
		);
2883
2884
		// Default to ISO-8859-1 unless we detected another supported charset
2885
		$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';
2886
2887
		$upcontext['charset_list'] = array_keys($charsets);
2888
2889
		// Translation table for the character sets not native for MySQL.
2890
		$translation_tables = array(
2891
			'windows-1255' => array(
2892
				'0x81' => '\'\'',		'0x8A' => '\'\'',		'0x8C' => '\'\'',
2893
				'0x8D' => '\'\'',		'0x8E' => '\'\'',		'0x8F' => '\'\'',
2894
				'0x90' => '\'\'',		'0x9A' => '\'\'',		'0x9C' => '\'\'',
2895
				'0x9D' => '\'\'',		'0x9E' => '\'\'',		'0x9F' => '\'\'',
2896
				'0xCA' => '\'\'',		'0xD9' => '\'\'',		'0xDA' => '\'\'',
2897
				'0xDB' => '\'\'',		'0xDC' => '\'\'',		'0xDD' => '\'\'',
2898
				'0xDE' => '\'\'',		'0xDF' => '\'\'',		'0xFB' => '0xD792',
2899
				'0xFC' => '0xE282AC',		'0xFF' => '0xD6B2',		'0xC2' => '0xFF',
2900
				'0x80' => '0xFC',		'0xE2' => '0xFB',		'0xA0' => '0xC2A0',
2901
				'0xA1' => '0xC2A1',		'0xA2' => '0xC2A2',		'0xA3' => '0xC2A3',
2902
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
2903
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
2904
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
2905
				'0xAF' => '0xC2AF',		'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',
2906
				'0xB2' => '0xC2B2',		'0xB3' => '0xC2B3',		'0xB4' => '0xC2B4',
2907
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
2908
				'0xB8' => '0xC2B8',		'0xB9' => '0xC2B9',		'0xBB' => '0xC2BB',
2909
				'0xBC' => '0xC2BC',		'0xBD' => '0xC2BD',		'0xBE' => '0xC2BE',
2910
				'0xBF' => '0xC2BF',		'0xD7' => '0xD7B3',		'0xD1' => '0xD781',
2911
				'0xD4' => '0xD7B0',		'0xD5' => '0xD7B1',		'0xD6' => '0xD7B2',
2912
				'0xE0' => '0xD790',		'0xEA' => '0xD79A',		'0xEC' => '0xD79C',
2913
				'0xED' => '0xD79D',		'0xEE' => '0xD79E',		'0xEF' => '0xD79F',
2914
				'0xF0' => '0xD7A0',		'0xF1' => '0xD7A1',		'0xF2' => '0xD7A2',
2915
				'0xF3' => '0xD7A3',		'0xF5' => '0xD7A5',		'0xF6' => '0xD7A6',
2916
				'0xF7' => '0xD7A7',		'0xF8' => '0xD7A8',		'0xF9' => '0xD7A9',
2917
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
2918
				'0x86' => '0xE280A0',	'0x87' => '0xE280A1',	'0x89' => '0xE280B0',
2919
				'0x8B' => '0xE280B9',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
2920
				'0x95' => '0xE280A2',	'0x97' => '0xE28094',	'0x99' => '0xE284A2',
2921
				'0xC0' => '0xD6B0',		'0xC1' => '0xD6B1',		'0xC3' => '0xD6B3',
2922
				'0xC4' => '0xD6B4',		'0xC5' => '0xD6B5',		'0xC6' => '0xD6B6',
2923
				'0xC7' => '0xD6B7',		'0xC8' => '0xD6B8',		'0xC9' => '0xD6B9',
2924
				'0xCB' => '0xD6BB',		'0xCC' => '0xD6BC',		'0xCD' => '0xD6BD',
2925
				'0xCE' => '0xD6BE',		'0xCF' => '0xD6BF',		'0xD0' => '0xD780',
2926
				'0xD2' => '0xD782',		'0xE3' => '0xD793',		'0xE4' => '0xD794',
2927
				'0xE5' => '0xD795',		'0xE7' => '0xD797',		'0xE9' => '0xD799',
2928
				'0xFD' => '0xE2808E',	'0xFE' => '0xE2808F',	'0x92' => '0xE28099',
2929
				'0x83' => '0xC692',		'0xD3' => '0xD783',		'0x88' => '0xCB86',
2930
				'0x98' => '0xCB9C',		'0x91' => '0xE28098',	'0x96' => '0xE28093',
2931
				'0xBA' => '0xC3B7',		'0x9B' => '0xE280BA',	'0xAA' => '0xC397',
2932
				'0xA4' => '0xE282AA',	'0xE1' => '0xD791',		'0xE6' => '0xD796',
2933
				'0xE8' => '0xD798',		'0xEB' => '0xD79B',		'0xF4' => '0xD7A4',
2934
				'0xFA' => '0xD7AA',
2935
			),
2936
			'windows-1253' => array(
2937
				'0x81' => '\'\'',			'0x88' => '\'\'',			'0x8A' => '\'\'',
2938
				'0x8C' => '\'\'',			'0x8D' => '\'\'',			'0x8E' => '\'\'',
2939
				'0x8F' => '\'\'',			'0x90' => '\'\'',			'0x98' => '\'\'',
2940
				'0x9A' => '\'\'',			'0x9C' => '\'\'',			'0x9D' => '\'\'',
2941
				'0x9E' => '\'\'',			'0x9F' => '\'\'',			'0xAA' => '\'\'',
2942
				'0xD2' => '0xE282AC',			'0xFF' => '0xCE92',			'0xCE' => '0xCE9E',
2943
				'0xB8' => '0xCE88',		'0xBA' => '0xCE8A',		'0xBC' => '0xCE8C',
2944
				'0xBE' => '0xCE8E',		'0xBF' => '0xCE8F',		'0xC0' => '0xCE90',
2945
				'0xC8' => '0xCE98',		'0xCA' => '0xCE9A',		'0xCC' => '0xCE9C',
2946
				'0xCD' => '0xCE9D',		'0xCF' => '0xCE9F',		'0xDA' => '0xCEAA',
2947
				'0xE8' => '0xCEB8',		'0xEA' => '0xCEBA',		'0xEC' => '0xCEBC',
2948
				'0xEE' => '0xCEBE',		'0xEF' => '0xCEBF',		'0xC2' => '0xFF',
2949
				'0xBD' => '0xC2BD',		'0xED' => '0xCEBD',		'0xB2' => '0xC2B2',
2950
				'0xA0' => '0xC2A0',		'0xA3' => '0xC2A3',		'0xA4' => '0xC2A4',
2951
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
2952
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
2953
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
2954
				'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',		'0xB3' => '0xC2B3',
2955
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
2956
				'0xBB' => '0xC2BB',		'0xE2' => '0xCEB2',		'0x80' => '0xD2',
2957
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
2958
				'0x86' => '0xE280A0',	'0xA1' => '0xCE85',		'0xA2' => '0xCE86',
2959
				'0x87' => '0xE280A1',	'0x89' => '0xE280B0',	'0xB9' => '0xCE89',
2960
				'0x8B' => '0xE280B9',	'0x91' => '0xE28098',	'0x99' => '0xE284A2',
2961
				'0x92' => '0xE28099',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
2962
				'0x95' => '0xE280A2',	'0x96' => '0xE28093',	'0x97' => '0xE28094',
2963
				'0x9B' => '0xE280BA',	'0xAF' => '0xE28095',	'0xB4' => '0xCE84',
2964
				'0xC1' => '0xCE91',		'0xC3' => '0xCE93',		'0xC4' => '0xCE94',
2965
				'0xC5' => '0xCE95',		'0xC6' => '0xCE96',		'0x83' => '0xC692',
2966
				'0xC7' => '0xCE97',		'0xC9' => '0xCE99',		'0xCB' => '0xCE9B',
2967
				'0xD0' => '0xCEA0',		'0xD1' => '0xCEA1',		'0xD3' => '0xCEA3',
2968
				'0xD4' => '0xCEA4',		'0xD5' => '0xCEA5',		'0xD6' => '0xCEA6',
2969
				'0xD7' => '0xCEA7',		'0xD8' => '0xCEA8',		'0xD9' => '0xCEA9',
2970
				'0xDB' => '0xCEAB',		'0xDC' => '0xCEAC',		'0xDD' => '0xCEAD',
2971
				'0xDE' => '0xCEAE',		'0xDF' => '0xCEAF',		'0xE0' => '0xCEB0',
2972
				'0xE1' => '0xCEB1',		'0xE3' => '0xCEB3',		'0xE4' => '0xCEB4',
2973
				'0xE5' => '0xCEB5',		'0xE6' => '0xCEB6',		'0xE7' => '0xCEB7',
2974
				'0xE9' => '0xCEB9',		'0xEB' => '0xCEBB',		'0xF0' => '0xCF80',
2975
				'0xF1' => '0xCF81',		'0xF2' => '0xCF82',		'0xF3' => '0xCF83',
2976
				'0xF4' => '0xCF84',		'0xF5' => '0xCF85',		'0xF6' => '0xCF86',
2977
				'0xF7' => '0xCF87',		'0xF8' => '0xCF88',		'0xF9' => '0xCF89',
2978
				'0xFA' => '0xCF8A',		'0xFB' => '0xCF8B',		'0xFC' => '0xCF8C',
2979
				'0xFD' => '0xCF8D',		'0xFE' => '0xCF8E',
2980
			),
2981
		);
2982
2983
		// Make some preparations.
2984
		if (isset($translation_tables[$upcontext['charset_detected']]))
2985
		{
2986
			$replace = '%field%';
2987
2988
			// Build a huge REPLACE statement...
2989
			foreach ($translation_tables[$upcontext['charset_detected']] as $from => $to)
2990
				$replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')';
2991
		}
2992
2993
		// Get a list of table names ahead of time... This makes it easier to set our substep and such
2994
		db_extend();
2995
		$queryTables = $smcFunc['db_list_tables'](false, $db_prefix . '%');
2996
2997
		$upcontext['table_count'] = count($queryTables);
2998
2999
		// What ones have we already done?
3000
		foreach ($queryTables as $id => $table)
3001
			if ($id < $_GET['substep'])
3002
				$upcontext['previous_tables'][] = $table;
3003
3004
		$upcontext['cur_table_num'] = $_GET['substep'];
3005
		$upcontext['cur_table_name'] = str_replace($db_prefix, '', $queryTables[$_GET['substep']]);
3006
		$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3007
3008
		// Make sure we're ready & have painted the template before proceeding
3009
		if ($support_js && !isset($_GET['xml']))
3010
		{
3011
			$_GET['substep'] = 0;
3012
			return false;
3013
		}
3014
3015
		// We want to start at the first table.
3016
		for ($substep = $_GET['substep'], $n = count($queryTables); $substep < $n; $substep++)
3017
		{
3018
			$table = $queryTables[$substep];
3019
3020
			$getTableStatus = $smcFunc['db_query']('', '
3021
				SHOW TABLE STATUS
3022
				LIKE {string:table_name}',
3023
				array(
3024
					'table_name' => str_replace('_', '\_', $table)
3025
				)
3026
			);
3027
3028
			// Only one row so we can just fetch_assoc and free the result...
3029
			$table_info = $smcFunc['db_fetch_assoc']($getTableStatus);
3030
			$smcFunc['db_free_result']($getTableStatus);
3031
3032
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($queryTables[$substep + 1]) ? $queryTables[$substep + 1] : $queryTables[$substep]));
3033
			$upcontext['cur_table_num'] = $substep + 1;
3034
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3035
3036
			// Do we need to pause?
3037
			nextSubstep($substep);
3038
3039
			// Just to make sure it doesn't time out.
3040
			if (function_exists('apache_reset_timeout'))
3041
				@apache_reset_timeout();
3042
3043
			$table_charsets = array();
3044
3045
			// Loop through each column.
3046
			$queryColumns = $smcFunc['db_query']('', '
3047
				SHOW FULL COLUMNS
3048
				FROM ' . $table_info['Name'],
3049
				array(
3050
				)
3051
			);
3052
			while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns))
3053
			{
3054
				// Only text'ish columns have a character set and need converting.
3055
				if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
3056
				{
3057
					$collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation'];
3058
					if (!empty($collation) && $collation !== 'NULL')
3059
					{
3060
						list($charset) = explode('_', $collation);
3061
3062
						// Build structure of columns to operate on organized by charset; only operate on columns not yet utf8
3063
						if ($charset != 'utf8')
3064
						{
3065
							if (!isset($table_charsets[$charset]))
3066
								$table_charsets[$charset] = array();
3067
3068
							$table_charsets[$charset][] = $column_info;
3069
						}
3070
					}
3071
				}
3072
			}
3073
			$smcFunc['db_free_result']($queryColumns);
3074
3075
			// Only change the non-utf8 columns identified above
3076
			if (count($table_charsets) > 0)
3077
			{
3078
				$updates_blob = '';
3079
				$updates_text = '';
3080
				foreach ($table_charsets as $charset => $columns)
3081
				{
3082
					if ($charset !== $charsets[$upcontext['charset_detected']])
3083
					{
3084
						foreach ($columns as $column)
3085
						{
3086
							$updates_blob .= '
3087
								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'] . '\'') . ',';
3088
							$updates_text .= '
3089
								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'] . '\'') . ',';
3090
						}
3091
					}
3092
				}
3093
3094
				// Change the columns to binary form.
3095
				$smcFunc['db_query']('', '
3096
					ALTER TABLE {raw:table_name}{raw:updates_blob}',
3097
					array(
3098
						'table_name' => $table_info['Name'],
3099
						'updates_blob' => substr($updates_blob, 0, -1),
3100
					)
3101
				);
3102
3103
				// Convert the character set if MySQL has no native support for it.
3104
				if (isset($translation_tables[$upcontext['charset_detected']]))
3105
				{
3106
					$update = '';
3107
					foreach ($table_charsets as $charset => $columns)
3108
						foreach ($columns as $column)
3109
							$update .= '
3110
								' . $column['Field'] . ' = ' . strtr($replace, array('%field%' => $column['Field'])) . ',';
3111
3112
					$smcFunc['db_query']('', '
3113
						UPDATE {raw:table_name}
3114
						SET {raw:updates}',
3115
						array(
3116
							'table_name' => $table_info['Name'],
3117
							'updates' => substr($update, 0, -1),
3118
						)
3119
					);
3120
				}
3121
3122
				// Change the columns back, but with the proper character set.
3123
				$smcFunc['db_query']('', '
3124
					ALTER TABLE {raw:table_name}{raw:updates_text}',
3125
					array(
3126
						'table_name' => $table_info['Name'],
3127
						'updates_text' => substr($updates_text, 0, -1),
3128
					)
3129
				);
3130
			}
3131
3132
			// Now do the actual conversion (if still needed).
3133
			if ($charsets[$upcontext['charset_detected']] !== 'utf8')
3134
			{
3135
				if ($command_line)
3136
					echo 'Converting table ' . $table_info['Name'] . ' to UTF-8...';
3137
3138
				$smcFunc['db_query']('', '
3139
					ALTER TABLE {raw:table_name}
3140
					CONVERT TO CHARACTER SET utf8',
3141
					array(
3142
						'table_name' => $table_info['Name'],
3143
					)
3144
				);
3145
3146
				if ($command_line)
3147
					echo " done.\n";
3148
			}
3149
			// If this is XML to keep it nice for the user do one table at a time anyway!
3150
			if (isset($_GET['xml']) && $upcontext['cur_table_num'] < $upcontext['table_count'])
3151
				return upgradeExit();
3152
		}
3153
3154
		$prev_charset = empty($translation_tables[$upcontext['charset_detected']]) ? $charsets[$upcontext['charset_detected']] : $translation_tables[$upcontext['charset_detected']];
3155
3156
		$smcFunc['db_insert']('replace',
3157
			'{db_prefix}settings',
3158
			array('variable' => 'string', 'value' => 'string'),
3159
			array(array('global_character_set', 'UTF-8'), array('previousCharacterSet', $prev_charset)),
3160
			array('variable')
3161
		);
3162
3163
		// Store it in Settings.php too because it's needed before db connection.
3164
		// Hopefully this works...
3165
		require_once($sourcedir . '/Subs-Admin.php');
3166
		updateSettingsFile(array('db_character_set' => '\'utf8\''));
3167
3168
		// The conversion might have messed up some serialized strings. Fix them!
3169
		$request = $smcFunc['db_query']('', '
3170
			SELECT id_action, extra
3171
			FROM {db_prefix}log_actions
3172
			WHERE action IN ({string:remove}, {string:delete})',
3173
			array(
3174
				'remove' => 'remove',
3175
				'delete' => 'delete',
3176
			)
3177
		);
3178
		while ($row = $smcFunc['db_fetch_assoc']($request))
3179
		{
3180
			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)
3181
				$smcFunc['db_query']('', '
3182
					UPDATE {db_prefix}log_actions
3183
					SET extra = {string:extra}
3184
					WHERE id_action = {int:current_action}',
3185
					array(
3186
						'current_action' => $row['id_action'],
3187
						'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4],
3188
					)
3189
				);
3190
		}
3191
		$smcFunc['db_free_result']($request);
3192
3193
		if ($upcontext['dropping_index'] && $command_line)
3194
		{
3195
			echo "\n" . '', $txt['upgrade_fulltext_error'], '';
3196
			flush();
3197
		}
3198
	}
3199
	$_GET['substep'] = 0;
3200
	return false;
3201
}
3202
3203
/**
3204
 * Attempts to repair corrupted serialized data strings
3205
 *
3206
 * @param string $string Serialized data that has been corrupted
3207
 * @return string|bool A working version of the serialized data, or the original if the repair failed
3208
 */
3209
function fix_serialized_data($string)
3210
{
3211
	// If its not broken, don't fix it.
3212
	if (!is_string($string) || !preg_match('/^[bidsa]:/', $string) || @safe_unserialize($string) !== false)
3213
		return $string;
3214
3215
	// This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8)
3216
	$new_string = preg_replace_callback('~\bs:(\d+):"(.*?)";(?=$|[bidsa]:|[{}]|N;)~s', function ($matches) {return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';}, $string);
3217
3218
	// @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc.
3219
3220
	// Did it work?
3221
	if (@safe_unserialize($new_string) !== false)
3222
		return $new_string;
3223
	else
3224
		return $string;
3225
}
3226
3227
function serialize_to_json()
3228
{
3229
	global $command_line, $smcFunc, $modSettings, $sourcedir, $upcontext, $support_js, $txt;
3230
3231
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json';
3232
	// First thing's first - did we already do this?
3233
	if (!empty($modSettings['json_done']))
3234
	{
3235
		if ($command_line)
3236
			return ConvertUtf8();
3237
		else
3238
			return true;
3239
	}
3240
3241
	// Done it already - js wise?
3242
	if (!empty($_POST['json_done']))
3243
		return true;
3244
3245
	// List of tables affected by this function
3246
	// name => array('key', col1[,col2|true[,col3]])
3247
	// If 3rd item in array is true, it indicates that col1 could be empty...
3248
	$tables = array(
3249
		'background_tasks' => array('id_task', 'task_data'),
3250
		'log_actions' => array('id_action', 'extra'),
3251
		'log_online' => array('session', 'url'),
3252
		'log_packages' => array('id_install', 'db_changes', 'failed_steps', 'credits'),
3253
		'log_spider_hits' => array('id_hit', 'url'),
3254
		'log_subscribed' => array('id_sublog', 'pending_details'),
3255
		'pm_rules' => array('id_rule', 'criteria', 'actions'),
3256
		'qanda' => array('id_question', 'answers'),
3257
		'subscriptions' => array('id_subscribe', 'cost'),
3258
		'user_alerts' => array('id_alert', 'extra', true),
3259
		'user_drafts' => array('id_draft', 'to_list', true),
3260
		// These last two are a bit different - we'll handle those separately
3261
		'settings' => array(),
3262
		'themes' => array()
3263
	);
3264
3265
	// Set up some context stuff...
3266
	// Because we're not using numeric indices, we need this to figure out the current table name...
3267
	$keys = array_keys($tables);
3268
3269
	$upcontext['page_title'] = $txt['converting_json'];
3270
	$upcontext['table_count'] = count($keys);
3271
	$upcontext['cur_table_num'] = $_GET['substep'];
3272
	$upcontext['cur_table_name'] = isset($keys[$_GET['substep']]) ? $keys[$_GET['substep']] : $keys[0];
3273
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3274
3275
	foreach ($keys as $id => $table)
3276
		if ($id < $_GET['substep'])
3277
			$upcontext['previous_tables'][] = $table;
3278
3279
	if ($command_line)
3280
		echo 'Converting data from serialize() to json_encode().';
3281
3282
	if (!$support_js || isset($_GET['xml']))
3283
	{
3284
		// Fix the data in each table
3285
		for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++)
3286
		{
3287
			$upcontext['cur_table_name'] = isset($keys[$substep + 1]) ? $keys[$substep + 1] : $keys[$substep];
3288
			$upcontext['cur_table_num'] = $substep + 1;
3289
3290
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3291
3292
			// Do we need to pause?
3293
			nextSubstep($substep);
3294
3295
			// Initialize a few things...
3296
			$where = '';
3297
			$vars = array();
3298
			$table = $keys[$substep];
3299
			$info = $tables[$table];
3300
3301
			// Now the fun - build our queries and all that fun stuff
3302
			if ($table == 'settings')
3303
			{
3304
				// Now a few settings...
3305
				$serialized_settings = array(
3306
					'attachment_basedirectories',
3307
					'attachmentUploadDir',
3308
					'cal_today_birthday',
3309
					'cal_today_event',
3310
					'cal_today_holiday',
3311
					'displayFields',
3312
					'last_attachments_directory',
3313
					'memberlist_cache',
3314
					'search_custom_index_config',
3315
					'spider_name_cache'
3316
				);
3317
3318
				// Loop through and fix these...
3319
				$new_settings = array();
3320
				if ($command_line)
3321
					echo "\n" . 'Fixing some settings...';
3322
3323
				foreach ($serialized_settings as $var)
3324
				{
3325
					if (isset($modSettings[$var]))
3326
					{
3327
						// Attempt to unserialize the setting
3328
						$temp = @safe_unserialize($modSettings[$var]);
3329
						// Maybe conversion to UTF-8 corrupted it
3330
						if ($temp === false)
3331
							$temp = @safe_unserialize(fix_serialized_data($modSettings[$var]));
3332
3333
						if (!$temp && $command_line)
3334
							echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping.";
3335
						elseif ($temp !== false)
3336
							$new_settings[$var] = json_encode($temp);
3337
					}
3338
				}
3339
3340
				// Update everything at once
3341
				if (!function_exists('cache_put_data'))
3342
					require_once($sourcedir . '/Load.php');
3343
				updateSettings($new_settings, true);
3344
3345
				if ($command_line)
3346
					echo ' done.';
3347
			}
3348
			elseif ($table == 'themes')
3349
			{
3350
				// Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point...
3351
				$query = $smcFunc['db_query']('', '
3352
					SELECT id_member, id_theme, value FROM {db_prefix}themes
3353
					WHERE variable = {string:admin_prefs}',
3354
					array(
3355
						'admin_prefs' => 'admin_preferences'
3356
					)
3357
				);
3358
3359
				if ($smcFunc['db_num_rows']($query) != 0)
3360
				{
3361
					while ($row = $smcFunc['db_fetch_assoc']($query))
3362
					{
3363
						$temp = @safe_unserialize($row['value']);
3364
						if ($temp === false)
3365
							$temp = @safe_unserialize(fix_serialized_data($row['value']));
3366
3367
						if ($command_line)
3368
						{
3369
							if ($temp === false)
3370
								echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.';
3371
							else
3372
								echo "\n" . 'Fixing admin preferences...';
3373
						}
3374
3375
						if ($temp !== false)
3376
						{
3377
							$row['value'] = json_encode($temp);
3378
3379
							// Even though we have all values from the table, UPDATE is still faster than REPLACE
3380
							$smcFunc['db_query']('', '
3381
								UPDATE {db_prefix}themes
3382
								SET value = {string:prefs}
3383
								WHERE id_theme = {int:theme}
3384
									AND id_member = {int:member}
3385
									AND variable = {string:admin_prefs}',
3386
								array(
3387
									'prefs' => $row['value'],
3388
									'theme' => $row['id_theme'],
3389
									'member' => $row['id_member'],
3390
									'admin_prefs' => 'admin_preferences'
3391
								)
3392
							);
3393
3394
							if ($command_line)
3395
								echo ' done.';
3396
						}
3397
					}
3398
3399
					$smcFunc['db_free_result']($query);
3400
				}
3401
			}
3402
			else
3403
			{
3404
				// First item is always the key...
3405
				$key = $info[0];
3406
				unset($info[0]);
3407
3408
				// Now we know what columns we have and such...
3409
				if (count($info) == 2 && $info[2] === true)
3410
				{
3411
					$col_select = $info[1];
3412
					$where = ' WHERE ' . $info[1] . ' != {empty}';
3413
				}
3414
				else
3415
				{
3416
					$col_select = implode(', ', $info);
3417
				}
3418
3419
				$query = $smcFunc['db_query']('', '
3420
					SELECT ' . $key . ', ' . $col_select . '
3421
					FROM {db_prefix}' . $table . $where,
3422
					array()
3423
				);
3424
3425
				if ($smcFunc['db_num_rows']($query) != 0)
3426
				{
3427
					if ($command_line)
3428
					{
3429
						echo "\n" . ' +++ Fixing the "' . $table . '" table...';
3430
						flush();
3431
					}
3432
3433
					while ($row = $smcFunc['db_fetch_assoc']($query))
3434
					{
3435
						$update = '';
3436
3437
						// We already know what our key is...
3438
						foreach ($info as $col)
3439
						{
3440
							if ($col !== true && $row[$col] != '')
3441
							{
3442
								$temp = @safe_unserialize($row[$col]);
3443
3444
								// Maybe we can fix the data?
3445
								if ($temp === false)
3446
									$temp = @safe_unserialize(fix_serialized_data($row[$col]));
3447
3448
								// Maybe the data is already JSON?
3449
								if ($temp === false)
3450
									$temp = smf_json_decode($row[$col], true, false);
3451
3452
								// Oh well...
3453
								if ($temp === null)
3454
								{
3455
									$temp = array();
3456
3457
									if ($command_line)
3458
										echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n";
3459
								}
3460
3461
								$row[$col] = json_encode($temp);
3462
3463
								// Build our SET string and variables array
3464
								$update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}';
3465
								$vars[$col] = $row[$col];
3466
							}
3467
						}
3468
3469
						$vars[$key] = $row[$key];
3470
3471
						// In a few cases, we might have empty data, so don't try to update in those situations...
3472
						if (!empty($update))
3473
						{
3474
							$smcFunc['db_query']('', '
3475
								UPDATE {db_prefix}' . $table . '
3476
								SET ' . $update . '
3477
								WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}',
3478
								$vars
3479
							);
3480
						}
3481
					}
3482
3483
					if ($command_line)
3484
						echo ' done.';
3485
3486
					// Free up some memory...
3487
					$smcFunc['db_free_result']($query);
3488
				}
3489
			}
3490
			// If this is XML to keep it nice for the user do one table at a time anyway!
3491
			if (isset($_GET['xml']))
3492
				return upgradeExit();
3493
		}
3494
3495
		if ($command_line)
3496
		{
3497
			echo "\n" . 'Successful.' . "\n";
3498
			flush();
3499
		}
3500
		$upcontext['step_progress'] = 100;
3501
3502
		// Last but not least, insert a dummy setting so we don't have to do this again in the future...
3503
		updateSettings(array('json_done' => true));
3504
3505
		$_GET['substep'] = 0;
3506
		// Make sure we move on!
3507
		if ($command_line)
3508
			return ConvertUtf8();
3509
3510
		return true;
3511
	}
3512
3513
	// If this fails we just move on to deleting the upgrade anyway...
3514
	$_GET['substep'] = 0;
3515
	return false;
3516
}
3517
3518
/**
3519
 * As of 2.1, we want to store db_last_error.php in the cache
3520
 * To make that happen, Settings.php needs to ensure the $cachedir path is correct before trying to write to db_last_error.php
3521
 */
3522
function move_db_last_error_to_cachedir()
3523
{
3524
	$settings = file_get_contents(dirname(__FILE__) . '/Settings.php');
3525
3526
	$regex = <<<'EOT'
3527
(\s*#\s*Make\s+sure\s+the\s+paths\s+are\s+correct\.\.\.\s+at\s+least\s+try\s+to\s+fix\s+them\.\s+)?if\s*\(\!file_exists\(\$boarddir\)\s+&&\s+file_exists\(dirname\(__FILE__\)\s+\.\s+'/agreement\.txt'\)\)\s+\$boarddir\s*\=\s*dirname\(__FILE__\);\s+if\s*\(\!file_exists\(\$sourcedir\)\s+&&\s+file_exists\(\$boarddir\s*\.\s*'/Sources'\)\)\s+\$sourcedir\s*\=\s*\$boarddir\s*\.\s*'/Sources';\s+if\s*\(\!file_exists\(\$cachedir\)\s+&&\s+file_exists\(\$boarddir\s*\.\s*'/cache'\)\)\s+\$cachedir\s*\=\s*\$boarddir\s*\.\s*'/cache';
3528
EOT;
3529
3530
	$replacement = <<<'EOT'
3531
# Make sure the paths are correct... at least try to fix them.
3532
if (!file_exists($boarddir) && file_exists(dirname(__FILE__) . '/agreement.txt'))
3533
	$boarddir = dirname(__FILE__);
3534
if (!file_exists($sourcedir) && file_exists($boarddir . '/Sources'))
3535
	$sourcedir = $boarddir . '/Sources';
3536
if (!file_exists($cachedir) && file_exists($boarddir . '/cache'))
3537
	$cachedir = $boarddir . '/cache';
3538
3539
3540
EOT;
3541
3542
	if (preg_match('~' . $regex . '~', $settings) && preg_match('~(#+\s*Error-Catching\s*#+)~', $settings))
3543
	{
3544
		$settings = preg_replace('~' . $regex . '~', '', $settings);
3545
		$settings = preg_replace('~(#+\s*Error-Catching\s*#+)~', $replacement . '$1', $settings);
3546
		$settings = preg_replace('~dirname(__FILE__) . \'/db_last_error.php\'~', '(isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\'', $settings);
3547
3548
		// Blank out the file - done to fix a oddity with some servers.
3549
		file_put_contents(dirname(__FILE__) . '/Settings.php', '');
3550
3551
		file_put_contents(dirname(__FILE__) . '/Settings.php', $settings);
3552
	}
3553
}
3554
3555
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3556
						Templates are below this point
3557
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
3558
3559
// This is what is displayed if there's any chmod to be done. If not it returns nothing...
3560
function template_chmod()
3561
{
3562
	global $upcontext, $txt, $settings;
3563
3564
	// Don't call me twice!
3565
	if (!empty($upcontext['chmod_called']))
3566
		return;
3567
3568
	$upcontext['chmod_called'] = true;
3569
3570
	// Nothing?
3571
	if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error']))
3572
		return;
3573
3574
	// Was it a problem with Windows?
3575
	if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess')
3576
	{
3577
		echo '
3578
		<div class="error">
3579
			<p>', $txt['upgrade_writable_files'], '</p>
3580
			<ul class="error_content">
3581
				<li>' . implode('</li>
3582
				<li>', $upcontext['chmod']['files']) . '</li>
3583
			</ul>
3584
		</div>';
3585
3586
		return false;
3587
	}
3588
3589
	echo '
3590
		<div class="panel">
3591
			<h2>', $txt['upgrade_ftp_login'], '</h2>
3592
			<h3>', $txt['upgrade_ftp_perms'], '</h3>
3593
			<script>
3594
				function warning_popup()
3595
				{
3596
					popup = window.open(\'\',\'popup\',\'height=150,width=400,scrollbars=yes\');
3597
					var content = popup.document;
3598
					content.write(\'<!DOCTYPE html>\n\');
3599
					content.write(\'<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>\n\t<head>\n\t\t<meta name="robots" content="noindex">\n\t\t\');
3600
					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\');
3601
					content.write(\'<div class="windowbg description">\n\t\t\t<h4>', $txt['upgrade_ftp_files'], '</h4>\n\t\t\t\');
3602
					content.write(\'<p>', implode('<br>\n\t\t\t', $upcontext['chmod']['files']), '</p>\n\t\t\t\');';
3603
3604
	if (isset($upcontext['systemos']) && $upcontext['systemos'] == 'linux')
3605
		echo '
3606
					content.write(\'<hr>\n\t\t\t\');
3607
					content.write(\'<p>', $txt['upgrade_ftp_shell'], '</p>\n\t\t\t\');
3608
					content.write(\'<tt># chmod a+w ', implode(' ', $upcontext['chmod']['files']), '</tt>\n\t\t\t\');';
3609
3610
	echo '
3611
					content.write(\'<a href="javascript:self.close();">close</a>\n\t\t</div>\n\t</body>\n</html>\');
3612
					content.close();
3613
				}
3614
			</script>';
3615
3616
	if (!empty($upcontext['chmod']['ftp_error']))
3617
		echo '
3618
			<div class="error">
3619
				<p>', $txt['upgrade_ftp_error'], '<p>
3620
				<code>', $upcontext['chmod']['ftp_error'], '</code>
3621
			</div>';
3622
3623
	if (empty($upcontext['chmod_in_form']))
3624
		echo '
3625
			<form action="', $upcontext['form_url'], '" method="post">';
3626
3627
	echo '
3628
				<dl class="settings">
3629
					<dt>
3630
						<label for="ftp_server">', $txt['ftp_server'], ':</label>
3631
					</dt>
3632
					<dd>
3633
						<div class="floatright">
3634
							<label for="ftp_port" class="textbox"><strong>', $txt['ftp_port'], ':</strong></label>
3635
							<input type="text" size="3" name="ftp_port" id="ftp_port" value="', isset($upcontext['chmod']['port']) ? $upcontext['chmod']['port'] : '21', '">
3636
						</div>
3637
						<input type="text" size="30" name="ftp_server" id="ftp_server" value="', isset($upcontext['chmod']['server']) ? $upcontext['chmod']['server'] : 'localhost', '">
3638
						<div class="smalltext">', $txt['ftp_server_info'], '</div>
3639
					</dd>
3640
					<dt>
3641
						<label for="ftp_username">', $txt['ftp_username'], ':</label>
3642
					</dt>
3643
					<dd>
3644
						<input type="text" size="30" name="ftp_username" id="ftp_username" value="', isset($upcontext['chmod']['username']) ? $upcontext['chmod']['username'] : '', '">
3645
						<div class="smalltext">', $txt['ftp_username_info'], '</div>
3646
					</dd>
3647
					<dt>
3648
						<label for="ftp_password">', $txt['ftp_password'], ':</label>
3649
					</dt>
3650
					<dd>
3651
						<input type="password" size="30" name="ftp_password" id="ftp_password">
3652
						<div class="smalltext">', $txt['ftp_password_info'], '</div>
3653
					</dd>
3654
					<dt>
3655
						<label for="ftp_path">', $txt['ftp_path'], ':</label>
3656
					</dt>
3657
					<dd>
3658
						<input type="text" size="30" name="ftp_path" id="ftp_path" value="', isset($upcontext['chmod']['path']) ? $upcontext['chmod']['path'] : '', '">
3659
						<div class="smalltext">', !empty($upcontext['chmod']['path']) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info'], '</div>
3660
					</dd>
3661
				</dl>
3662
3663
				<div class="righttext buttons">
3664
					<input type="submit" value="', $txt['ftp_connect'], '" class="button">
3665
				</div>';
3666
3667
	if (empty($upcontext['chmod_in_form']))
3668
		echo '
3669
			</form>';
3670
3671
	echo '
3672
		</div><!-- .panel -->';
3673
}
3674
3675
function template_upgrade_above()
3676
{
3677
	global $modSettings, $txt, $settings, $upcontext, $upgradeurl;
3678
3679
	echo '<!DOCTYPE html>
3680
<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>
3681
<head>
3682
	<meta charset="', isset($txt['lang_character_set']) ? $txt['lang_character_set'] : 'UTF-8', '">
3683
	<meta name="robots" content="noindex">
3684
	<title>', $txt['upgrade_upgrade_utility'], '</title>
3685
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">
3686
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/install.css">
3687
	', $txt['lang_rtl'] == true ? '<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/rtl.css">' : '', '
3688
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
3689
	<script src="', $settings['default_theme_url'], '/scripts/script.js"></script>
3690
	<script>
3691
		var smf_scripturl = \'', $upgradeurl, '\';
3692
		var smf_charset = \'', (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'UTF-8' : $txt['lang_character_set']) : $modSettings['global_character_set']), '\';
3693
		var startPercent = ', $upcontext['overall_percent'], ';
3694
3695
		// This function dynamically updates the step progress bar - and overall one as required.
3696
		function updateStepProgress(current, max, overall_weight)
3697
		{
3698
			// What out the actual percent.
3699
			var width = parseInt((current / max) * 100);
3700
			if (document.getElementById(\'step_progress\'))
3701
			{
3702
				document.getElementById(\'step_progress\').style.width = width + "%";
3703
				setInnerHTML(document.getElementById(\'step_text\'), width + "%");
3704
			}
3705
			if (overall_weight && document.getElementById(\'overall_progress\'))
3706
			{
3707
				overall_width = parseInt(startPercent + width * (overall_weight / 100));
3708
				document.getElementById(\'overall_progress\').style.width = overall_width + "%";
3709
				setInnerHTML(document.getElementById(\'overall_text\'), overall_width + "%");
3710
			}
3711
		}
3712
	</script>
3713
</head>
3714
<body>
3715
	<div id="footerfix">
3716
	<div id="header">
3717
		<h1 class="forumtitle">', $txt['upgrade_upgrade_utility'], '</h1>
3718
		<img id="smflogo" src="', $settings['default_theme_url'], '/images/smflogo.svg" alt="Simple Machines Forum" title="Simple Machines Forum">
3719
	</div>
3720
	<div id="wrapper">
3721
		<div id="content_section">
3722
			<div id="main_content_section">
3723
				<div id="main_steps">
3724
					<h2>', $txt['upgrade_progress'], '</h2>
3725
					<ul class="steps_list">';
3726
3727
	foreach ($upcontext['steps'] as $num => $step)
3728
		echo '
3729
						<li', $num == $upcontext['current_step'] ? ' class="stepcurrent"' : '', '>
3730
							', $txt['upgrade_step'], ' ', $step[0], ': ', $txt[$step[1]], '
3731
						</li>';
3732
3733
	echo '
3734
					</ul>
3735
				</div><!-- #main_steps -->
3736
3737
				<div id="install_progress">
3738
					<div id="progress_bar" class="progress_bar progress_green">
3739
						<h3>', $txt['upgrade_overall_progress'], '</h3>
3740
						<div id="overall_progress" class="bar" style="width: ', $upcontext['overall_percent'], '%;"></div>
3741
						<span id="overall_text">', $upcontext['overall_percent'], '%</span>
3742
					</div>';
3743
3744
	if (isset($upcontext['step_progress']))
3745
		echo '
3746
					<div id="progress_bar_step" class="progress_bar progress_yellow">
3747
						<h3>', $txt['upgrade_step_progress'], '</h3>
3748
						<div id="step_progress" class="bar" style="width: ', $upcontext['step_progress'], '%;"></div>
3749
						<span id="step_text">', $upcontext['step_progress'], '%</span>
3750
					</div>';
3751
3752
	echo '
3753
					<div id="substep_bar_div" class="progress_bar ', isset($upcontext['substep_progress']) ? '' : 'hidden', '">
3754
						<h3 id="substep_name">', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], array('.' => ''))) : '', '</h3>
3755
						<div id="substep_progress" class="bar" style="width: ', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%;"></div>
3756
						<span id="substep_text">', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%</span>
3757
					</div>';
3758
3759
	// How long have we been running this?
3760
	$elapsed = time() - $upcontext['started'];
3761
	$mins = (int) ($elapsed / 60);
3762
	$seconds = $elapsed - $mins * 60;
3763
	echo '
3764
					<div class="smalltext time_elapsed">
3765
						', $txt['upgrade_time_elapsed'], ':
3766
						<span id="mins_elapsed">', $mins, '</span> ', $txt['upgrade_time_mins'], ', <span id="secs_elapsed">', $seconds, '</span> ', $txt['upgrade_time_secs'], '.
3767
					</div>';
3768
	echo '
3769
				</div><!-- #install_progress -->
3770
				<div id="main_screen" class="clear">
3771
					<h2>', $upcontext['page_title'], '</h2>
3772
					<div class="panel">';
3773
}
3774
3775
function template_upgrade_below()
3776
{
3777
	global $upcontext, $txt;
3778
3779
	if (!empty($upcontext['pause']))
3780
		echo '
3781
							<em>', $txt['upgrade_incomplete'], '.</em><br>
3782
3783
							<h2 style="margin-top: 2ex;">', $txt['upgrade_not_quite_done'], '</h2>
3784
							<h3>
3785
								', $txt['upgrade_paused_overload'], '
3786
							</h3>';
3787
3788
	if (!empty($upcontext['custom_warning']))
3789
		echo '
3790
							<div class="errorbox">
3791
								<h3>', $txt['upgrade_note'], '</h3>
3792
								', $upcontext['custom_warning'], '
3793
							</div>';
3794
3795
	echo '
3796
							<div class="righttext buttons">';
3797
3798
	if (!empty($upcontext['continue']))
3799
		echo '
3800
								<input type="submit" id="contbutt" name="contbutt" value="', $txt['upgrade_continue'], '"', $upcontext['continue'] == 2 ? ' disabled' : '', ' class="button">';
3801
	if (!empty($upcontext['skip']))
3802
		echo '
3803
								<input type="submit" id="skip" name="skip" value="', $txt['upgrade_skip'], '" onclick="dontSubmit = true; document.getElementById(\'contbutt\').disabled = \'disabled\'; return true;" class="button">';
3804
3805
	echo '
3806
							</div>
3807
						</form>
3808
					</div><!-- .panel -->
3809
				</div><!-- #main_screen -->
3810
			</div><!-- #main_content_section -->
3811
		</div><!-- #content_section -->
3812
	</div><!-- #wrapper -->
3813
	</div><!-- #footerfix -->
3814
	<div id="footer">
3815
		<ul>
3816
			<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>
3817
		</ul>
3818
	</div>';
3819
3820
	// Are we on a pause?
3821
	if (!empty($upcontext['pause']))
3822
	{
3823
		echo '
3824
	<script>
3825
		window.onload = doAutoSubmit;
3826
		var countdown = 3;
3827
		var dontSubmit = false;
3828
3829
		function doAutoSubmit()
3830
		{
3831
			if (countdown == 0 && !dontSubmit)
3832
				document.upform.submit();
3833
			else if (countdown == -1)
3834
				return;
3835
3836
			document.getElementById(\'contbutt\').value = "', $txt['upgrade_continue'], ' (" + countdown + ")";
3837
			countdown--;
3838
3839
			setTimeout("doAutoSubmit();", 1000);
3840
		}
3841
	</script>';
3842
	}
3843
3844
	echo '
3845
</body>
3846
</html>';
3847
}
3848
3849
function template_xml_above()
3850
{
3851
	global $upcontext;
3852
3853
	echo '<', '?xml version="1.0" encoding="UTF-8"?', '>
3854
	<smf>';
3855
3856
	if (!empty($upcontext['get_data']))
3857
		foreach ($upcontext['get_data'] as $k => $v)
3858
			echo '
3859
		<get key="', $k, '">', $v, '</get>';
3860
}
3861
3862
function template_xml_below()
3863
{
3864
	echo '
3865
	</smf>';
3866
}
3867
3868
function template_error_message()
3869
{
3870
	global $upcontext;
3871
3872
	echo '
3873
	<div class="error">
3874
		', $upcontext['error_msg'], '
3875
		<br>
3876
		<a href="', $_SERVER['PHP_SELF'], '">Click here to try again.</a>
3877
	</div>';
3878
}
3879
3880
function template_welcome_message()
3881
{
3882
	global $upcontext, $disable_security, $settings, $txt;
3883
3884
	echo '
3885
				<script src="https://www.simplemachines.org/smf/current-version.js?version=' . SMF_VERSION . '"></script>
3886
3887
				<h3>', sprintf($txt['upgrade_ready_proceed'], SMF_VERSION), '</h3>
3888
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">
3889
					<input type="hidden" name="', $upcontext['login_token_var'], '" value="', $upcontext['login_token'], '">
3890
3891
					<div id="version_warning" class="noticebox hidden">
3892
						<h3>', $txt['upgrade_warning'], '</h3>
3893
						', sprintf($txt['upgrade_warning_out_of_date'], SMF_VERSION, 'https://www.simplemachines.org'), '
3894
					</div>';
3895
3896
	$upcontext['chmod_in_form'] = true;
3897
	template_chmod();
3898
3899
	// For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade!
3900
	if ($upcontext['is_large_forum'])
3901
		echo '
3902
					<div class="errorbox">
3903
						<h3>', $txt['upgrade_warning'], '</h3>
3904
						', $txt['upgrade_warning_lots_data'], '
3905
					</div>';
3906
3907
	// A warning message?
3908
	if (!empty($upcontext['warning']))
3909
		echo '
3910
					<div class="errorbox">
3911
						<h3>', $txt['upgrade_warning'], '</h3>
3912
						', $upcontext['warning'], '
3913
					</div>';
3914
3915
	// Paths are incorrect?
3916
	echo '
3917
					<div class="errorbox', (file_exists($settings['default_theme_dir'] . '/scripts/script.js') ? ' hidden' : ''), '" id="js_script_missing_error">
3918
						<h3>', $txt['upgrade_critical_error'], '</h3>
3919
						', sprintf($txt['upgrade_error_script_js'], 'https://download.simplemachines.org/?tools'), '
3920
					</div>';
3921
3922
	// Is there someone already doing this?
3923
	if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600))
3924
	{
3925
		$ago = time() - $upcontext['started'];
3926
		$ago_hours = floor($ago / 3600);
3927
		$ago_minutes = intval(($ago / 60) % 60);
3928
		$ago_seconds = intval($ago % 60);
3929
		$agoTxt = $ago < 60 ? 'upgrade_time_s' : ($ago < 3600 ? 'upgrade_time_ms' : 'upgrade_time_hms');
3930
3931
		$updated = time() - $upcontext['updated'];
3932
		$updated_hours = floor($updated / 3600);
3933
		$updated_minutes = intval(($updated / 60) % 60);
3934
		$updated_seconds = intval($updated % 60);
3935
		$updatedTxt = $updated < 60 ? 'upgrade_time_updated_s' : ($updated < 3600 ? 'upgrade_time_updated_hm' : 'upgrade_time_updated_hms');
3936
3937
		echo '
3938
					<div class="errorbox">
3939
						<h3>', $txt['upgrade_warning'], '</h3>
3940
						<p>', sprintf($txt['upgrade_time_user'], $upcontext['user']['name']), '</p>
3941
						<p>', sprintf($txt[$agoTxt], $ago_seconds, $ago_minutes, $ago_hours), '</p>
3942
						<p>', sprintf($txt[$updatedTxt], $updated_seconds, $updated_minutes, $updated_hours), '</p>';
3943
3944
		if ($updated < 600)
3945
			echo '
3946
						<p>', $txt['upgrade_run_script'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_run_script2'], '</p>';
3947
3948
		if ($updated > $upcontext['inactive_timeout'])
3949
			echo '
3950
						<p>', $txt['upgrade_run'], '</p>';
3951
		else
3952
			echo '
3953
						<p>', $txt['upgrade_script_timeout'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_script_timeout2'], ' ', ($upcontext['inactive_timeout'] > 120 ? round($upcontext['inactive_timeout'] / 60, 1) . ' minutes!' : $upcontext['inactive_timeout'] . ' seconds!'), '</p>';
3954
3955
		echo '
3956
					</div>';
3957
	}
3958
3959
	echo '
3960
					<strong>', $txt['upgrade_admin_login'], ' ', $disable_security ? '(DISABLED)' : '', '</strong>
3961
					<h3>', $txt['upgrade_sec_login'], '</h3>
3962
					<dl class="settings adminlogin">
3963
						<dt>
3964
							<label for="user"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_username'], '</label>
3965
						</dt>
3966
						<dd>
3967
							<input type="text" name="user" value="', !empty($upcontext['username']) ? $upcontext['username'] : '', '"', $disable_security ? ' disabled' : '', '>';
3968
3969
	if (!empty($upcontext['username_incorrect']))
3970
		echo '
3971
							<div class="smalltext red">', $txt['upgrade_wrong_username'], '</div>';
3972
3973
	echo '
3974
						</dd>
3975
						<dt>
3976
							<label for="passwrd"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_password'], '</label>
3977
						</dt>
3978
						<dd>
3979
							<input type="password" name="passwrd" value=""', $disable_security ? ' disabled' : '', '>
3980
							<input type="hidden" name="hash_passwrd" value="">';
3981
3982
	if (!empty($upcontext['password_failed']))
3983
		echo '
3984
							<div class="smalltext red">', $txt['upgrade_wrong_password'], '</div>';
3985
3986
	echo '
3987
						</dd>';
3988
3989
	// Can they continue?
3990
	if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1)
3991
	{
3992
		echo '
3993
						<dd>
3994
							<label for="cont"><input type="checkbox" id="cont" name="cont" checked>', $txt['upgrade_continue_step'], '</label>
3995
						</dd>';
3996
	}
3997
3998
	echo '
3999
					</dl>
4000
					<span class="smalltext">
4001
						', $txt['upgrade_bypass'], '
4002
					</span>
4003
					<input type="hidden" name="login_attempt" id="login_attempt" value="1">
4004
					<input type="hidden" name="js_works" id="js_works" value="0">';
4005
4006
	// Say we want the continue button!
4007
	$upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1;
4008
4009
	// This defines whether javascript is going to work elsewhere :D
4010
	echo '
4011
					<script>
4012
						if (\'XMLHttpRequest\' in window && document.getElementById(\'js_works\'))
4013
							document.getElementById(\'js_works\').value = 1;
4014
4015
						// Latest version?
4016
						function smfCurrentVersion()
4017
						{
4018
							var smfVer, yourVer;
4019
4020
							if (!(\'smfVersion\' in window))
4021
								return;
4022
4023
							window.smfVersion = window.smfVersion.replace(/SMF\s?/g, \'\');
4024
4025
							smfVer = document.getElementById(\'smfVersion\');
4026
							yourVer = document.getElementById(\'yourVersion\');
4027
4028
							setInnerHTML(smfVer, window.smfVersion);
4029
4030
							var currentVersion = getInnerHTML(yourVer);
4031
							if (currentVersion < window.smfVersion)
4032
								document.getElementById(\'version_warning\').classList.remove(\'hidden\');
4033
						}
4034
						addLoadEvent(smfCurrentVersion);
4035
4036
						// This checks that the script file even exists!
4037
						if (typeof(smfSelectText) == \'undefined\')
4038
							document.getElementById(\'js_script_missing_error\').classList.remove(\'hidden\');
4039
4040
					</script>';
4041
}
4042
4043
function template_upgrade_options()
4044
{
4045
	global $upcontext, $modSettings, $db_prefix, $mmessage, $mtitle, $txt;
4046
4047
	echo '
4048
				<h3>', $txt['upgrade_areyouready'], '</h3>
4049
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">';
4050
4051
	// Warning message?
4052
	if (!empty($upcontext['upgrade_options_warning']))
4053
		echo '
4054
				<div class="errorbox">
4055
					<h3>', $txt['upgrade_warning'], '</h3>
4056
					', $upcontext['upgrade_options_warning'], '
4057
				</div>';
4058
4059
	echo '
4060
				<ul class="upgrade_settings">
4061
					<li>
4062
						<input type="checkbox" name="backup" id="backup" value="1">
4063
						<label for="backup">', $txt['upgrade_backup_table'], ' &quot;backup_' . $db_prefix . '&quot;.</label>
4064
						(', $txt['upgrade_recommended'], ')
4065
					</li>
4066
					<li>
4067
						<input type="checkbox" name="maint" id="maint" value="1" checked>
4068
						<label for="maint">', $txt['upgrade_maintenance'], '</label>
4069
						<span class="smalltext">(<a href="javascript:void(0)" onclick="document.getElementById(\'mainmess\').classList.toggle(\'hidden\')">', $txt['upgrade_customize'], '</a>)</span>
4070
						<div id="mainmess" class="hidden">
4071
							<strong class="smalltext">', $txt['upgrade_maintenance_title'], ' </strong><br>
4072
							<input type="text" name="maintitle" size="30" value="', htmlspecialchars($mtitle), '"><br>
4073
							<strong class="smalltext">', $txt['upgrade_maintenance_message'], ' </strong><br>
4074
							<textarea name="mainmessage" rows="3" cols="50">', htmlspecialchars($mmessage), '</textarea>
4075
						</div>
4076
					</li>
4077
					<li>
4078
						<input type="checkbox" name="debug" id="debug" value="1">
4079
						<label for="debug">'.$txt['upgrade_debug_info'], '</label>
4080
					</li>
4081
					<li>
4082
						<input type="checkbox" name="empty_error" id="empty_error" value="1">
4083
						<label for="empty_error">', $txt['upgrade_empty_errorlog'], '</label>
4084
					</li>';
4085
4086
	if (!empty($upcontext['karma_installed']['good']) || !empty($upcontext['karma_installed']['bad']))
4087
		echo '
4088
					<li>
4089
						<input type="checkbox" name="delete_karma" id="delete_karma" value="1">
4090
						<label for="delete_karma">', $txt['upgrade_delete_karma'], '</label>
4091
					</li>';
4092
4093
	echo '
4094
					<li>
4095
						<input type="checkbox" name="stats" id="stats" value="1"', empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']) ? '' : ' checked="checked"', '>
4096
						<label for="stat">
4097
							', $txt['upgrade_stats_collection'], '<br>
4098
							<span class="smalltext">', sprintf($txt['upgrade_stats_info'], 'https://www.simplemachines.org/about/stats.php'), '</a></span>
4099
						</label>
4100
					</li>
4101
					<li>
4102
						<input type="checkbox" name="migrateSettings" id="migrateSettings" value="1"', empty($upcontext['migrateSettingsNeeded']) ? '' : ' checked="checked"', '>
4103
						<label for="migrateSettings">
4104
							', $txt['upgrade_migrate_settings_file'], '
4105
						</label>
4106
					</li>
4107
				</ul>
4108
				<input type="hidden" name="upcont" value="1">';
4109
4110
	// We need a normal continue button here!
4111
	$upcontext['continue'] = 1;
4112
}
4113
4114
// Template for the database backup tool/
4115
function template_backup_database()
4116
{
4117
	global $upcontext, $support_js, $is_debug, $txt;
4118
4119
	echo '
4120
				<h3>', $txt['upgrade_wait'], '</h3>';
4121
4122
	echo '
4123
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4124
					<input type="hidden" name="backup_done" id="backup_done" value="0">
4125
					<strong>', sprintf($txt['upgrade_completedtables_outof'], $upcontext['cur_table_num'], $upcontext['table_count']), '</strong>
4126
					<div id="debug_section">
4127
						<span id="debuginfo"></span>
4128
					</div>';
4129
4130
	// Dont any tables so far?
4131
	if (!empty($upcontext['previous_tables']))
4132
		foreach ($upcontext['previous_tables'] as $table)
4133
			echo '
4134
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4135
4136
	echo '
4137
					<h3 id="current_tab">
4138
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4139
					</h3>
4140
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">Backup Complete! Click Continue to Proceed.</p>';
4141
4142
	// Continue please!
4143
	$upcontext['continue'] = $support_js ? 2 : 1;
4144
4145
	// If javascript allows we want to do this using XML.
4146
	if ($support_js)
4147
	{
4148
		echo '
4149
					<script>
4150
						var lastTable = ', $upcontext['cur_table_num'], ';
4151
						function getNextTables()
4152
						{
4153
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4154
						}
4155
4156
						// Got an update!
4157
						function onBackupUpdate(oXMLDoc)
4158
						{
4159
							var sCurrentTableName = "";
4160
							var iTableNum = 0;
4161
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4162
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4163
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4164
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4165
4166
							// Update the page.
4167
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4168
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4169
							lastTable = iTableNum;
4170
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4171
4172
		// If debug flood the screen.
4173
		if ($is_debug)
4174
			echo '
4175
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4176
4177
							if (document.getElementById(\'debug_section\').scrollHeight)
4178
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4179
4180
		echo '
4181
							// Get the next update...
4182
							if (iTableNum == ', $upcontext['table_count'], ')
4183
							{
4184
								document.getElementById(\'commess\').classList.remove("hidden");
4185
								document.getElementById(\'current_tab\').classList.add("hidden");
4186
								document.getElementById(\'contbutt\').disabled = 0;
4187
								document.getElementById(\'backup_done\').value = 1;
4188
							}
4189
							else
4190
								getNextTables();
4191
						}
4192
						getNextTables();
4193
					//# sourceURL=dynamicScript-bkup.js
4194
					</script>';
4195
	}
4196
}
4197
4198
function template_backup_xml()
4199
{
4200
	global $upcontext;
4201
4202
	echo '
4203
		<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4204
}
4205
4206
// Here is the actual "make the changes" template!
4207
function template_database_changes()
4208
{
4209
	global $upcontext, $support_js, $is_debug, $timeLimitThreshold, $txt;
4210
4211
	if (empty($is_debug) && !empty($upcontext['upgrade_status']['debug']))
4212
		$is_debug = true;
4213
4214
	echo '
4215
				<h3>', $txt['upgrade_db_changes'], '</h3>
4216
				<h4><em>', $txt['upgrade_db_patient'], '</em></h4>';
4217
4218
	echo '
4219
				<form action="', $upcontext['form_url'], '&amp;filecount=', $upcontext['file_count'], '" name="upform" id="upform" method="post">
4220
					<input type="hidden" name="database_done" id="database_done" value="0">';
4221
4222
	// No javascript looks rubbish!
4223
	if (!$support_js)
4224
	{
4225
		foreach ($upcontext['actioned_items'] as $num => $item)
4226
		{
4227
			if ($num != 0)
4228
				echo ' Successful!';
4229
			echo '<br>' . $item;
4230
		}
4231
4232
		// Only tell deubbers how much time they wasted waiting for the upgrade because they don't have javascript.
4233
		if (!empty($upcontext['changes_complete']))
4234
		{
4235
			if ($is_debug)
4236
			{
4237
				$active = time() - $upcontext['started'];
4238
				$hours = floor($active / 3600);
4239
				$minutes = intval(($active / 60) % 60);
4240
				$seconds = intval($active % 60);
4241
4242
				echo '', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '<br>';
4243
			}
4244
			else
4245
				echo '', $txt['upgrade_success'], '<br>';
4246
4247
			echo '
4248
					<p id="commess">', $txt['upgrade_db_complete'], '</p>';
4249
		}
4250
	}
4251
	else
4252
	{
4253
		// Tell them how many files we have in total.
4254
		if ($upcontext['file_count'] > 1)
4255
			echo '
4256
					<strong id="info1">', $txt['upgrade_script'], ' <span id="file_done">', $upcontext['cur_file_num'], '</span> of ', $upcontext['file_count'], '.</strong>';
4257
4258
		echo '
4259
					<h3 id="info2">
4260
						<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>
4261
					</h3>
4262
					<p id="commess" class="', !empty($upcontext['changes_complete']) || $upcontext['current_debug_item_num'] == $upcontext['debug_items'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_db_complete2'], '</p>';
4263
4264
		if ($is_debug)
4265
		{
4266
			// Let our debuggers know how much time was spent, but not wasted since JS handled refreshing the page!
4267
			if ($upcontext['current_debug_item_num'] == $upcontext['debug_items'])
4268
			{
4269
				$active = time() - $upcontext['started'];
4270
				$hours = floor($active / 3600);
4271
				$minutes = intval(($active / 60) % 60);
4272
				$seconds = intval($active % 60);
4273
4274
				echo '
4275
					<p id="upgradeCompleted">', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '</p>';
4276
			}
4277
			else
4278
				echo '
4279
					<p id="upgradeCompleted"></p>';
4280
4281
			echo '
4282
					<div id="debug_section">
4283
						<span id="debuginfo"></span>
4284
					</div>';
4285
		}
4286
	}
4287
4288
	// Place for the XML error message.
4289
	echo '
4290
					<div id="error_block" class="errorbox', empty($upcontext['error_message']) ? ' hidden' : '', '">
4291
						<h3>', $txt['upgrade_error'], '</h3>
4292
						<div id="error_message">', isset($upcontext['error_message']) ? $upcontext['error_message'] : $txt['upgrade_unknown_error'], '</div>
4293
					</div>';
4294
4295
	// We want to continue at some point!
4296
	$upcontext['continue'] = $support_js ? 2 : 1;
4297
4298
	// If javascript allows we want to do this using XML.
4299
	if ($support_js)
4300
	{
4301
		echo '
4302
					<script>
4303
						var lastItem = ', $upcontext['current_debug_item_num'], ';
4304
						var sLastString = "', strtr($upcontext['current_debug_item_name'], array('"' => '&quot;')), '";
4305
						var iLastSubStepProgress = -1;
4306
						var curFile = ', $upcontext['cur_file_num'], ';
4307
						var totalItems = 0;
4308
						var prevFile = 0;
4309
						var retryCount = 0;
4310
						var testvar = 0;
4311
						var timeOutID = 0;
4312
						var getData = "";
4313
						var debugItems = ', $upcontext['debug_items'], ';';
4314
4315
		if ($is_debug)
4316
			echo '
4317
						var upgradeStartTime = ' . $upcontext['started'] . ';';
4318
4319
		echo '
4320
						function getNextItem()
4321
						{
4322
							// We want to track this...
4323
							if (timeOutID)
4324
								clearTimeout(timeOutID);
4325
							timeOutID = window.setTimeout("retTimeout()", ', (10 * $timeLimitThreshold), '000);
4326
4327
							getXMLDocument(\'', $upcontext['form_url'], '&xml&filecount=', $upcontext['file_count'], '&substep=\' + lastItem + getData, onItemUpdate);
4328
						}
4329
4330
						// Got an update!
4331
						function onItemUpdate(oXMLDoc)
4332
						{
4333
							var sItemName = "";
4334
							var sDebugName = "";
4335
							var iItemNum = 0;
4336
							var iSubStepProgress = -1;
4337
							var iDebugNum = 0;
4338
							var bIsComplete = 0;
4339
							var bSkipped = 0;
4340
							getData = "";
4341
4342
							// We\'ve got something - so reset the timeout!
4343
							if (timeOutID)
4344
								clearTimeout(timeOutID);
4345
4346
							// Assume no error at this time...
4347
							document.getElementById("error_block").classList.add("hidden");
4348
4349
							// Are we getting some duff info?
4350
							if (!oXMLDoc.getElementsByTagName("item")[0])
4351
							{
4352
								// Too many errors?
4353
								if (retryCount > 15)
4354
								{
4355
									document.getElementById("error_block").classList.remove("hidden");
4356
									setInnerHTML(document.getElementById("error_message"), "Error retrieving information on step: " + (sDebugName == "" ? sLastString : sDebugName));';
4357
4358
		if ($is_debug)
4359
			echo '
4360
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4361
4362
		echo '
4363
								}
4364
								else
4365
								{
4366
									retryCount++;
4367
									getNextItem();
4368
								}
4369
								return false;
4370
							}
4371
4372
							// Never allow loops.
4373
							if (curFile == prevFile)
4374
							{
4375
								retryCount++;
4376
								if (retryCount > 10)
4377
								{
4378
									document.getElementById("error_block").classList.remove("hidden");
4379
									setInnerHTML(document.getElementById("error_message"), "', $txt['upgrade_loop'], '" + sDebugName);';
4380
4381
		if ($is_debug)
4382
			echo '
4383
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4384
4385
		echo '
4386
								}
4387
							}
4388
							retryCount = 0;
4389
4390
							for (var i = 0; i < oXMLDoc.getElementsByTagName("item")[0].childNodes.length; i++)
4391
								sItemName += oXMLDoc.getElementsByTagName("item")[0].childNodes[i].nodeValue;
4392
							for (var i = 0; i < oXMLDoc.getElementsByTagName("debug")[0].childNodes.length; i++)
4393
								sDebugName += oXMLDoc.getElementsByTagName("debug")[0].childNodes[i].nodeValue;
4394
							for (var i = 0; i < oXMLDoc.getElementsByTagName("get").length; i++)
4395
							{
4396
								getData += "&" + oXMLDoc.getElementsByTagName("get")[i].getAttribute("key") + "=";
4397
								for (var j = 0; j < oXMLDoc.getElementsByTagName("get")[i].childNodes.length; j++)
4398
								{
4399
									getData += oXMLDoc.getElementsByTagName("get")[i].childNodes[j].nodeValue;
4400
								}
4401
							}
4402
4403
							iItemNum = oXMLDoc.getElementsByTagName("item")[0].getAttribute("num");
4404
							iDebugNum = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("num"));
4405
							bIsComplete = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("complete"));
4406
							bSkipped = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("skipped"));
4407
							iSubStepProgress = parseFloat(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("percent"));
4408
							sLastString = sDebugName + " (Item: " + iDebugNum + ")";
4409
4410
							curFile = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("num"));
4411
							debugItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("debug_items"));
4412
							totalItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("items"));
4413
4414
							// If we have an error we haven\'t completed!
4415
							if (oXMLDoc.getElementsByTagName("error")[0] && bIsComplete)
4416
								iDebugNum = lastItem;
4417
4418
							// Do we have the additional progress bar?
4419
							if (iSubStepProgress != -1)
4420
							{
4421
								document.getElementById("substep_bar_div").classList.remove("hidden");
4422
								document.getElementById("substep_progress").style.width = iSubStepProgress + "%";
4423
								setInnerHTML(document.getElementById("substep_text"), iSubStepProgress + "%");
4424
								setInnerHTML(document.getElementById("substep_name"), sDebugName.replace(/\./g, ""));
4425
							}
4426
							else
4427
							{
4428
								document.getElementById("substep_bar_div").classList.add("hidden");
4429
							}
4430
4431
							// Move onto the next item?
4432
							if (bIsComplete)
4433
								lastItem = iDebugNum;
4434
							else
4435
								lastItem = iDebugNum - 1;
4436
4437
							// Are we finished?
4438
							if (bIsComplete && iDebugNum == -1 && curFile >= ', $upcontext['file_count'], ')
4439
							{';
4440
4441
		// Database Changes, tell us how much time we spen to do this.  If this gets updated via JS.
4442
		if ($is_debug)
4443
			echo '
4444
								document.getElementById(\'debug_section\').classList.add("hidden");
4445
4446
								var upgradeFinishedTime = parseInt(oXMLDoc.getElementsByTagName("curtime")[0].childNodes[0].nodeValue);
4447
								var diffTime = upgradeFinishedTime - upgradeStartTime;
4448
								var diffHours = Math.floor(diffTime / 3600);
4449
								var diffMinutes = parseInt((diffTime / 60) % 60);
4450
								var diffSeconds = parseInt(diffTime % 60);
4451
4452
								var completedTxt = "', $txt['upgrade_success_time_db'], '";
4453
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4454
4455
								completedTxt = completedTxt.replace("%1$d", diffSeconds).replace("%2$d", diffMinutes).replace("%3$d", diffHours);
4456
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4457
								setInnerHTML(document.getElementById("upgradeCompleted"), completedTxt);';
4458
4459
		echo '
4460
4461
								document.getElementById(\'commess\').classList.remove("hidden");
4462
								document.getElementById(\'contbutt\').disabled = 0;
4463
								document.getElementById(\'database_done\').value = 1;';
4464
4465
		if ($upcontext['file_count'] > 1)
4466
			echo '
4467
								document.getElementById(\'info1\').classList.add(\'hidden\');';
4468
4469
		echo '
4470
								document.getElementById(\'info2\').classList.add(\'hidden\');
4471
								updateStepProgress(100, 100, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4472
								return true;
4473
							}
4474
							// Was it the last step in the file?
4475
							else if (bIsComplete && iDebugNum == -1)
4476
							{
4477
								lastItem = 0;
4478
								prevFile = curFile;';
4479
4480
		if ($is_debug)
4481
			echo '
4482
								setOuterHTML(document.getElementById(\'debuginfo\'), \'Moving to next script file...done<br><span id="debuginfo"><\' + \'/span>\');';
4483
4484
		echo '
4485
								getNextItem();
4486
								return true;
4487
							}';
4488
4489
		// If debug scroll the screen.
4490
		if ($is_debug)
4491
			echo '
4492
							if (iLastSubStepProgress == -1)
4493
							{
4494
								// Give it consistent dots.
4495
								dots = sDebugName.match(/\./g);
4496
								numDots = dots ? dots.length : 0;
4497
								for (var i = numDots; i < 3; i++)
4498
									sDebugName += ".";
4499
								setOuterHTML(document.getElementById(\'debuginfo\'), sDebugName + \'<span id="debuginfo"><\' + \'/span>\');
4500
							}
4501
							iLastSubStepProgress = iSubStepProgress;
4502
4503
							if (bIsComplete && bSkipped)
4504
								setOuterHTML(document.getElementById(\'debuginfo\'), \'skipped<br><span id="debuginfo"><\' + \'/span>\');
4505
							else if (bIsComplete)
4506
								setOuterHTML(document.getElementById(\'debuginfo\'), \'done<br><span id="debuginfo"><\' + \'/span>\');
4507
							else
4508
								setOuterHTML(document.getElementById(\'debuginfo\'), \'...<span id="debuginfo"><\' + \'/span>\');
4509
4510
							if (document.getElementById(\'debug_section\').scrollHeight)
4511
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4512
4513
		echo '
4514
							// Update the page.
4515
							setInnerHTML(document.getElementById(\'item_num\'), iItemNum);
4516
							setInnerHTML(document.getElementById(\'cur_item_name\'), sItemName);';
4517
4518
		if ($upcontext['file_count'] > 1)
4519
		{
4520
			echo '
4521
							setInnerHTML(document.getElementById(\'file_done\'), curFile);
4522
							setInnerHTML(document.getElementById(\'item_count\'), totalItems);';
4523
		}
4524
4525
		echo '
4526
							// Is there an error?
4527
							if (oXMLDoc.getElementsByTagName("error")[0])
4528
							{
4529
								var sErrorMsg = "";
4530
								for (var i = 0; i < oXMLDoc.getElementsByTagName("error")[0].childNodes.length; i++)
4531
									sErrorMsg += oXMLDoc.getElementsByTagName("error")[0].childNodes[i].nodeValue;
4532
								document.getElementById("error_block").classList.remove("hidden");
4533
								setInnerHTML(document.getElementById("error_message"), sErrorMsg);
4534
								return false;
4535
							}
4536
4537
							// Get the progress bar right.
4538
							barTotal = debugItems * ', $upcontext['file_count'], ';
4539
							barDone = (debugItems * (curFile - 1)) + lastItem;
4540
4541
							updateStepProgress(barDone, barTotal, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4542
4543
							// Finally - update the time here as it shows the server is responding!
4544
							curTime = new Date();
4545
							iElapsed = (curTime.getTime() / 1000 - ', $upcontext['started'], ');
4546
							mins = parseInt(iElapsed / 60);
4547
							secs = parseInt(iElapsed - mins * 60);
4548
							setInnerHTML(document.getElementById("mins_elapsed"), mins);
4549
							setInnerHTML(document.getElementById("secs_elapsed"), secs);
4550
4551
							getNextItem();
4552
							return true;
4553
						}
4554
4555
						// What if we timeout?!
4556
						function retTimeout(attemptAgain)
4557
						{
4558
							// Oh noes...
4559
							if (!attemptAgain)
4560
							{
4561
								document.getElementById("error_block").classList.remove("hidden");
4562
								setInnerHTML(document.getElementById("error_message"), "', sprintf($txt['upgrade_respondtime'], ($timeLimitThreshold * 10)), '" + "<a href=\"#\" onclick=\"retTimeout(true); return false;\">', $txt['upgrade_respondtime_clickhere'], '</a>");
4563
							}
4564
							else
4565
							{
4566
								document.getElementById("error_block").classList.add("hidden");
4567
								getNextItem();
4568
							}
4569
						}';
4570
4571
		// Start things off assuming we've not errored.
4572
		if (empty($upcontext['error_message']))
4573
			echo '
4574
						getNextItem();';
4575
4576
		echo '
4577
					//# sourceURL=dynamicScript-dbch.js
4578
					</script>';
4579
	}
4580
	return;
4581
}
4582
4583
function template_database_xml()
4584
{
4585
	global $is_debug, $upcontext;
4586
4587
	echo '
4588
	<file num="', $upcontext['cur_file_num'], '" items="', $upcontext['total_items'], '" debug_items="', $upcontext['debug_items'], '">', $upcontext['cur_file_name'], '</file>
4589
	<item num="', $upcontext['current_item_num'], '">', $upcontext['current_item_name'], '</item>
4590
	<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>';
4591
4592
	if (!empty($upcontext['error_message']))
4593
		echo '
4594
	<error>', $upcontext['error_message'], '</error>';
4595
4596
	if (!empty($upcontext['error_string']))
4597
		echo '
4598
	<sql>', $upcontext['error_string'], '</sql>';
4599
4600
	if ($is_debug)
4601
		echo '
4602
	<curtime>', time(), '</curtime>';
4603
}
4604
4605
// Template for the UTF-8 conversion step. Basically a copy of the backup stuff with slight modifications....
4606
function template_convert_utf8()
4607
{
4608
	global $upcontext, $support_js, $is_debug, $txt;
4609
4610
	echo '
4611
				<h3>', $txt['upgrade_wait2'], '</h3>
4612
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4613
					<input type="hidden" name="utf8_done" id="utf8_done" value="0">
4614
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4615
					<div id="debug_section">
4616
						<span id="debuginfo"></span>
4617
					</div>';
4618
4619
	// Done any tables so far?
4620
	if (!empty($upcontext['previous_tables']))
4621
		foreach ($upcontext['previous_tables'] as $table)
4622
			echo '
4623
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4624
4625
	echo '
4626
					<h3 id="current_tab">
4627
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4628
					</h3>';
4629
4630
	// If we dropped their index, let's let them know
4631
	if ($upcontext['dropping_index'])
4632
		echo '
4633
					<p id="indexmsg" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '>', $txt['upgrade_fulltext'], '</p>';
4634
4635
	// Completion notification
4636
	echo '
4637
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_conversion_proceed'], '</p>';
4638
4639
	// Continue please!
4640
	$upcontext['continue'] = $support_js ? 2 : 1;
4641
4642
	// If javascript allows we want to do this using XML.
4643
	if ($support_js)
4644
	{
4645
		echo '
4646
					<script>
4647
						var lastTable = ', $upcontext['cur_table_num'], ';
4648
						function getNextTables()
4649
						{
4650
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onConversionUpdate);
4651
						}
4652
4653
						// Got an update!
4654
						function onConversionUpdate(oXMLDoc)
4655
						{
4656
							var sCurrentTableName = "";
4657
							var iTableNum = 0;
4658
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4659
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4660
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4661
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4662
4663
							// Update the page.
4664
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4665
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4666
							lastTable = iTableNum;
4667
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4668
4669
		// If debug flood the screen.
4670
		if ($is_debug)
4671
			echo '
4672
						setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4673
4674
						if (document.getElementById(\'debug_section\').scrollHeight)
4675
							document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4676
4677
		echo '
4678
						// Get the next update...
4679
						if (iTableNum == ', $upcontext['table_count'], ')
4680
						{
4681
							document.getElementById(\'commess\').classList.remove(\'hidden\');
4682
							if (document.getElementById(\'indexmsg\') != null) {
4683
								document.getElementById(\'indexmsg\').classList.remove(\'hidden\');
4684
							}
4685
							document.getElementById(\'current_tab\').classList.add(\'hidden\');
4686
							document.getElementById(\'contbutt\').disabled = 0;
4687
							document.getElementById(\'utf8_done\').value = 1;
4688
						}
4689
						else
4690
							getNextTables();
4691
					}
4692
					getNextTables();
4693
				//# sourceURL=dynamicScript-conv.js
4694
				</script>';
4695
	}
4696
}
4697
4698
function template_convert_xml()
4699
{
4700
	global $upcontext;
4701
4702
	echo '
4703
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4704
}
4705
4706
// Template for the database backup tool/
4707
function template_serialize_json()
4708
{
4709
	global $upcontext, $support_js, $is_debug, $txt;
4710
4711
	echo '
4712
				<h3>', $txt['upgrade_convert_datajson'], '</h3>
4713
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4714
					<input type="hidden" name="json_done" id="json_done" value="0">
4715
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4716
					<div id="debug_section">
4717
						<span id="debuginfo"></span>
4718
					</div>';
4719
4720
	// Dont any tables so far?
4721
	if (!empty($upcontext['previous_tables']))
4722
		foreach ($upcontext['previous_tables'] as $table)
4723
			echo '
4724
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4725
4726
	echo '
4727
					<h3 id="current_tab">
4728
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4729
					</h3>
4730
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_json_completed'], '</p>';
4731
4732
	// Try to make sure substep was reset.
4733
	if ($upcontext['cur_table_num'] == $upcontext['table_count'])
4734
		echo '
4735
					<input type="hidden" name="substep" id="substep" value="0">';
4736
4737
	// Continue please!
4738
	$upcontext['continue'] = $support_js ? 2 : 1;
4739
4740
	// If javascript allows we want to do this using XML.
4741
	if ($support_js)
4742
	{
4743
		echo '
4744
					<script>
4745
						var lastTable = ', $upcontext['cur_table_num'], ';
4746
						function getNextTables()
4747
						{
4748
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4749
						}
4750
4751
						// Got an update!
4752
						function onBackupUpdate(oXMLDoc)
4753
						{
4754
							var sCurrentTableName = "";
4755
							var iTableNum = 0;
4756
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4757
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4758
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4759
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4760
4761
							// Update the page.
4762
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4763
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4764
							lastTable = iTableNum;
4765
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4766
4767
		// If debug flood the screen.
4768
		if ($is_debug)
4769
			echo '
4770
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4771
4772
							if (document.getElementById(\'debug_section\').scrollHeight)
4773
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4774
4775
		echo '
4776
							// Get the next update...
4777
							if (iTableNum == ', $upcontext['table_count'], ')
4778
							{
4779
								document.getElementById(\'commess\').classList.remove("hidden");
4780
								document.getElementById(\'current_tab\').classList.add("hidden");
4781
								document.getElementById(\'contbutt\').disabled = 0;
4782
								document.getElementById(\'json_done\').value = 1;
4783
							}
4784
							else
4785
								getNextTables();
4786
						}
4787
						getNextTables();
4788
					//# sourceURL=dynamicScript-json.js
4789
					</script>';
4790
	}
4791
}
4792
4793
function template_serialize_json_xml()
4794
{
4795
	global $upcontext;
4796
4797
	echo '
4798
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4799
}
4800
4801
function template_upgrade_complete()
4802
{
4803
	global $upcontext, $upgradeurl, $settings, $boardurl, $is_debug, $txt;
4804
4805
	echo '
4806
				<h3>', $txt['upgrade_done'], ' <a href="', $boardurl, '/index.php">', $txt['upgrade_done2'], '</a>.  ', $txt['upgrade_done3'], '</h3>
4807
				<form action="', $boardurl, '/index.php">';
4808
4809
	if (!empty($upcontext['can_delete_script']))
4810
		echo '
4811
					<label>
4812
						<input type="checkbox" id="delete_self" onclick="doTheDelete(this);"> ', $txt['upgrade_delete_now'], '
4813
					</label>
4814
					<em>', $txt['upgrade_delete_server'], '</em>
4815
					<script>
4816
						function doTheDelete(theCheck)
4817
						{
4818
							var theImage = document.getElementById ? document.getElementById("delete_upgrader") : document.all.delete_upgrader;
4819
							theImage.src = "', $upgradeurl, '?delete=1&ts_" + (new Date().getTime());
4820
							theCheck.disabled = true;
4821
						}
4822
					</script>
4823
					<img src="', $settings['default_theme_url'], '/images/blank.png" alt="" id="delete_upgrader"><br>';
4824
4825
	// Show Upgrade time in debug mode when we completed the upgrade process totally
4826
	if ($is_debug)
4827
	{
4828
		$active = time() - $upcontext['started'];
4829
		$hours = floor($active / 3600);
4830
		$minutes = intval(($active / 60) % 60);
4831
		$seconds = intval($active % 60);
4832
4833
		if ($hours > 0)
4834
			echo '', sprintf($txt['upgrade_completed_time_hms'], $seconds, $minutes, $hours), '';
4835
		elseif ($minutes > 0)
4836
			echo '', sprintf($txt['upgrade_completed_time_ms'], $seconds, $minutes), '';
4837
		elseif ($seconds > 0)
4838
			echo '', sprintf($txt['upgrade_completed_time_s'], $seconds), '';
4839
	}
4840
4841
	echo '
4842
					<p>
4843
						', sprintf($txt['upgrade_problems'], 'http://simplemachines.org'), '
4844
						<br>
4845
						', $txt['upgrade_luck'], '<br>
4846
						Simple Machines
4847
					</p>';
4848
}
4849
4850
/**
4851
 * Convert MySQL (var)char ip col to binary
4852
 *
4853
 * @param string $targetTable The table to perform the operation on
4854
 * @param string $oldCol The old column to gather data from
4855
 * @param string $newCol The new column to put data in
4856
 * @param int $limit The amount of entries to handle at once.
4857
 * @param int $setSize The amount of entries after which to update the database.
4858
 *
4859
 * newCol needs to be a varbinary(16) null able field
4860
 * @return bool
4861
 */
4862
function MySQLConvertOldIp($targetTable, $oldCol, $newCol, $limit = 50000, $setSize = 100)
4863
{
4864
	global $smcFunc, $step_progress;
4865
4866
	$current_substep = $_GET['substep'];
4867
4868
	if (empty($_GET['a']))
4869
		$_GET['a'] = 0;
4870
	$step_progress['name'] = 'Converting ips';
4871
	$step_progress['current'] = $_GET['a'];
4872
4873
	// Skip this if we don't have the column
4874
	$request = $smcFunc['db_query']('', '
4875
		SHOW FIELDS
4876
		FROM {db_prefix}{raw:table}
4877
		WHERE Field = {string:name}',
4878
		array(
4879
			'table' => $targetTable,
4880
			'name' => $oldCol,
4881
		)
4882
	);
4883
	if ($smcFunc['db_num_rows']($request) !== 1)
4884
	{
4885
		$smcFunc['db_free_result']($request);
4886
		return;
4887
	}
4888
	$smcFunc['db_free_result']($request);
4889
4890
	$is_done = false;
4891
	while (!$is_done)
4892
	{
4893
		// Keep looping at the current step.
4894
		nextSubstep($current_substep);
4895
4896
		// mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html
4897
		$arIp = array();
4898
4899
		$request = $smcFunc['db_query']('', '
4900
			SELECT DISTINCT {raw:old_col}
4901
			FROM {db_prefix}{raw:table_name}
4902
			WHERE {raw:new_col} IS NULL AND
4903
				{raw:old_col} != {string:unknown} AND
4904
				{raw:old_col} != {string:empty}
4905
			LIMIT {int:limit}',
4906
			array(
4907
				'old_col' => $oldCol,
4908
				'new_col' => $newCol,
4909
				'table_name' => $targetTable,
4910
				'empty' => '',
4911
				'limit' => $limit,
4912
				'unknown' => 'unknown',
4913
			)
4914
		);
4915
		while ($row = $smcFunc['db_fetch_assoc']($request))
4916
			$arIp[] = $row[$oldCol];
4917
4918
		$smcFunc['db_free_result']($request);
4919
4920
		// Special case, null ip could keep us in a loop.
4921
		if (is_null($arIp[0]))
4922
			unset($arIp[0]);
4923
4924
		if (empty($arIp))
4925
			$is_done = true;
4926
4927
		$updates = array();
4928
		$cases = array();
4929
		$count = count($arIp);
4930
		for ($i = 0; $i < $count; $i++)
4931
		{
4932
			$arIp[$i] = trim($arIp[$i]);
4933
4934
			if (empty($arIp[$i]))
4935
				continue;
4936
4937
			$updates['ip' . $i] = $arIp[$i];
4938
			$cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:ip' . $i . '}';
4939
4940
			if ($setSize > 0 && $i % $setSize === 0)
4941
			{
4942
				if (count($updates) == 1)
4943
					continue;
4944
4945
				$updates['whereSet'] = array_values($updates);
4946
				$smcFunc['db_query']('', '
4947
					UPDATE {db_prefix}' . $targetTable . '
4948
					SET ' . $newCol . ' = CASE ' .
4949
					implode('
4950
						', $cases) . '
4951
						ELSE NULL
4952
					END
4953
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
4954
					$updates
4955
				);
4956
4957
				$updates = array();
4958
				$cases = array();
4959
			}
4960
		}
4961
4962
		// Incase some extras made it through.
4963
		if (!empty($updates))
4964
		{
4965
			if (count($updates) == 1)
4966
			{
4967
				foreach ($updates as $key => $ip)
4968
				{
4969
					$smcFunc['db_query']('', '
4970
						UPDATE {db_prefix}' . $targetTable . '
4971
						SET ' . $newCol . ' = {inet:ip}
4972
						WHERE ' . $oldCol . ' = {string:ip}',
4973
						array(
4974
							'ip' => $ip
4975
						)
4976
					);
4977
				}
4978
			}
4979
			else
4980
			{
4981
				$updates['whereSet'] = array_values($updates);
4982
				$smcFunc['db_query']('', '
4983
					UPDATE {db_prefix}' . $targetTable . '
4984
					SET ' . $newCol . ' = CASE ' .
4985
					implode('
4986
						', $cases) . '
4987
						ELSE NULL
4988
					END
4989
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
4990
					$updates
4991
				);
4992
			}
4993
		}
4994
		else
4995
			$is_done = true;
4996
4997
		$_GET['a'] += $limit;
4998
		$step_progress['current'] = $_GET['a'];
4999
	}
5000
5001
	unset($_GET['a']);
5002
}
5003
5004
/**
5005
 * Get the column info. This is basically the same as smf_db_list_columns but we get 1 column, force detail and other checks.
5006
 *
5007
 * @param string $targetTable The table to perform the operation on
5008
 * @param string $column The column we are looking for.
5009
 *
5010
 * @return array Info on the table.
5011
 */
5012
function upgradeGetColumnInfo($targetTable, $column)
5013
{
5014
	global $smcFunc;
5015
5016
	// This should already be here, but be safe.
5017
	db_extend('packages');
5018
5019
	$columns = $smcFunc['db_list_columns']($targetTable, true);
5020
5021
	if (isset($columns[$column]))
5022
		return $columns[$column];
5023
	else
5024
		return null;
5025
}
5026
5027
/**
5028
 * Takes the changes to be made during the upgradeOptions step, grabs all known Settings data from Settings.php, then runs
5029
 * through a process to rebuild onto a brand new Settings template.  This should only be done if detection believes the
5030
 * settings file isn't using any advanced configuration setups in the Settings.php file.  A copy is made as Settings_org.php
5031
 * to preserve all changes prior to migration.
5032
 *
5033
 * @param array $config_vars An array of one or more variables to update
5034
 *
5035
 * @return void We either successfully update the Settings file, or throw a error here.
5036
 */
5037
function migrateSettingsFile($changes)
5038
{
5039
	global $boarddir, $cachedir, $txt;
5040
5041
	// Try to find all of these settings.
5042
	$settingsVars = array(
5043
		'maintenance' => 'int',
5044
		'mtitle' => 'string',
5045
		'mmessage' => 'string',
5046
		'mbname' => 'string',
5047
		'language' => 'string',
5048
		'boardurl' => 'string',
5049
		'webmaster_email' => 'string',
5050
		'cookiename' => 'string',
5051
		'db_type' => 'string',
5052
		'db_port' => 'int',
5053
		'db_server' => 'string_fatal',
5054
		'db_name' => 'string_fatal',
5055
		'db_user' => 'string_fatal',
5056
		'db_passwd' => 'string_fatal',
5057
		'ssi_db_user' => 'string',
5058
		'ssi_db_passwd' => 'string',
5059
		'db_prefix' => 'string_fatal',
5060
		'db_persist' => 'int',
5061
		'db_error_send' => 'int',
5062
		'db_mb4' => 'null',
5063
		'cache_accelerator' => 'string',
5064
		'cache_enable' => 'int',
5065
		'cache_memcached' => 'string',
5066
		'cachedir' => 'string',
5067
		'image_proxy_enabled' => 'bool',
5068
		'image_proxy_secret' => 'string',
5069
		'image_proxy_maxsize' => 'int',
5070
		'boarddir' => 'string',
5071
		'sourcedir' => 'string',
5072
		'packagesdir' => 'string',
5073
		'tasksdir' => 'string',
5074
		'db_character_set' => 'string',
5075
	);
5076
5077
	// The Settings file, in an array as if it was handled by updateSettingsFile
5078
	$settingsArray = array(
5079
		'<' . '?' . 'php',
5080
		'',
5081
		'/**',
5082
		' * The settings file contains all of the basic settings that need to be present when a database/cache is not available.',
5083
		' *',
5084
		' * Simple Machines Forum (SMF)',
5085
		' *',
5086
		' * @package SMF',
5087
		' * @author Simple Machines http://www.simplemachines.org',
5088
		' * @copyright ' . SMF_SOFTWARE_YEAR . ' Simple Machines and individual contributors',
5089
		' * @license http://www.simplemachines.org/about/smf/license.php BSD',
5090
		' *',
5091
		' * @version ' . SMF_VERSION,
5092
		' */',
5093
		'',
5094
		'########## Maintenance ##########',
5095
		'/**',
5096
		' * The maintenance "mode"',
5097
		' * Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you\'ll have to make it 0 again manually!)',
5098
		' * 0 is default and disables maintenance mode.',
5099
		' * @var int 0, 1, 2',
5100
		' * @global int $maintenance',
5101
		' */',
5102
		'$maintenance = 0;',
5103
		'/**',
5104
		' * Title for the Maintenance Mode message.',
5105
		' * @var string',
5106
		' * @global int $mtitle',
5107
		' */',
5108
		'$mtitle = \'Maintenance Mode\';',
5109
		'/**',
5110
		' * Description of why the forum is in maintenance mode.',
5111
		' * @var string',
5112
		' * @global string $mmessage',
5113
		' */',
5114
		'$mmessage = \'Okay faithful users...we\\\'re attempting to restore an older backup of the database...news will be posted once we\\\'re back!\';',
5115
		'',
5116
		'########## Forum Info ##########',
5117
		'/**',
5118
		' * The name of your forum.',
5119
		' * @var string',
5120
		' */',
5121
		'$mbname = \'My Community\';',
5122
		'/**',
5123
		' * The default language file set for the forum.',
5124
		' * @var string',
5125
		' */',
5126
		'$language = \'english\';',
5127
		'/**',
5128
		' * URL to your forum\'s folder. (without the trailing /!)',
5129
		' * @var string',
5130
		' */',
5131
		'$boardurl = \'http://127.0.0.1/smf\';',
5132
		'/**',
5133
		' * Email address to send emails from. (like [email protected].)',
5134
		' * @var string',
5135
		' */',
5136
		'$webmaster_email = \'[email protected]\';',
5137
		'/**',
5138
		' * Name of the cookie to set for authentication.',
5139
		' * @var string',
5140
		' */',
5141
		'$cookiename = \'SMFCookie21\';',
5142
		'',
5143
		'########## Database Info ##########',
5144
		'/**',
5145
		' * The database type',
5146
		' * Default options: mysql, postgresql',
5147
		' * @var string',
5148
		' */',
5149
		'$db_type = \'mysql\';',
5150
		'/**',
5151
		' * The database port',
5152
		' * 0 to use default port for the database type',
5153
		' * @var int',
5154
		' */',
5155
		'$db_port = 0;',
5156
		'/**',
5157
		' * The server to connect to (or a Unix socket)',
5158
		' * @var string',
5159
		' */',
5160
		'$db_server = \'localhost\';',
5161
		'/**',
5162
		' * The database name',
5163
		' * @var string',
5164
		' */',
5165
		'$db_name = \'smf\';',
5166
		'/**',
5167
		' * Database username',
5168
		' * @var string',
5169
		' */',
5170
		'$db_user = \'root\';',
5171
		'/**',
5172
		' * Database password',
5173
		' * @var string',
5174
		' */',
5175
		'$db_passwd = \'\';',
5176
		'/**',
5177
		' * Database user for when connecting with SSI',
5178
		' * @var string',
5179
		' */',
5180
		'$ssi_db_user = \'\';',
5181
		'/**',
5182
		' * Database password for when connecting with SSI',
5183
		' * @var string',
5184
		' */',
5185
		'$ssi_db_passwd = \'\';',
5186
		'/**',
5187
		' * A prefix to put in front of your table names.',
5188
		' * This helps to prevent conflicts',
5189
		' * @var string',
5190
		' */',
5191
		'$db_prefix = \'smf_\';',
5192
		'/**',
5193
		' * Use a persistent database connection',
5194
		' * @var int|bool',
5195
		' */',
5196
		'$db_persist = 0;',
5197
		'/**',
5198
		' *',
5199
		' * @var int|bool',
5200
		' */',
5201
		'$db_error_send = 0;',
5202
		'/**',
5203
		' * Override the default behavior of the database layer for mb4 handling',
5204
		' * null keep the default behavior untouched',
5205
		' * @var null|bool',
5206
		' */',
5207
		'$db_mb4 = null;',
5208
		'',
5209
		'########## Cache Info ##########',
5210
		'/**',
5211
		' * Select a cache system. You want to leave this up to the cache area of the admin panel for',
5212
		' * proper detection of apc, memcached, output_cache, smf, or xcache',
5213
		' * (you can add more with a mod).',
5214
		' * @var string',
5215
		' */',
5216
		'$cache_accelerator = \'\';',
5217
		'/**',
5218
		' * The level at which you would like to cache. Between 0 (off) through 3 (cache a lot).',
5219
		' * @var int',
5220
		' */',
5221
		'$cache_enable = 0;',
5222
		'/**',
5223
		' * This is only used for memcache / memcached. Should be a string of \'server:port,server:port\'',
5224
		' * @var array',
5225
		' */',
5226
		'$cache_memcached = \'\';',
5227
		'/**',
5228
		' * This is only for the \'smf\' file cache system. It is the path to the cache directory.',
5229
		' * It is also recommended that you place this in /tmp/ if you are going to use this.',
5230
		' * @var string',
5231
		' */',
5232
		'$cachedir = dirname(__FILE__) . \'/cache\';',
5233
		'',
5234
		'########## Image Proxy ##########',
5235
		'# This is done entirely in Settings.php to avoid loading the DB while serving the images',
5236
		'/**',
5237
		' * Whether the proxy is enabled or not',
5238
		' * @var bool',
5239
		' */',
5240
		'$image_proxy_enabled = true;',
5241
		'',
5242
		'/**',
5243
		' * Secret key to be used by the proxy',
5244
		' * @var string',
5245
		' */',
5246
		'$image_proxy_secret = \'smfisawesome\';',
5247
		'',
5248
		'/**',
5249
		' * Maximum file size (in KB) for individual files',
5250
		' * @var int',
5251
		' */',
5252
		'$image_proxy_maxsize = 5192;',
5253
		'',
5254
		'########## Directories/Files ##########',
5255
		'# Note: These directories do not have to be changed unless you move things.',
5256
		'/**',
5257
		' * The absolute path to the forum\'s folder. (not just \'.\'!)',
5258
		' * @var string',
5259
		' */',
5260
		'$boarddir = dirname(__FILE__);',
5261
		'/**',
5262
		' * Path to the Sources directory.',
5263
		' * @var string',
5264
		' */',
5265
		'$sourcedir = dirname(__FILE__) . \'/Sources\';',
5266
		'/**',
5267
		' * Path to the Packages directory.',
5268
		' * @var string',
5269
		' */',
5270
		'$packagesdir = dirname(__FILE__) . \'/Packages\';',
5271
		'/**',
5272
		' * Path to the tasks directory.',
5273
		' * @var string',
5274
		' */',
5275
		'$tasksdir = $sourcedir . \'/tasks\';',
5276
		'',
5277
		'# Make sure the paths are correct... at least try to fix them.',
5278
		'if (!file_exists($boarddir) && file_exists(dirname(__FILE__) . \'/agreement.txt\'))',
5279
		'	$boarddir = dirname(__FILE__);',
5280
		'if (!file_exists($sourcedir) && file_exists($boarddir . \'/Sources\'))',
5281
		'	$sourcedir = $boarddir . \'/Sources\';',
5282
		'if (!file_exists($cachedir) && file_exists($boarddir . \'/cache\'))',
5283
		'	$cachedir = $boarddir . \'/cache\';',
5284
		'',
5285
		'######### Legacy Settings #########',
5286
		'# UTF-8 is now the only character set supported in 2.1.',
5287
		'$db_character_set = \'utf8\';',
5288
		'',
5289
		'########## Error-Catching ##########',
5290
		'# Note: You shouldn\'t touch these settings.',
5291
		'if (file_exists((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\'))',
5292
		'	include((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\');',
5293
		'',
5294
		'if (!isset($db_last_error))',
5295
		'{',
5296
		'	// File does not exist so lets try to create it',
5297
		'	file_put_contents((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\', \'<\' . \'?\' . "php\n" . \'$db_last_error = 0;\' . "\n" . \'?\' . \'>\');',
5298
		'	$db_last_error = 0;',
5299
		'}',
5300
		'',
5301
		'?' . '>',
5302
	);
5303
5304
	// Now, find all of the original settings.  Mark those for the "change".
5305
	$original = array();
5306
	foreach ($settingsVars as $setVar => $setType)
5307
	{
5308
		global $$setVar;
5309
5310
		// Find the setting.
5311
		if ($setType == 'string' || $setType == 'string_fatal')
5312
			$original[$setVar] = isset($$setVar) ? '\'' . addcslashes($$setVar, '\'\\') . '\'' : (strpos('fatal', $setType) ? null : '\'\'');
5313
		elseif ($setType == 'int' || $setType == 'int_fatal')
5314
			$original[$setVar] = isset($$setVar) ? (int) $$setVar : (strpos('fatal', $setType) ? null : 0);
5315
		elseif ($setType == 'bool' || $setType == 'bool_fatal')
5316
			$original[$setVar] = isset($$setVar) && in_array($$setVar, array(1, true)) ? 'true' : (strpos('fatal', $setType) ? null : 'false');
5317
		elseif ($setType == 'null' || $setType == 'null_fatal')
5318
			$original[$setVar] = isset($$setVar) && in_array($$setVar, array(1, true)) ? 'true' : (strpos('fatal', $setType) ? null : 'null');
5319
5320
		// Well this isn't good.  Do we fix it or bail?
5321
		if (is_null($original) && $setType != 'null' && strpos('fatal', $setType) > -1)
5322
			return throw_error(sprintf($txt['error_settings_migration_no_var'], $setVar));
5323
	}
5324
5325
	// Finally, merge the changes with the new ones.
5326
	$config_vars = $original;
5327
	foreach ($changes as $setVar => $value)
5328
	{
5329
		// Nothing needed here.
5330
		if ($setVar != 'upgradeData' && $config_vars[$setVar] == $changes[$setVar])
5331
			continue;
5332
5333
		$config_vars[$setVar] = $value;
5334
	}
5335
5336
	/*
5337
		It would be nice to call updateSettingsFile and be done with this. However the function doesn't support passing in the entire file. We also want to backup with a different name, just incase.
5338
	*/
5339
5340
	// When was Settings.php last changed?
5341
	$last_settings_change = filemtime($boarddir . '/Settings.php');
5342
5343
	// remove any \r's that made their way in here
5344
	foreach ($settingsArray as $k => $dummy)
5345
		$settingsArray[$k] = strtr($dummy, array("\r" => '')) . "\n";
5346
5347
	// go line by line and see whats changing
5348
	for ($i = 0, $n = count($settingsArray); $i < $n; $i++)
5349
	{
5350
		// Don't trim or bother with it if it's not a variable.
5351
		if (substr($settingsArray[$i], 0, 1) != '$')
5352
			continue;
5353
5354
		$settingsArray[$i] = trim($settingsArray[$i]) . "\n";
5355
5356
		// Look through the variables to set....
5357
		foreach ($config_vars as $var => $val)
5358
		{
5359
			// be sure someone is not updating db_last_error this with a group
5360
			if ($var === 'db_last_error')
5361
				unset($config_vars[$var]);
5362
			elseif (strncasecmp($settingsArray[$i], '$' . $var, 1 + strlen($var)) == 0)
5363
			{
5364
				$comment = strstr(substr($settingsArray[$i], strpos($settingsArray[$i], ';')), '#');
5365
				$settingsArray[$i] = '$' . $var . ' = ' . $val . ';' . ($comment == '' ? '' : "\t\t" . rtrim($comment)) . "\n";
5366
5367
				// This one's been 'used', so to speak.
5368
				unset($config_vars[$var]);
5369
			}
5370
		}
5371
5372
		// End of the file ... maybe
5373
		if (substr(trim($settingsArray[$i]), 0, 2) == '?' . '>')
5374
			$end = $i;
5375
	}
5376
5377
	// This should never happen, but apparently it is happening.
5378
	if (empty($end) || $end < 10)
5379
		$end = count($settingsArray) - 1;
5380
5381
	// Still more variables to go?  Then lets add them at the end.
5382
	if (!empty($config_vars))
5383
	{
5384
		if (trim($settingsArray[$end]) == '?' . '>')
5385
			$settingsArray[$end++] = '';
5386
		else
5387
			$end++;
5388
5389
		// Add in any newly defined vars that were passed
5390
		foreach ($config_vars as $var => $val)
5391
			$settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n";
5392
5393
		$settingsArray[$end] = '?' . '>';
5394
	}
5395
	else
5396
		$settingsArray[$end] = trim($settingsArray[$end]);
5397
5398
	// Sanity error checking: the file needs to be at least 12 lines.
5399
	if (count($settingsArray) < 12)
5400
		return throw_error($txt['error_settings_migration_too_short']);
5401
5402
	// Try to avoid a few pitfalls:
5403
	//  - like a possible race condition,
5404
	//  - or a failure to write at low diskspace
5405
	//
5406
	// Check before you act: if cache is enabled, we can do a simple write test
5407
	// to validate that we even write things on this filesystem.
5408
	if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
5409
		$cachedir = $boarddir . '/cache';
5410
5411
	$test_fp = @fopen($cachedir . '/settings_update.tmp', "w+");
5412
	if ($test_fp !== false)
5413
	{
5414
		fclose($test_fp);
5415
		$written_bytes = file_put_contents($cachedir . '/settings_update.tmp', 'test', LOCK_EX);
5416
		@unlink($cachedir . '/settings_update.tmp');
5417
5418
		// Oops. Low disk space, perhaps. Don't mess with Settings.php then.
5419
		// No means no. :P
5420
		if ($written_bytes !== 4)
5421
			return throw_error($txt['error_settings_migration_write_failed']);
5422
	}
5423
5424
	// Protect me from what I want! :P
5425
	clearstatcache();
5426
	if (filemtime($boarddir . '/Settings.php') === $last_settings_change)
5427
	{
5428
		// save the old before we do anything
5429
		$settings_backup_fail = !@is_writable($boarddir . '/Settings_org.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_org.php');
5430
		$settings_backup_fail = !$settings_backup_fail ? (!file_exists($boarddir . '/Settings_org.php') || filesize($boarddir . '/Settings_org.php') === 0) : $settings_backup_fail;
5431
5432
		// write out the new
5433
		$write_settings = implode('', $settingsArray);
5434
		$written_bytes = file_put_contents($boarddir . '/Settings.php', $write_settings, LOCK_EX);
5435
5436
		// survey says ...
5437
		if ($written_bytes !== strlen($write_settings) && !$settings_backup_fail)
5438
		{
5439
			if (file_exists($boarddir . '/Settings_bak.php'))
5440
				@copy($boarddir . '/Settings_bak.php', $boarddir . '/Settings.php');
5441
5442
			return throw_error($txt['error_settings_migration_general']);
5443
		}
5444
	}
5445
5446
	// Even though on normal installations the filemtime should prevent this being used by the installer incorrectly
5447
	// it seems that there are times it might not. So let's MAKE it dump the cache.
5448
	if (function_exists('opcache_invalidate'))
5449
		opcache_invalidate($boarddir . '/Settings.php', true);
5450
}
5451
5452
/**
5453
 * Determine if we should auto select the migrate Settings file.  This is determined by a variety of missing settings.
5454
 * Prior to checking these settings, we look for advanced setups such as integrations or if variables have been moved
5455
 * to another file.  If these are detected, we abort.
5456
 *
5457
 * @param array $config_vars An array of one or more variables to update
5458
 *
5459
 * @return bool We either successfully update the Settings file, or throw a error here.
5460
 */
5461
function detectSettingsFileMigrationNeeded()
5462
{
5463
	global $boarddir, $packagesdir, $tasksdir, $db_server, $db_type, $image_proxy_enabled, $db_show_debug;
5464
5465
	// We should not migrate if db_show_debug is in there, some dev stuff going on here.
5466
	if (isset($db_show_debug))
5467
		return false;
5468
5469
	$file_contents = file_get_contents($boarddir . '/Settings.php');
5470
5471
	// Is there a include statement somewhere in there? Some advanced handling of the variables elsewhere?
5472
	// Try our best to stay away from the cachedir match.
5473
	if (preg_match('~\sinclude\((?:(?!\(isset\(\$cachedir))~im', $file_contents))
5474
		return false;
5475
5476
	// If we find a mention of $GLOBALS, there may be a integration going on.
5477
	if (preg_match('~\$GLOBALS\[~im', $file_contents))
5478
		return false;
5479
5480
	// If these are not set, it makes us a candidate to migrate.
5481
	if (!isset($packagesdir, $tasksdir, $db_server, $db_type, $image_proxy_enabled))
5482
		return true;
5483
5484
	return false;
5485
}
5486
5487
?>