Passed
Pull Request — release-2.1 (#5595)
by Mathias
06:10
created

other/upgrade.php (1 issue)

Severity
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
21
/**
22
 * The minimum required PHP version.
23
 *
24
 * @var string
25
 */
26
$GLOBALS['required_php_version'] = '5.4.0';
27
28
/**
29
 * A list of supported database systems.
30
 *
31
 * @var array
32
 */
33
$databases = array(
34
	'mysql' => array(
35
		'name' => 'MySQL',
36
		'version' => '5.0.22',
37
		'version_check' => 'global $db_connection; return min(mysqli_get_server_info($db_connection), mysqli_get_client_info());',
38
		'utf8_support' => true,
39
		'utf8_version' => '5.0.22',
40
		'utf8_version_check' => 'global $db_connection; return mysqli_get_server_info($db_connection);',
41
		'alter_support' => true,
42
	),
43
	'postgresql' => array(
44
		'name' => 'PostgreSQL',
45
		'version' => '9.4',
46
		'version_check' => '$version = pg_version(); return $version[\'client\'];',
47
		'always_has_db' => true,
48
	),
49
);
50
51
/**
52
 * The maximum time a single substep may take, in seconds.
53
 *
54
 * @var int
55
 */
56
$timeLimitThreshold = 3;
57
58
/**
59
 * The current path to the upgrade.php file.
60
 *
61
 * @var string
62
 */
63
$upgrade_path = dirname(__FILE__);
64
65
/**
66
 * The URL of the current page.
67
 *
68
 * @var string
69
 */
70
$upgradeurl = $_SERVER['PHP_SELF'];
71
72
/**
73
 * Flag to disable the required administrator login.
74
 *
75
 * @var bool
76
 */
77
$disable_security = false;
78
79
/**
80
 * The amount of seconds allowed between logins.
81
 * If the first user to login is inactive for this amount of seconds, a second login is allowed.
82
 *
83
 * @var int
84
 */
85
$upcontext['inactive_timeout'] = 10;
86
87
global $txt;
88
89
// All the steps in detail.
90
// Number,Name,Function,Progress Weight.
91
$upcontext['steps'] = array(
92
	0 => array(1, 'upgrade_step_login', 'WelcomeLogin', 2),
93
	1 => array(2, 'upgrade_step_options', 'UpgradeOptions', 2),
94
	2 => array(3, 'upgrade_step_backup', 'BackupDatabase', 10),
95
	3 => array(4, 'upgrade_step_database', 'DatabaseChanges', 50),
96
	4 => array(5, 'upgrade_step_convertjson', 'serialize_to_json', 10),
97
	5 => array(6, 'upgrade_step_convertutf', 'ConvertUtf8', 20),
98
	6 => array(7, 'upgrade_step_delete', 'DeleteUpgrade', 1),
99
);
100
// Just to remember which one has files in it.
101
$upcontext['database_step'] = 3;
102
@set_time_limit(600);
103
if (!ini_get('safe_mode'))
104
{
105
	ini_set('mysql.connect_timeout', -1);
106
	ini_set('default_socket_timeout', 900);
107
}
108
// Clean the upgrade path if this is from the client.
109
if (!empty($_SERVER['argv']) && php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
110
	for ($i = 1; $i < $_SERVER['argc']; $i++)
111
	{
112
		if (preg_match('~^--path=(.+)$~', $_SERVER['argv'][$i], $match) != 0)
113
			$upgrade_path = substr($match[1], -1) == '/' ? substr($match[1], 0, -1) : $match[1];
114
	}
115
116
// Are we from the client?
117
if (php_sapi_name() == 'cli' && empty($_SERVER['REMOTE_ADDR']))
118
{
119
	$command_line = true;
120
	$disable_security = true;
121
}
122
else
123
	$command_line = false;
124
125
// We can't do anything without these files.
126
foreach (array('upgrade-helper.php', 'Settings.php') as $required_file)
127
{
128
	if (!file_exists($upgrade_path . '/' . $required_file))
129
		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.');
130
131
	require_once($upgrade_path . '/' . $required_file);
132
}
133
134
// We don't use "-utf8" anymore...  Tweak the entry that may have been loaded by Settings.php
135
if (isset($language))
136
	$language = str_ireplace('-utf8', '', basename($language, '.lng'));
137
138
// Figure out a valid language request (if any)
139
// 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.
140
if (isset($_SERVER['QUERY_STRING']) && preg_match('~\blang=(\w+)~', $_SERVER['QUERY_STRING'], $matches))
141
	$upcontext['lang'] = $matches[1];
142
143
// Are we logged in?
144
if (isset($upgradeData))
145
{
146
	$upcontext['user'] = json_decode(base64_decode($upgradeData), true);
147
148
	// Check for sensible values.
149
	if (empty($upcontext['user']['started']) || $upcontext['user']['started'] < time() - 86400)
150
		$upcontext['user']['started'] = time();
151
	if (empty($upcontext['user']['updated']) || $upcontext['user']['updated'] < time() - 86400)
152
		$upcontext['user']['updated'] = 0;
153
154
	$upcontext['started'] = $upcontext['user']['started'];
155
	$upcontext['updated'] = $upcontext['user']['updated'];
156
157
	$is_debug = !empty($upcontext['user']['debug']) ? true : false;
158
159
	$upcontext['skip_db_substeps'] = !empty($upcontext['user']['skip_db_substeps']);
160
}
161
162
// Nothing sensible?
163
if (empty($upcontext['updated']))
164
{
165
	$upcontext['started'] = time();
166
	$upcontext['updated'] = 0;
167
	$upcontext['skip_db_substeps'] = false;
168
	$upcontext['user'] = array(
169
		'id' => 0,
170
		'name' => 'Guest',
171
		'pass' => 0,
172
		'started' => $upcontext['started'],
173
		'updated' => $upcontext['updated'],
174
	);
175
}
176
177
// Try to load the language file... or at least define a few necessary strings for now.
178
load_lang_file();
179
180
// Load up some essential data...
181
loadEssentialData();
182
183
// Are we going to be mimic'ing SSI at this point?
184
if (isset($_GET['ssi']))
185
{
186
	require_once($sourcedir . '/Errors.php');
187
	require_once($sourcedir . '/Logging.php');
188
	require_once($sourcedir . '/Load.php');
189
	require_once($sourcedir . '/Security.php');
190
	require_once($sourcedir . '/Subs-Package.php');
191
192
	// SMF isn't started up properly, but loadUserSettings calls our cookies.
193
	if (!isset($smcFunc['json_encode']))
194
	{
195
		$smcFunc['json_encode'] = 'json_encode';
196
		$smcFunc['json_decode'] = 'smf_json_decode';
197
	}
198
199
	loadUserSettings();
200
	loadPermissions();
201
}
202
203
// Include our helper functions.
204
require_once($sourcedir . '/Subs.php');
205
require_once($sourcedir . '/LogInOut.php');
206
207
// This only exists if we're on SMF ;)
208
if (isset($modSettings['smfVersion']))
209
{
210
	$request = $smcFunc['db_query']('', '
211
		SELECT variable, value
212
		FROM {db_prefix}themes
213
		WHERE id_theme = {int:id_theme}
214
			AND variable IN ({string:theme_url}, {string:theme_dir}, {string:images_url})',
215
		array(
216
			'id_theme' => 1,
217
			'theme_url' => 'theme_url',
218
			'theme_dir' => 'theme_dir',
219
			'images_url' => 'images_url',
220
			'db_error_skip' => true,
221
		)
222
	);
223
	while ($row = $smcFunc['db_fetch_assoc']($request))
224
		$modSettings[$row['variable']] = $row['value'];
225
	$smcFunc['db_free_result']($request);
226
}
227
228
if (!isset($modSettings['theme_url']))
229
{
230
	$modSettings['theme_dir'] = $boarddir . '/Themes/default';
231
	$modSettings['theme_url'] = 'Themes/default';
232
	$modSettings['images_url'] = 'Themes/default/images';
233
}
234
if (!isset($settings['default_theme_url']))
235
	$settings['default_theme_url'] = $modSettings['theme_url'];
236
if (!isset($settings['default_theme_dir']))
237
	$settings['default_theme_dir'] = $modSettings['theme_dir'];
238
239
// This is needed in case someone invokes the upgrader using https when upgrading an http forum
240
if (httpsOn())
241
	$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
242
243
$upcontext['is_large_forum'] = (empty($modSettings['smfVersion']) || $modSettings['smfVersion'] <= '1.1 RC1') && !empty($modSettings['totalMessages']) && $modSettings['totalMessages'] > 75000;
244
245
// Have we got tracking data - if so use it (It will be clean!)
246
if (isset($_GET['data']))
247
{
248
	global $is_debug;
249
250
	$upcontext['upgrade_status'] = json_decode(base64_decode($_GET['data']), true);
251
	$upcontext['current_step'] = $upcontext['upgrade_status']['curstep'];
252
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
253
	$upcontext['rid'] = $upcontext['upgrade_status']['rid'];
254
	$support_js = $upcontext['upgrade_status']['js'];
255
256
	// Only set this if the upgrader status says so.
257
	if (empty($is_debug))
258
		$is_debug = $upcontext['upgrade_status']['debug'];
259
}
260
// Set the defaults.
261
else
262
{
263
	$upcontext['current_step'] = 0;
264
	$upcontext['rid'] = mt_rand(0, 5000);
265
	$upcontext['upgrade_status'] = array(
266
		'curstep' => 0,
267
		'lang' => isset($upcontext['lang']) ? $upcontext['lang'] : basename($language, '.lng'),
268
		'rid' => $upcontext['rid'],
269
		'pass' => 0,
270
		'debug' => 0,
271
		'js' => 0,
272
	);
273
	$upcontext['language'] = $upcontext['upgrade_status']['lang'];
274
}
275
276
// Now that we have the necessary info, make sure we loaded the right language file.
277
load_lang_file();
278
279
// Default title...
280
$upcontext['page_title'] = $txt['updating_smf_installation'];
281
282
// If this isn't the first stage see whether they are logging in and resuming.
283
if ($upcontext['current_step'] != 0 || !empty($upcontext['user']['step']))
284
	checkLogin();
285
286
if ($command_line)
287
	cmdStep0();
288
289
// Don't error if we're using xml.
290
if (isset($_GET['xml']))
291
	$upcontext['return_error'] = true;
292
293
// Loop through all the steps doing each one as required.
294
$upcontext['overall_percent'] = 0;
295
foreach ($upcontext['steps'] as $num => $step)
296
{
297
	if ($num >= $upcontext['current_step'])
298
	{
299
		// The current weight of this step in terms of overall progress.
300
		$upcontext['step_weight'] = $step[3];
301
		// Make sure we reset the skip button.
302
		$upcontext['skip'] = false;
303
304
		// We cannot proceed if we're not logged in.
305
		if ($num != 0 && !$disable_security && $upcontext['user']['pass'] != $upcontext['upgrade_status']['pass'])
306
		{
307
			$upcontext['steps'][0][2]();
308
			break;
309
		}
310
311
		// Call the step and if it returns false that means pause!
312
		if (function_exists($step[2]) && $step[2]() === false)
313
			break;
314
		elseif (function_exists($step[2]))
315
		{
316
			//Start each new step with this unset, so the 'normal' template is called first
317
			unset($_GET['xml']);
318
			//Clear out warnings at the start of each step
319
			unset($upcontext['custom_warning']);
320
			$_GET['substep'] = 0;
321
			$upcontext['current_step']++;
322
		}
323
	}
324
	$upcontext['overall_percent'] += $step[3];
325
}
326
327
upgradeExit();
328
329
// Exit the upgrade script.
330
function upgradeExit($fallThrough = false)
331
{
332
	global $upcontext, $upgradeurl, $sourcedir, $command_line, $is_debug, $txt;
333
334
	// Save where we are...
335
	if (!empty($upcontext['current_step']) && !empty($upcontext['user']['id']))
336
	{
337
		$upcontext['user']['step'] = $upcontext['current_step'];
338
		$upcontext['user']['substep'] = $_GET['substep'];
339
		$upcontext['user']['updated'] = time();
340
		$upcontext['user']['skip_db_substeps'] = !empty($upcontext['skip_db_substeps']);
341
		$upcontext['debug'] = $is_debug;
342
		$upgradeData = base64_encode(json_encode($upcontext['user']));
343
		require_once($sourcedir . '/Subs-Admin.php');
344
		updateSettingsFile(array('upgradeData' => '"' . $upgradeData . '"'));
345
		updateDbLastError(0);
0 ignored issues
show
The call to updateDbLastError() has too many arguments starting with 0. ( Ignorable by Annotation )

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

345
		/** @scrutinizer ignore-call */ 
346
  updateDbLastError(0);

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

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

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