Completed
Pull Request — release-2.1 (#5439)
by John
04:33
created

UpgradeOptions()   F

Complexity

Conditions 44
Paths > 20000

Size

Total Lines 257
Code Lines 125

Duplication

Lines 0
Ratio 0 %

Importance

Changes 2
Bugs 0 Features 0
Metric Value
cc 44
eloc 125
c 2
b 0
f 0
nc 82575362
nop 0
dl 0
loc 257
rs 0

How to fix   Long Method    Complexity   

Long Method

Small methods make your code easier to understand, in particular if combined with a good name. Besides, if your method is small, finding a good name is usually much easier.

For example, if you find yourself adding comments to a method's body, this is usually a good sign to extract the commented part to a new method, and use the comment as a starting point when coming up with a good name for this new method.

Commonly applied refactorings include:

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