loadEssentialData()   F
last analyzed

Complexity

Conditions 18
Paths 688

Size

Total Lines 109
Code Lines 58

Duplication

Lines 0
Ratio 0 %

Importance

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

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

347
		/** @scrutinizer ignore-call */ 
348
  updateDbLastError(0);

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

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

Loading history...
348
	}
349
350
	// Handle the progress of the step, if any.
351
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
352
	{
353
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
354
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
355
	}
356
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
357
358
	// We usually dump our templates out.
359
	if (!$fallThrough)
360
	{
361
		// This should not happen my dear... HELP ME DEVELOPERS!!
362
		if (!empty($command_line))
363
		{
364
			if (function_exists('debug_print_backtrace'))
365
				debug_print_backtrace();
366
367
			printf($txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
368
			flush();
369
			die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
370
		}
371
372
		if (!isset($_GET['xml']))
373
			template_upgrade_above();
374
		else
375
		{
376
			header('content-type: text/xml; charset=UTF-8');
377
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
378
			$upcontext['get_data'] = array();
379
			foreach ($_GET as $k => $v)
380
			{
381
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
382
				{
383
					$upcontext['get_data'][$k] = $v;
384
				}
385
			}
386
			template_xml_above();
387
		}
388
389
		// Call the template.
390
		if (isset($upcontext['sub_template']))
391
		{
392
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
393
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
394
395
			// Custom stuff to pass back?
396
			if (!empty($upcontext['query_string']))
397
				$upcontext['form_url'] .= $upcontext['query_string'];
398
399
			// Call the appropriate subtemplate
400
			if (is_callable('template_' . $upcontext['sub_template']))
401
				call_user_func('template_' . $upcontext['sub_template']);
402
			else
403
				die(sprintf($txt['error_invalid_template'], $upcontext['sub_template']));
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
404
		}
405
406
		// Was there an error?
407
		if (!empty($upcontext['forced_error_message']))
408
			echo $upcontext['forced_error_message'];
409
410
		// Show the footer.
411
		if (!isset($_GET['xml']))
412
			template_upgrade_below();
413
		else
414
			template_xml_below();
415
	}
416
417
	// Show the upgrade time for CLI when we are completely done, if in debug mode.
418
	if (!empty($command_line) && $is_debug)
419
	{
420
		$active = time() - $upcontext['started'];
421
		$hours = floor($active / 3600);
422
		$minutes = intval(($active / 60) % 60);
423
		$seconds = intval($active % 60);
424
425
		if ($hours > 0)
426
			echo "\n" . '', sprintf($txt['upgrade_completed_time_hms'], $hours, $minutes, $seconds), '' . "\n";
427
		elseif ($minutes > 0)
428
			echo "\n" . '', sprintf($txt['upgrade_completed_time_ms'], $minutes, $seconds), '' . "\n";
429
		elseif ($seconds > 0)
430
			echo "\n" . '', sprintf($txt['upgrade_completed_time_s'], $seconds), '' . "\n";
431
	}
432
433
	// Bang - gone!
434
	die();
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
435
}
436
437
// Load the list of language files, and the current language file.
438
function load_lang_file()
439
{
440
	global $txt, $upcontext, $language, $modSettings;
441
442
	static $lang_dir = '', $detected_languages = array(), $loaded_langfile = '';
443
444
	// Do we know where to look for the language files, or shall we just guess for now?
445
	$temp = isset($modSettings['theme_dir']) ? $modSettings['theme_dir'] . '/languages' : dirname(__FILE__) . '/Themes/default/languages';
446
447
	if ($lang_dir != $temp)
448
	{
449
		$lang_dir = $temp;
450
		$detected_languages = array();
451
	}
452
453
	// Override the language file?
454
	if (isset($upcontext['language']))
455
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['language'] . '.php';
456
	elseif (isset($upcontext['lang']))
457
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['lang'] . '.php';
458
	elseif (isset($language))
459
		$_SESSION['upgrader_langfile'] = 'Install.' . $language . '.php';
460
461
	// Avoid pointless repetition
462
	if (isset($_SESSION['upgrader_langfile']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_langfile'])
463
		return;
464
465
	// Now try to find the language files
466
	if (empty($detected_languages))
467
	{
468
		// Make sure the languages directory actually exists.
469
		if (file_exists($lang_dir))
470
		{
471
			// Find all the "Install" language files in the directory.
472
			$dir = dir($lang_dir);
473
			while ($entry = $dir->read())
474
			{
475
				// Skip any old '-utf8' language files that might be lying around
476
				if (strpos($entry, '-utf8') !== false)
477
					continue;
478
479
				if (substr($entry, 0, 8) == 'Install.' && substr($entry, -4) == '.php')
480
					$detected_languages[$entry] = ucfirst(substr($entry, 8, strlen($entry) - 12));
481
			}
482
			$dir->close();
483
		}
484
		// Our guess was wrong, but that's fine. We'll try again after $modSettings['theme_dir'] is defined.
485
		elseif (!isset($modSettings['theme_dir']))
486
		{
487
			// Define a few essential strings for now.
488
			$txt['error_db_connect_settings'] = 'Cannot connect to the database server.<br><br>Please check that the database info variables are correct in Settings.php.';
489
			$txt['error_sourcefile_missing'] = 'Unable to find the Sources/%1$s file. Please make sure it was uploaded properly, and then try again.';
490
491
			$txt['warning_lang_old'] = 'The language files for your selected language, %1$s, have not been updated to the latest version. Upgrade will continue with the forum default, %2$s.';
492
			$txt['warning_lang_missing'] = 'The upgrader could not find the &quot;Install&quot; language file for your selected language, %1$s. Upgrade will continue with the forum default, %2$s.';
493
494
			return;
495
		}
496
	}
497
498
	// Didn't find any, show an error message!
499
	if (empty($detected_languages))
500
	{
501
		$from = explode('/', $_SERVER['PHP_SELF']);
502
		$to = explode('/', $lang_dir);
503
		$relPath = $to;
504
505
		foreach($from as $depth => $dir)
506
		{
507
			if ($dir === $to[$depth])
508
				array_shift($relPath);
509
			else
510
			{
511
				$remaining = count($from) - $depth;
512
				if ($remaining > 1)
513
				{
514
					$padLength = (count($relPath) + $remaining - 1) * -1;
515
					$relPath = array_pad($relPath, $padLength, '..');
516
					break;
517
				}
518
				else
519
					$relPath[0] = './' . $relPath[0];
520
			}
521
		}
522
		$relPath = implode(DIRECTORY_SEPARATOR, $relPath);
523
524
		// Let's not cache this message, eh?
525
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
526
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
527
		header('Cache-Control: no-cache');
528
529
		echo '<!DOCTYPE html>
530
			<html>
531
				<head>
532
					<title>SMF Upgrader: Error!</title>
533
						<style>
534
							body {
535
								font-family: sans-serif;
536
								max-width: 700px; }
537
538
								h1 {
539
									font-size: 14pt; }
540
541
								.directory {
542
									margin: 0.3em;
543
									font-family: monospace;
544
									font-weight: bold; }
545
						</style>
546
				</head>
547
				<body>
548
					<h1>A critical error has occurred.</h1>
549
						<p>This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:</p>
550
						<div class="directory">', $relPath, '</div>
551
						<p>In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you <strong>have uploaded all the files in the distribution</strong>.</p>
552
						<p>If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.</p>
553
						<p>If you continue to get this error message, feel free to <a href="https://support.simplemachines.org/">look to us for support</a>.</p>
554
				</body>
555
			</html>';
556
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
557
	}
558
559
	// Make sure it exists. If it doesn't, reset it.
560
	if (!isset($_SESSION['upgrader_langfile']) || preg_match('~[^\w.-]~', $_SESSION['upgrader_langfile']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_langfile']))
561
	{
562
		// Use the first one...
563
		list ($_SESSION['upgrader_langfile']) = array_keys($detected_languages);
564
565
		// If we have English and some other language, use the other language.
566
		if ($_SESSION['upgrader_langfile'] == 'Install.english.php' && count($detected_languages) > 1)
567
			list (, $_SESSION['upgrader_langfile']) = array_keys($detected_languages);
568
	}
569
570
	// For backup we load English at first, then the second language will overwrite it.
571
	if ($_SESSION['upgrader_langfile'] != 'Install.english.php')
572
		require_once($lang_dir . '/Install.english.php');
573
574
	// And now include the actual language file itself.
575
	require_once($lang_dir . '/' . $_SESSION['upgrader_langfile']);
576
577
	// Remember what we've done
578
	$loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_langfile'];
579
}
580
581
// Used to direct the user to another location.
582
function redirectLocation($location, $addForm = true)
583
{
584
	global $upgradeurl, $upcontext, $command_line;
585
586
	// Command line users can't be redirected.
587
	if ($command_line)
588
		upgradeExit(true);
589
590
	// Are we providing the core info?
591
	if ($addForm)
592
	{
593
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
594
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
595
	}
596
597
	while (@ob_end_clean())
598
		header('location: ' . strtr($location, array('&amp;' => '&')));
599
600
	// Exit - saving status as we go.
601
	upgradeExit(true);
602
}
603
604
// Load all essential data and connect to the DB as this is pre SSI.php
605
function loadEssentialData()
606
{
607
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection;
608
	global $db_prefix, $db_character_set, $db_type, $db_port;
609
	global $db_mb4, $modSettings, $sourcedir, $smcFunc, $txt;
610
611
	error_reporting(E_ALL);
612
	define('SMF', 1);
613
614
	// Start the session.
615
	if (@ini_get('session.save_handler') == 'user')
616
		@ini_set('session.save_handler', 'files');
617
	@session_start();
618
619
	if (empty($smcFunc))
620
		$smcFunc = array();
621
622
	$smcFunc['random_int'] = function($min = 0, $max = PHP_INT_MAX)
623
	{
624
		global $sourcedir;
625
626
		// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
627
		if (!is_callable('random_int'))
628
			require_once($sourcedir . '/random_compat/random.php');
629
630
		return random_int($min, $max);
631
	};
632
633
	// We need this for authentication and some upgrade code
634
	require_once($sourcedir . '/Subs-Auth.php');
635
	require_once($sourcedir . '/Class-Package.php');
636
637
	$smcFunc['strtolower'] = 'smf_strtolower';
638
639
	// Initialize everything...
640
	initialize_inputs();
641
642
	// Get the database going!
643
	if (empty($db_type) || $db_type == 'mysqli')
644
	{
645
		$db_type = 'mysql';
646
		// If overriding $db_type, need to set its settings.php entry too
647
		$changes = array();
648
		$changes['db_type'] = '\'mysql\'';
649
		require_once($sourcedir . '/Subs-Admin.php');
650
		updateSettingsFile($changes);
651
	}
652
653
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
654
	{
655
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
656
657
		// Make the connection...
658
		if (empty($db_connection))
659
		{
660
			$options = array('non_fatal' => true);
661
			// Add in the port if needed
662
			if (!empty($db_port))
663
				$options['port'] = $db_port;
664
665
			if (!empty($db_mb4))
666
				$options['db_mb4'] = $db_mb4;
667
668
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
669
		}
670
		else
671
			// If we've returned here, ping/reconnect to be safe
672
			$smcFunc['db_ping']($db_connection);
673
674
		// Oh dear god!!
675
		if ($db_connection === null)
676
			die($txt['error_db_connect_settings']);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
677
678
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
679
			$smcFunc['db_query']('', '
680
				SET NAMES {string:db_character_set}',
681
				array(
682
					'db_error_skip' => true,
683
					'db_character_set' => $db_character_set,
684
				)
685
			);
686
687
		// Load the modSettings data...
688
		$request = $smcFunc['db_query']('', '
689
			SELECT variable, value
690
			FROM {db_prefix}settings',
691
			array(
692
				'db_error_skip' => true,
693
			)
694
		);
695
		$modSettings = array();
696
		while ($row = $smcFunc['db_fetch_assoc']($request))
697
			$modSettings[$row['variable']] = $row['value'];
698
		$smcFunc['db_free_result']($request);
699
	}
700
	else
701
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
702
703
	require_once($sourcedir . '/Subs.php');
704
705
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
706
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
707
	{
708
		require_once($sourcedir . '/QueryString.php');
709
		cleanRequest();
710
	}
711
712
	if (!isset($_GET['substep']))
713
		$_GET['substep'] = 0;
714
}
715
716
function initialize_inputs()
717
{
718
	global $start_time, $db_type;
719
720
	$start_time = time();
721
722
	umask(0);
723
724
	ob_start();
725
726
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
727
	ignore_user_abort(true);
728
729
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
730
	if (isset($_GET['delete']))
731
	{
732
		@unlink(__FILE__);
733
734
		// And the extra little files ;).
735
		@unlink(dirname(__FILE__) . '/upgrade_1-0.sql');
736
		@unlink(dirname(__FILE__) . '/upgrade_1-1.sql');
737
		@unlink(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
738
		@unlink(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
739
		@unlink(dirname(__FILE__) . '/upgrade-helper.php');
740
741
		$dh = opendir(dirname(__FILE__));
742
		while ($file = readdir($dh))
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of readdir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

742
		while ($file = readdir(/** @scrutinizer ignore-type */ $dh))
Loading history...
743
		{
744
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
745
				@unlink(dirname(__FILE__) . '/' . $file);
746
		}
747
		closedir($dh);
0 ignored issues
show
Bug introduced by
It seems like $dh can also be of type false; however, parameter $dir_handle of closedir() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

747
		closedir(/** @scrutinizer ignore-type */ $dh);
Loading history...
748
749
		// Legacy files while we're at it. NOTE: We only touch files we KNOW shouldn't be there.
750
		// 1.1 Sources files not in 2.0+
751
		@unlink(dirname(__FILE__) . '/Sources/ModSettings.php');
752
		// 1.1 Templates that don't exist any more (e.g. renamed)
753
		@unlink(dirname(__FILE__) . '/Themes/default/Combat.template.php');
754
		@unlink(dirname(__FILE__) . '/Themes/default/Modlog.template.php');
755
		// 1.1 JS files were stored in the main theme folder, but in 2.0+ are in the scripts/ folder
756
		@unlink(dirname(__FILE__) . '/Themes/default/fader.js');
757
		@unlink(dirname(__FILE__) . '/Themes/default/script.js');
758
		@unlink(dirname(__FILE__) . '/Themes/default/spellcheck.js');
759
		@unlink(dirname(__FILE__) . '/Themes/default/xml_board.js');
760
		@unlink(dirname(__FILE__) . '/Themes/default/xml_topic.js');
761
762
		// 2.0 Sources files not in 2.1+
763
		@unlink(dirname(__FILE__) . '/Sources/DumpDatabase.php');
764
		@unlink(dirname(__FILE__) . '/Sources/LockTopic.php');
765
766
		header('location: http://' . (isset($_SERVER['HTTP_HOST']) ? $_SERVER['HTTP_HOST'] : $_SERVER['SERVER_NAME'] . ':' . $_SERVER['SERVER_PORT']) . dirname($_SERVER['PHP_SELF']) . '/Themes/default/images/blank.png');
767
		exit;
768
	}
769
770
	// Something is causing this to happen, and it's annoying.  Stop it.
771
	$temp = 'upgrade_php?step';
772
	while (strlen($temp) > 4)
773
	{
774
		if (isset($_GET[$temp]))
775
			unset($_GET[$temp]);
776
		$temp = substr($temp, 1);
777
	}
778
779
	// Force a step, defaulting to 0.
780
	$_GET['step'] = (int) @$_GET['step'];
781
	$_GET['substep'] = (int) @$_GET['substep'];
782
}
783
784
// Step 0 - Let's welcome them in and ask them to login!
785
function WelcomeLogin()
786
{
787
	global $boarddir, $sourcedir, $modSettings, $cachedir, $upgradeurl, $upcontext;
788
	global $smcFunc, $db_type, $databases, $boardurl;
789
790
	// We global $txt here so that the language files can add to them. This variable is NOT unused.
791
	global $txt;
792
793
	$upcontext['sub_template'] = 'welcome_message';
794
795
	// Check for some key files - one template, one language, and a new and an old source file.
796
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
797
		&& @file_exists($sourcedir . '/QueryString.php')
798
		&& @file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php')
799
		&& @file_exists(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
800
801
	// Need legacy scripts?
802
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.1)
803
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
804
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 2.0)
805
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-1.sql');
806
	if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < 1.1)
807
		$check &= @file_exists(dirname(__FILE__) . '/upgrade_1-0.sql');
808
809
	// We don't need "-utf8" files anymore...
810
	$upcontext['language'] = str_ireplace('-utf8', '', $upcontext['language']);
811
812
	if (!$check)
813
		// Don't tell them what files exactly because it's a spot check - just like teachers don't tell which problems they are spot checking, that's dumb.
814
		return throw_error($txt['error_upgrade_files_missing']);
815
816
	// Do they meet the install requirements?
817
	if (!php_version_check())
818
		return throw_error($txt['error_php_too_low']);
819
820
	if (!db_version_check())
821
		return throw_error(sprintf($txt['error_db_too_low'], $databases[$db_type]['name']));
822
823
	// Do some checks to make sure they have proper privileges
824
	db_extend('packages');
825
826
	// CREATE
827
	$create = $smcFunc['db_create_table']('{db_prefix}priv_check', array(array('name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true)), array(array('columns' => array('id_test'), 'type' => 'primary')), array(), 'overwrite');
828
829
	// ALTER
830
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
831
832
	// DROP
833
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
834
835
	// Sorry... we need CREATE, ALTER and DROP
836
	if (!$create || !$alter || !$drop)
837
		return throw_error(sprintf($txt['error_db_privileges'], $databases[$db_type]['name']));
838
839
	// Do a quick version spot check.
840
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
0 ignored issues
show
Bug introduced by
It seems like @file($boarddir . '/index.php') can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

840
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
841
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
842
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
843
		return throw_error($txt['error_upgrade_old_files']);
844
845
	// What absolutely needs to be writable?
846
	$writable_files = array(
847
		$boarddir . '/Settings.php',
848
		$boarddir . '/Settings_bak.php',
849
	);
850
851
	// Only check for minified writable files if we have it enabled or not set.
852
	if (!empty($modSettings['minimize_files']) || !isset($modSettings['minimize_files']))
853
		$writable_files += array(
854
			$modSettings['theme_dir'] . '/css/minified.css',
855
			$modSettings['theme_dir'] . '/scripts/minified.js',
856
			$modSettings['theme_dir'] . '/scripts/minified_deferred.js',
857
		);
858
859
	// Do we need to add this setting?
860
	$need_settings_update = empty($modSettings['custom_avatar_dir']);
861
862
	$custom_av_dir = !empty($modSettings['custom_avatar_dir']) ? $modSettings['custom_avatar_dir'] : $GLOBALS['boarddir'] . '/custom_avatar';
863
	$custom_av_url = !empty($modSettings['custom_avatar_url']) ? $modSettings['custom_avatar_url'] : $boardurl . '/custom_avatar';
864
865
	// This little fellow has to cooperate...
866
	quickFileWritable($custom_av_dir);
867
868
	// Are we good now?
869
	if (!is_writable($custom_av_dir))
870
		return throw_error(sprintf($txt['error_dir_not_writable'], $custom_av_dir));
871
	elseif ($need_settings_update)
872
	{
873
		if (!function_exists('cache_put_data'))
874
			require_once($sourcedir . '/Load.php');
875
876
		updateSettings(array('custom_avatar_dir' => $custom_av_dir));
877
		updateSettings(array('custom_avatar_url' => $custom_av_url));
878
	}
879
880
	require_once($sourcedir . '/Security.php');
881
882
	// Check the cache directory.
883
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
884
	if (!file_exists($cachedir_temp))
885
		@mkdir($cachedir_temp);
886
887
	if (!file_exists($cachedir_temp))
888
		return throw_error($txt['error_cache_not_found']);
889
890
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
891
		return throw_error(sprintf($txt['error_lang_index_missing'], $upcontext['language'], $upgradeurl));
892
	elseif (!isset($_GET['skiplang']))
893
	{
894
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
895
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
896
897
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
898
			return throw_error(sprintf($txt['error_upgrade_old_lang_files'], $upcontext['language'], $upgradeurl));
899
	}
900
901
	if (!makeFilesWritable($writable_files))
902
		return false;
903
904
	// Check agreement.txt. (it may not exist, in which case $boarddir must be writable.)
905
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
906
		return throw_error($txt['error_agreement_not_writable']);
907
908
	// Upgrade the agreement.
909
	elseif (isset($modSettings['agreement']))
910
	{
911
		$fp = fopen($boarddir . '/agreement.txt', 'w');
912
		fwrite($fp, $modSettings['agreement']);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

912
		fwrite(/** @scrutinizer ignore-type */ $fp, $modSettings['agreement']);
Loading history...
913
		fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

913
		fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
914
	}
915
916
	// We're going to check that their board dir setting is right in case they've been moving stuff around.
917
	if (strtr($boarddir, array('/' => '', '\\' => '')) != strtr(dirname(__FILE__), array('/' => '', '\\' => '')))
918
		$upcontext['warning'] = '
919
			' . sprintf($txt['upgrade_boarddir_settings'], $boarddir, dirname(__FILE__)) . '<br>
920
			<ul>
921
				<li>' . $txt['upgrade_boarddir'] . '  ' . $boarddir . '</li>
922
				<li>' . $txt['upgrade_sourcedir'] . '  ' . $boarddir . '</li>
923
				<li>' . $txt['upgrade_cachedir'] . '  ' . $cachedir_temp . '</li>
924
			</ul>
925
			' . $txt['upgrade_incorrect_settings'] . '';
926
927
	// Confirm mbstring is loaded...
928
	if (!extension_loaded('mbstring'))
929
		return throw_error($txt['install_no_mbstring']);
930
931
	// Check for https stream support.
932
	$supported_streams = stream_get_wrappers();
933
	if (!in_array('https', $supported_streams))
934
		$upcontext['custom_warning'] = $txt['install_no_https'];
935
936
	// Either we're logged in or we're going to present the login.
937
	if (checkLogin())
938
		return true;
939
940
	$upcontext += createToken('login');
941
942
	return false;
943
}
944
945
// Step 0.5: Does the login work?
946
function checkLogin()
947
{
948
	global $modSettings, $upcontext, $disable_security;
949
	global $smcFunc, $db_type, $support_js, $sourcedir, $txt;
950
951
	// Don't bother if the security is disabled.
952
	if ($disable_security)
953
		return true;
954
955
	// Are we trying to login?
956
	if (isset($_POST['contbutt']) && (!empty($_POST['user'])))
957
	{
958
		// If we've disabled security pick a suitable name!
959
		if (empty($_POST['user']))
960
			$_POST['user'] = 'Administrator';
961
962
		// Before 2.0 these column names were different!
963
		$oldDB = false;
964
		if (empty($db_type) || $db_type == 'mysql')
965
		{
966
			$request = $smcFunc['db_query']('', '
967
				SHOW COLUMNS
968
				FROM {db_prefix}members
969
				LIKE {string:member_name}',
970
				array(
971
					'member_name' => 'memberName',
972
					'db_error_skip' => true,
973
				)
974
			);
975
			if ($smcFunc['db_num_rows']($request) != 0)
976
				$oldDB = true;
977
			$smcFunc['db_free_result']($request);
978
		}
979
980
		// Get what we believe to be their details.
981
		if (!$disable_security)
982
		{
983
			if ($oldDB)
984
				$request = $smcFunc['db_query']('', '
985
					SELECT id_member, memberName AS member_name, passwd, id_group,
986
						additionalGroups AS additional_groups, lngfile
987
					FROM {db_prefix}members
988
					WHERE memberName = {string:member_name}',
989
					array(
990
						'member_name' => $_POST['user'],
991
						'db_error_skip' => true,
992
					)
993
				);
994
			else
995
				$request = $smcFunc['db_query']('', '
996
					SELECT id_member, member_name, passwd, id_group, additional_groups, lngfile
997
					FROM {db_prefix}members
998
					WHERE member_name = {string:member_name}',
999
					array(
1000
						'member_name' => $_POST['user'],
1001
						'db_error_skip' => true,
1002
					)
1003
				);
1004
			if ($smcFunc['db_num_rows']($request) != 0)
1005
			{
1006
				list ($id_member, $name, $password, $id_group, $addGroups, $user_language) = $smcFunc['db_fetch_row']($request);
1007
1008
				$groups = explode(',', $addGroups);
1009
				$groups[] = $id_group;
1010
1011
				foreach ($groups as $k => $v)
1012
					$groups[$k] = (int) $v;
1013
1014
				$sha_passwd = sha1(strtolower($name) . un_htmlspecialchars($_REQUEST['passwrd']));
1015
1016
				// We don't use "-utf8" anymore...
1017
				$user_language = str_ireplace('-utf8', '', $user_language);
1018
			}
1019
			else
1020
				$upcontext['username_incorrect'] = true;
1021
1022
			$smcFunc['db_free_result']($request);
1023
		}
1024
		$upcontext['username'] = $_POST['user'];
1025
1026
		// Track whether javascript works!
1027
		if (!empty($_POST['js_works']))
1028
		{
1029
			$upcontext['upgrade_status']['js'] = 1;
1030
			$support_js = 1;
1031
		}
1032
		else
1033
			$support_js = 0;
1034
1035
		// Note down the version we are coming from.
1036
		if (!empty($modSettings['smfVersion']) && empty($upcontext['user']['version']))
1037
			$upcontext['user']['version'] = $modSettings['smfVersion'];
1038
1039
		// Didn't get anywhere?
1040
		if (!$disable_security && (empty($sha_passwd) || (!empty($password) ? $password : '') != $sha_passwd) && !hash_verify_password((!empty($name) ? $name : ''), $_REQUEST['passwrd'], (!empty($password) ? $password : '')) && empty($upcontext['username_incorrect']))
1041
		{
1042
			// MD5?
1043
			$md5pass = md5_hmac($_REQUEST['passwrd'], strtolower($_POST['user']));
1044
			if ($md5pass != $password)
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $password does not seem to be defined for all execution paths leading up to this point.
Loading history...
1045
			{
1046
				$upcontext['password_failed'] = true;
1047
				// Disable the hashing this time.
1048
				$upcontext['disable_login_hashing'] = true;
1049
			}
1050
		}
1051
1052
		if ((empty($upcontext['password_failed']) && !empty($name)) || $disable_security)
1053
		{
1054
			// Set the password.
1055
			if (!$disable_security)
1056
			{
1057
				// Do we actually have permission?
1058
				if (!in_array(1, $groups))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $groups does not seem to be defined for all execution paths leading up to this point.
Loading history...
1059
				{
1060
					$request = $smcFunc['db_query']('', '
1061
						SELECT permission
1062
						FROM {db_prefix}permissions
1063
						WHERE id_group IN ({array_int:groups})
1064
							AND permission = {string:admin_forum}',
1065
						array(
1066
							'groups' => $groups,
1067
							'admin_forum' => 'admin_forum',
1068
							'db_error_skip' => true,
1069
						)
1070
					);
1071
					if ($smcFunc['db_num_rows']($request) == 0)
1072
						return throw_error($txt['error_not_admin']);
1073
					$smcFunc['db_free_result']($request);
1074
				}
1075
1076
				$upcontext['user']['id'] = $id_member;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $id_member does not seem to be defined for all execution paths leading up to this point.
Loading history...
1077
				$upcontext['user']['name'] = $name;
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $name does not seem to be defined for all execution paths leading up to this point.
Loading history...
1078
			}
1079
			else
1080
			{
1081
				$upcontext['user']['id'] = 1;
1082
				$upcontext['user']['name'] = 'Administrator';
1083
			}
1084
1085
			if (!is_callable('random_int'))
1086
				require_once('Sources/random_compat/random.php');
1087
1088
			$upcontext['user']['pass'] = random_int(0, 60000);
1089
			// This basically is used to match the GET variables to Settings.php.
1090
			$upcontext['upgrade_status']['pass'] = $upcontext['user']['pass'];
1091
1092
			// Set the language to that of the user?
1093
			if (isset($user_language) && $user_language != $upcontext['language'] && file_exists($modSettings['theme_dir'] . '/languages/index.' . basename($user_language, '.lng') . '.php'))
1094
			{
1095
				$user_language = basename($user_language, '.lng');
1096
				$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
0 ignored issues
show
Bug introduced by
It seems like @file($modSettings['them...user_language . '.php') can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1096
				$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
Loading history...
1097
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1098
1099
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1100
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1101
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1102
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1103
				else
1104
				{
1105
					// Set this as the new language.
1106
					$upcontext['language'] = $user_language;
1107
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1108
1109
					// Include the file.
1110
					load_lang_file();
1111
				}
1112
			}
1113
1114
			// If we're resuming set the step and substep to be correct.
1115
			if (isset($_POST['cont']))
1116
			{
1117
				$upcontext['current_step'] = $upcontext['user']['step'];
1118
				$_GET['substep'] = $upcontext['user']['substep'];
1119
			}
1120
1121
			return true;
1122
		}
1123
	}
1124
1125
	return false;
1126
}
1127
1128
// Step 1: Do the maintenance and backup.
1129
function UpgradeOptions()
1130
{
1131
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1132
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1133
1134
	$upcontext['sub_template'] = 'upgrade_options';
1135
	$upcontext['page_title'] = $txt['upgrade_options'];
1136
1137
	db_extend('packages');
1138
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1139
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1140
1141
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1142
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1143
1144
	unset($member_columns);
1145
1146
	// If these options are missing, we may need to migrate to a new Settings.php
1147
	$upcontext['migrateSettingsNeeded'] = detectSettingsFileMigrationNeeded();
1148
1149
	// If we've not submitted then we're done.
1150
	if (empty($_POST['upcont']))
1151
		return false;
1152
1153
	// Firstly, if they're enabling SM stat collection just do it.
1154
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1155
	{
1156
		$upcontext['allow_sm_stats'] = true;
1157
1158
		// Don't register if we still have a key.
1159
		if (empty($modSettings['sm_stats_key']))
1160
		{
1161
			// Attempt to register the site etc.
1162
			$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1163
			if ($fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1164
			{
1165
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1166
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1167
				$out .= 'Connection: Close' . "\r\n\r\n";
1168
				fwrite($fp, $out);
1169
1170
				$return_data = '';
1171
				while (!feof($fp))
1172
					$return_data .= fgets($fp, 128);
1173
1174
				fclose($fp);
1175
1176
				// Get the unique site ID.
1177
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1178
1179
				if (!empty($ID[1]))
1180
					$smcFunc['db_insert']('replace',
1181
						$db_prefix . 'settings',
1182
						array('variable' => 'string', 'value' => 'string'),
1183
						array(
1184
							array('sm_stats_key', $ID[1]),
1185
							array('enable_sm_stats', 1),
1186
						),
1187
						array('variable')
1188
					);
1189
			}
1190
		}
1191
		else
1192
		{
1193
			$smcFunc['db_insert']('replace',
1194
				$db_prefix . 'settings',
1195
				array('variable' => 'string', 'value' => 'string'),
1196
				array('enable_sm_stats', 1),
1197
				array('variable')
1198
			);
1199
		}
1200
	}
1201
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1202
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1203
		$smcFunc['db_query']('', '
1204
			DELETE FROM {db_prefix}settings
1205
			WHERE variable = {string:enable_sm_stats}',
1206
			array(
1207
				'enable_sm_stats' => 'enable_sm_stats',
1208
				'db_error_skip' => true,
1209
			)
1210
		);
1211
1212
	// Deleting old karma stuff?
1213
	if (!empty($_POST['delete_karma']))
1214
	{
1215
		// Delete old settings vars.
1216
		$smcFunc['db_query']('', '
1217
			DELETE FROM {db_prefix}settings
1218
			WHERE variable IN ({array_string:karma_vars})',
1219
			array(
1220
				'karma_vars' => array('karmaMode', 'karmaTimeRestrictAdmins', 'karmaWaitTime', 'karmaMinPosts', 'karmaLabel', 'karmaSmiteLabel', 'karmaApplaudLabel'),
1221
			)
1222
		);
1223
1224
		// Cleaning up old karma member settings.
1225
		if ($upcontext['karma_installed']['good'])
1226
			$smcFunc['db_query']('', '
1227
				ALTER TABLE {db_prefix}members
1228
				DROP karma_good',
1229
				array()
1230
			);
1231
1232
		// Does karma bad was enable?
1233
		if ($upcontext['karma_installed']['bad'])
1234
			$smcFunc['db_query']('', '
1235
				ALTER TABLE {db_prefix}members
1236
				DROP karma_bad',
1237
				array()
1238
			);
1239
1240
		// Cleaning up old karma permissions.
1241
		$smcFunc['db_query']('', '
1242
			DELETE FROM {db_prefix}permissions
1243
			WHERE permission = {string:karma_vars}',
1244
			array(
1245
				'karma_vars' => 'karma_edit',
1246
			)
1247
		);
1248
		// Cleaning up old log_karma table
1249
		$smcFunc['db_query']('', '
1250
			DROP TABLE IF EXISTS {db_prefix}log_karma',
1251
			array()
1252
		);
1253
	}
1254
1255
	// Emptying the error log?
1256
	if (!empty($_POST['empty_error']))
1257
		$smcFunc['db_query']('truncate_table', '
1258
			TRUNCATE {db_prefix}log_errors',
1259
			array(
1260
			)
1261
		);
1262
1263
	$changes = array();
1264
1265
	// Add proxy settings.
1266
	if (!isset($GLOBALS['image_proxy_maxsize']))
1267
		$changes += array(
1268
			'image_proxy_secret' => '\'' . substr(sha1(mt_rand()), 0, 20) . '\'',
1269
			'image_proxy_maxsize' => 5190,
1270
			'image_proxy_enabled' => 0,
1271
		);
1272
1273
	// If $boardurl reflects https, set force_ssl
1274
	if (!function_exists('cache_put_data'))
1275
		require_once($sourcedir . '/Load.php');
1276
	if (stripos($boardurl, 'https://') !== false)
1277
		updateSettings(array('force_ssl' => '1'));
1278
1279
	// If we're overriding the language follow it through.
1280
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1281
		$changes['language'] = '\'' . $upcontext['lang'] . '\'';
1282
1283
	if (!empty($_POST['maint']))
1284
	{
1285
		$changes['maintenance'] = '2';
1286
		// Remember what it was...
1287
		$upcontext['user']['main'] = $maintenance;
1288
1289
		if (!empty($_POST['maintitle']))
1290
		{
1291
			$changes['mtitle'] = '\'' . addslashes($_POST['maintitle']) . '\'';
1292
			$changes['mmessage'] = '\'' . addslashes($_POST['mainmessage']) . '\'';
1293
		}
1294
		else
1295
		{
1296
			$changes['mtitle'] = '\'' . addslashes($txt['mtitle']) . '\'';
1297
			$changes['mmessage'] = '\'' . addslashes($txt['mmessage']) . '\'';
1298
		}
1299
	}
1300
1301
	if ($command_line)
1302
		echo ' * Updating Settings.php...';
1303
1304
	// Fix some old paths.
1305
	if (substr($boarddir, 0, 1) == '.')
1306
		$changes['boarddir'] = '\'' . fixRelativePath($boarddir) . '\'';
1307
1308
	if (substr($sourcedir, 0, 1) == '.')
1309
		$changes['sourcedir'] = '\'' . fixRelativePath($sourcedir) . '\'';
1310
1311
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1312
		$changes['cachedir'] = '\'' . fixRelativePath($boarddir) . '/cache\'';
1313
1314
	// Migrate cache settings.
1315
	// Accelerator setting didn't exist previously; use 'smf' file based caching as default if caching had been enabled.
1316
	if (!isset($GLOBALS['cache_enable']))
1317
		$changes += array(
1318
			'cache_accelerator' => !empty($modSettings['cache_enable']) ? '\'smf\'' : '\'\'',
1319
			'cache_enable' => !empty($modSettings['cache_enable']) ? $modSettings['cache_enable'] : 0,
1320
			'cache_memcached' => !empty($modSettings['cache_memcached']) ? '\'' . $modSettings['cache_memcached'] . '\'' : '\'\'',
1321
		);
1322
1323
	// If they have a "host:port" setup for the host, split that into separate values
1324
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1325
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1326
	{
1327
		list ($db_server, $db_port) = explode(':', $db_server);
1328
1329
		$changes['db_server'] = '\'' . $db_server . '\'';
1330
1331
		// Only set this if we're not using the default port
1332
		if ($db_port != ini_get('mysqli.default_port'))
1333
			$changes['db_port'] = (int) $db_port;
1334
	}
1335
1336
	// If db_port is set and is the same as the default, set it to 0.
1337
	if (!empty($db_port))
1338
	{
1339
		if ($db_type == 'mysql' && $db_port == ini_get('mysqli.default_port'))
1340
			$changes['db_port'] = 0;
1341
		elseif ($db_type == 'postgresql' && $db_port == 5432)
1342
			$changes['db_port'] = 0;
1343
	}
1344
1345
	// Maybe we haven't had this option yet?
1346
	if (empty($packagesdir))
1347
		$changes['packagesdir'] = '\'' . fixRelativePath($boarddir) . '/Packages\'';
1348
1349
	// Add support for $tasksdir var.
1350
	if (empty($tasksdir))
1351
		$changes['tasksdir'] = '\'' . fixRelativePath($sourcedir) . '/tasks\'';
1352
1353
	// Make sure we fix the language as well.
1354
	if (stristr($language, '-utf8'))
1355
		$changes['language'] = '\'' . str_ireplace('-utf8', '', $language) . '\'';
1356
1357
	// @todo Maybe change the cookie name if going to 1.1, too?
1358
1359
	// If we are migrating the settings, get them ready.
1360
	if (!empty($_POST['migrateSettings']))
1361
	{
1362
		// Ensure this doesn't get lost in translation.
1363
		$changes['upgradeData'] = '"' . base64_encode(json_encode($upcontext['user'])) . '"';
1364
1365
		migrateSettingsFile($changes);
1366
	}
1367
	else
1368
	{
1369
		// Update Settings.php with the new settings.
1370
		require_once($sourcedir . '/Subs-Admin.php');
1371
		updateSettingsFile($changes);
1372
1373
		// Tell Settings.php to store db_last_error.php in the cache
1374
		move_db_last_error_to_cachedir();
1375
	}
1376
1377
	if ($command_line)
1378
		echo ' Successful.' . "\n";
1379
1380
	// Are we doing debug?
1381
	if (isset($_POST['debug']))
1382
	{
1383
		$upcontext['upgrade_status']['debug'] = true;
1384
		$is_debug = true;
1385
	}
1386
1387
	// If we're not backing up then jump one.
1388
	if (empty($_POST['backup']))
1389
		$upcontext['current_step']++;
1390
1391
	// If we've got here then let's proceed to the next step!
1392
	return true;
1393
}
1394
1395
// Backup the database - why not...
1396
function BackupDatabase()
1397
{
1398
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1399
1400
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1401
	$upcontext['page_title'] = $txt['backup_database'];
1402
1403
	// Done it already - js wise?
1404
	if (!empty($_POST['backup_done']))
1405
		return true;
1406
1407
	// Some useful stuff here.
1408
	db_extend();
1409
1410
	// Might need this as well
1411
	db_extend('packages');
1412
1413
	// Get all the table names.
1414
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1415
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1416
	$tables = $smcFunc['db_list_tables']($db, $filter);
1417
1418
	$table_names = array();
1419
	foreach ($tables as $table)
1420
		if (substr($table, 0, 7) !== 'backup_')
1421
			$table_names[] = $table;
1422
1423
	$upcontext['table_count'] = count($table_names);
1424
	$upcontext['cur_table_num'] = $_GET['substep'];
1425
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1426
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1427
	// For non-java auto submit...
1428
	$file_steps = $upcontext['table_count'];
1429
1430
	// What ones have we already done?
1431
	foreach ($table_names as $id => $table)
1432
		if ($id < $_GET['substep'])
1433
			$upcontext['previous_tables'][] = $table;
1434
1435
	if ($command_line)
1436
		echo 'Backing Up Tables.';
1437
1438
	// If we don't support javascript we backup here.
1439
	if (!$support_js || isset($_GET['xml']))
1440
	{
1441
		// Backup each table!
1442
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1443
		{
1444
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1445
			$upcontext['cur_table_num'] = $substep + 1;
1446
1447
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1448
1449
			// Do we need to pause?
1450
			nextSubstep($substep);
1451
1452
			backupTable($table_names[$substep]);
1453
1454
			// If this is XML to keep it nice for the user do one table at a time anyway!
1455
			if (isset($_GET['xml']))
1456
				return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1457
		}
1458
1459
		if ($command_line)
1460
		{
1461
			echo "\n" . ' Successful.\'' . "\n";
1462
			flush();
1463
		}
1464
		$upcontext['step_progress'] = 100;
1465
1466
		$_GET['substep'] = 0;
1467
		// Make sure we move on!
1468
		return true;
1469
	}
1470
1471
	// Either way next place to post will be database changes!
1472
	$_GET['substep'] = 0;
1473
	return false;
1474
}
1475
1476
// Backup one table...
1477
function backupTable($table)
1478
{
1479
	global $command_line, $db_prefix, $smcFunc;
1480
1481
	if ($command_line)
1482
	{
1483
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1484
		flush();
1485
	}
1486
1487
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1488
1489
	if ($command_line)
1490
		echo ' done.';
1491
}
1492
1493
// Step 2: Everything.
1494
function DatabaseChanges()
1495
{
1496
	global $db_prefix, $modSettings, $smcFunc, $txt;
1497
	global $upcontext, $support_js, $db_type;
1498
1499
	// Have we just completed this?
1500
	if (!empty($_POST['database_done']))
1501
		return true;
1502
1503
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1504
	$upcontext['page_title'] = $txt['database_changes'];
1505
1506
	// All possible files.
1507
	// Name, < version, insert_on_complete
1508
	$files = array(
1509
		array('upgrade_1-0.sql', '1.1', '1.1 RC0'),
1510
		array('upgrade_1-1.sql', '2.0', '2.0 a'),
1511
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0'),
1512
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION),
1513
	);
1514
1515
	// How many files are there in total?
1516
	if (isset($_GET['filecount']))
1517
		$upcontext['file_count'] = (int) $_GET['filecount'];
1518
	else
1519
	{
1520
		$upcontext['file_count'] = 0;
1521
		foreach ($files as $file)
1522
		{
1523
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1524
				$upcontext['file_count']++;
1525
		}
1526
	}
1527
1528
	// Do each file!
1529
	$did_not_do = count($files) - $upcontext['file_count'];
1530
	$upcontext['step_progress'] = 0;
1531
	$upcontext['cur_file_num'] = 0;
1532
	foreach ($files as $file)
1533
	{
1534
		if ($did_not_do)
1535
			$did_not_do--;
1536
		else
1537
		{
1538
			$upcontext['cur_file_num']++;
1539
			$upcontext['cur_file_name'] = $file[0];
1540
			// Do we actually need to do this still?
1541
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1542
			{
1543
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1544
				if ($nextFile)
1545
				{
1546
					// Only update the version of this if complete.
1547
					$smcFunc['db_insert']('replace',
1548
						$db_prefix . 'settings',
1549
						array('variable' => 'string', 'value' => 'string'),
1550
						array('smfVersion', $file[2]),
1551
						array('variable')
1552
					);
1553
1554
					$modSettings['smfVersion'] = $file[2];
1555
				}
1556
1557
				// If this is XML we only do this stuff once.
1558
				if (isset($_GET['xml']))
1559
				{
1560
					// Flag to move on to the next.
1561
					$upcontext['completed_step'] = true;
1562
					// Did we complete the whole file?
1563
					if ($nextFile)
1564
						$upcontext['current_debug_item_num'] = -1;
1565
					return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
1566
				}
1567
				elseif ($support_js)
1568
					break;
1569
			}
1570
			// Set the progress bar to be right as if we had - even if we hadn't...
1571
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1572
		}
1573
	}
1574
1575
	$_GET['substep'] = 0;
1576
	// So the template knows we're done.
1577
	if (!$support_js)
1578
	{
1579
		$upcontext['changes_complete'] = true;
1580
1581
		return true;
1582
	}
1583
	return false;
1584
}
1585
1586
// Delete the damn thing!
1587
function DeleteUpgrade()
1588
{
1589
	global $command_line, $language, $upcontext, $sourcedir;
1590
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1591
1592
	// Now it's nice to have some of the basic SMF source files.
1593
	if (!isset($_GET['ssi']) && !$command_line)
1594
		redirectLocation('&ssi=1');
1595
1596
	$upcontext['sub_template'] = 'upgrade_complete';
1597
	$upcontext['page_title'] = $txt['upgrade_complete'];
1598
1599
	$endl = $command_line ? "\n" : '<br>' . "\n";
1600
1601
	$changes = array(
1602
		'language' => '\'' . (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language) . '\'',
1603
		'db_error_send' => '1',
1604
		'upgradeData' => '\'\'',
1605
	);
1606
1607
	// Are we in maintenance mode?
1608
	if (isset($upcontext['user']['main']))
1609
	{
1610
		if ($command_line)
1611
			echo ' * ';
1612
		$upcontext['removed_maintenance'] = true;
1613
		$changes['maintenance'] = $upcontext['user']['main'];
1614
	}
1615
	// Otherwise if somehow we are in 2 let's go to 1.
1616
	elseif (!empty($maintenance) && $maintenance == 2)
1617
		$changes['maintenance'] = 1;
1618
1619
	// Wipe this out...
1620
	$upcontext['user'] = array();
1621
1622
	require_once($sourcedir . '/Subs-Admin.php');
1623
	updateSettingsFile($changes);
1624
1625
	// Clean any old cache files away.
1626
	upgrade_clean_cache();
1627
1628
	// Can we delete the file?
1629
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1630
1631
	// Now is the perfect time to fetch the SM files.
1632
	if ($command_line)
1633
		cli_scheduled_fetchSMfiles();
1634
	else
1635
	{
1636
		require_once($sourcedir . '/ScheduledTasks.php');
1637
		scheduled_fetchSMfiles(); // Now go get those files!
1638
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1639
		if (httpsOn())
1640
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1641
	}
1642
1643
	// Log what we've done.
1644
	if (empty($user_info['id']))
1645
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1646
1647
	// Log the action manually, so CLI still works.
1648
	$smcFunc['db_insert']('',
1649
		'{db_prefix}log_actions',
1650
		array(
1651
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1652
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1653
		),
1654
		array(
1655
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1656
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1657
		),
1658
		array('id_action')
1659
	);
1660
	$user_info['id'] = 0;
1661
1662
	if ($command_line)
1663
	{
1664
		echo $endl;
1665
		echo 'Upgrade Complete!', $endl;
1666
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1667
		exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
1668
	}
1669
1670
	// Make sure it says we're done.
1671
	$upcontext['overall_percent'] = 100;
1672
	if (isset($upcontext['step_progress']))
1673
		unset($upcontext['step_progress']);
1674
1675
	$_GET['substep'] = 0;
1676
	return false;
1677
}
1678
1679
// Just like the built in one, but setup for CLI to not use themes.
1680
function cli_scheduled_fetchSMfiles()
1681
{
1682
	global $sourcedir, $language, $modSettings, $smcFunc;
1683
1684
	if (empty($modSettings['time_format']))
1685
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1686
1687
	// What files do we want to get
1688
	$request = $smcFunc['db_query']('', '
1689
		SELECT id_file, filename, path, parameters
1690
		FROM {db_prefix}admin_info_files',
1691
		array(
1692
		)
1693
	);
1694
1695
	$js_files = array();
1696
	while ($row = $smcFunc['db_fetch_assoc']($request))
1697
	{
1698
		$js_files[$row['id_file']] = array(
1699
			'filename' => $row['filename'],
1700
			'path' => $row['path'],
1701
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1702
		);
1703
	}
1704
	$smcFunc['db_free_result']($request);
1705
1706
	// We're gonna need fetch_web_data() to pull this off.
1707
	require_once($sourcedir . '/Subs.php');
1708
1709
	foreach ($js_files as $ID_FILE => $file)
1710
	{
1711
		// Create the url
1712
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
1713
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1714
1715
		// Get the file
1716
		$file_data = fetch_web_data($url);
1717
1718
		// If we got an error - give up - the site might be down.
1719
		if ($file_data === false)
1720
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
1721
1722
		// Save the file to the database.
1723
		$smcFunc['db_query']('substring', '
1724
			UPDATE {db_prefix}admin_info_files
1725
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1726
			WHERE id_file = {int:id_file}',
1727
			array(
1728
				'id_file' => $ID_FILE,
1729
				'file_data' => $file_data,
1730
			)
1731
		);
1732
	}
1733
	return true;
1734
}
1735
1736
function convertSettingsToTheme()
1737
{
1738
	global $db_prefix, $modSettings, $smcFunc;
1739
1740
	$values = array(
1741
		'show_latest_member' => @$GLOBALS['showlatestmember'],
1742
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
1743
		'show_modify' => @$GLOBALS['showmodify'],
1744
		'show_user_images' => @$GLOBALS['showuserpic'],
1745
		'show_blurb' => @$GLOBALS['showusertext'],
1746
		'show_gender' => @$GLOBALS['showgenderimage'],
1747
		'show_newsfader' => @$GLOBALS['shownewsfader'],
1748
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
1749
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
1750
		'linktree_link' => @$GLOBALS['curposlinks'],
1751
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
1752
		'show_mark_read' => @$GLOBALS['showmarkread'],
1753
		'newsfader_time' => @$GLOBALS['fadertime'],
1754
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
1755
		'enable_news' => @$GLOBALS['enable_news'],
1756
		'return_to_post' => @$modSettings['returnToPost'],
1757
	);
1758
1759
	$themeData = array();
1760
	foreach ($values as $variable => $value)
1761
	{
1762
		if (!isset($value) || $value === null)
1763
			$value = 0;
1764
1765
		$themeData[] = array(0, 1, $variable, $value);
1766
	}
1767
	if (!empty($themeData))
1768
	{
1769
		$smcFunc['db_insert']('ignore',
1770
			$db_prefix . 'themes',
1771
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
1772
			$themeData,
1773
			array('id_member', 'id_theme', 'variable')
1774
		);
1775
	}
1776
}
1777
1778
// This function only works with MySQL but that's fine as it is only used for v1.0.
1779
function convertSettingstoOptions()
1780
{
1781
	global $modSettings, $smcFunc;
1782
1783
	// Format: new_setting -> old_setting_name.
1784
	$values = array(
1785
		'calendar_start_day' => 'cal_startmonday',
1786
		'view_newest_first' => 'viewNewestFirst',
1787
		'view_newest_pm_first' => 'viewNewestFirst',
1788
	);
1789
1790
	foreach ($values as $variable => $value)
1791
	{
1792
		if (empty($modSettings[$value[0]]))
1793
			continue;
1794
1795
		$smcFunc['db_query']('', '
1796
			INSERT IGNORE INTO {db_prefix}themes
1797
				(id_member, id_theme, variable, value)
1798
			SELECT id_member, 1, {string:variable}, {string:value}
1799
			FROM {db_prefix}members',
1800
			array(
1801
				'variable' => $variable,
1802
				'value' => $modSettings[$value[0]],
1803
				'db_error_skip' => true,
1804
			)
1805
		);
1806
1807
		$smcFunc['db_query']('', '
1808
			INSERT IGNORE INTO {db_prefix}themes
1809
				(id_member, id_theme, variable, value)
1810
			VALUES (-1, 1, {string:variable}, {string:value})',
1811
			array(
1812
				'variable' => $variable,
1813
				'value' => $modSettings[$value[0]],
1814
				'db_error_skip' => true,
1815
			)
1816
		);
1817
	}
1818
}
1819
1820
function php_version_check()
1821
{
1822
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
1823
}
1824
1825
function db_version_check()
1826
{
1827
	global $db_type, $databases;
1828
1829
	$curver = eval($databases[$db_type]['version_check']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1830
	$curver = preg_replace('~\-.+?$~', '', $curver);
1831
1832
	return version_compare($databases[$db_type]['version'], $curver, '<=');
1833
}
1834
1835
function fixRelativePath($path)
1836
{
1837
	global $install_path;
1838
1839
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
1840
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
1841
}
1842
1843
function parse_sql($filename)
1844
{
1845
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
1846
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set;
1847
1848
/*
1849
	Failure allowed on:
1850
		- INSERT INTO but not INSERT IGNORE INTO.
1851
		- UPDATE IGNORE but not UPDATE.
1852
		- ALTER TABLE and ALTER IGNORE TABLE.
1853
		- DROP TABLE.
1854
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
1855
1856
	If a comment...
1857
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
1858
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
1859
		- is only ---#, it is "done." and then a break - only shown in debug.
1860
		- begins with ---{ it is a code block terminating at ---}.
1861
1862
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
1863
1864
	Replaces the following variables:
1865
		- {$boarddir}
1866
		- {$boardurl}
1867
		- {$db_prefix}
1868
		- {$db_collation}
1869
*/
1870
1871
	// May want to use extended functionality.
1872
	db_extend();
1873
	db_extend('packages');
1874
1875
	// Our custom error handler - does nothing but does stop public errors from XML!
1876
	set_error_handler(
1877
		function($errno, $errstr, $errfile, $errline) use ($support_js)
1878
		{
1879
			if ($support_js)
1880
				return true;
1881
			else
1882
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
1883
		}
1884
	);
1885
1886
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
1887
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
1888
	if ($db_type == 'mysql')
1889
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
1890
	else
1891
		$db_collation = '';
1892
1893
	$endl = $command_line ? "\n" : '<br>' . "\n";
1894
1895
	$lines = file($filename);
1896
1897
	$current_type = 'sql';
1898
	$current_data = '';
1899
	$substep = 0;
1900
	$last_step = '';
1901
1902
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
1903
	if (isset($db_character_set) && $db_character_set === 'utf8')
1904
		$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
1905
1906
	// Count the total number of steps within this file - for progress.
1907
	$file_steps = substr_count(implode('', $lines), '---#');
0 ignored issues
show
Bug introduced by
It seems like $lines can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

1907
	$file_steps = substr_count(implode('', /** @scrutinizer ignore-type */ $lines), '---#');
Loading history...
1908
	$upcontext['total_items'] = substr_count(implode('', $lines), '--- ');
1909
	$upcontext['debug_items'] = $file_steps;
1910
	$upcontext['current_item_num'] = 0;
1911
	$upcontext['current_item_name'] = '';
1912
	$upcontext['current_debug_item_num'] = 0;
1913
	$upcontext['current_debug_item_name'] = '';
1914
	// This array keeps a record of what we've done in case java is dead...
1915
	$upcontext['actioned_items'] = array();
1916
1917
	$done_something = false;
1918
1919
	foreach ($lines as $line_number => $line)
1920
	{
1921
		$do_current = $substep >= $_GET['substep'];
1922
1923
		// Get rid of any comments in the beginning of the line...
1924
		if (substr(trim($line), 0, 2) === '/*')
1925
			$line = preg_replace('~/\*.+?\*/~', '', $line);
1926
1927
		// Always flush.  Flush, flush, flush.  Flush, flush, flush, flush!  FLUSH!
1928
		if ($is_debug && !$support_js && $command_line)
1929
			flush();
1930
1931
		if (trim($line) === '')
1932
			continue;
1933
1934
		if (trim(substr($line, 0, 3)) === '---')
1935
		{
1936
			$type = substr($line, 3, 1);
1937
1938
			// An error??
1939
			if (trim($current_data) != '' && $type !== '}')
1940
			{
1941
				$upcontext['error_message'] = 'Error in upgrade script - line ' . $line_number . '!' . $endl;
1942
				if ($command_line)
1943
					echo $upcontext['error_message'];
1944
			}
1945
1946
			if ($type == ' ')
1947
			{
1948
				if (!$support_js && $do_current && $_GET['substep'] != 0 && $command_line)
1949
				{
1950
					echo ' Successful.', $endl;
1951
					flush();
1952
				}
1953
1954
				$last_step = htmlspecialchars(rtrim(substr($line, 4)));
1955
				$upcontext['current_item_num']++;
1956
				$upcontext['current_item_name'] = $last_step;
1957
1958
				if ($do_current)
1959
				{
1960
					$upcontext['actioned_items'][] = $last_step;
1961
					if ($command_line)
1962
						echo ' * ';
1963
1964
					// Starting a new main step in our DB changes, so it's time to reset this.
1965
					$upcontext['skip_db_substeps'] = false;
1966
				}
1967
			}
1968
			elseif ($type == '#')
1969
			{
1970
				$upcontext['step_progress'] += (100 / $upcontext['file_count']) / $file_steps;
1971
1972
				$upcontext['current_debug_item_num']++;
1973
				if (trim($line) != '---#')
1974
					$upcontext['current_debug_item_name'] = htmlspecialchars(rtrim(substr($line, 4)));
1975
1976
				// Have we already done something?
1977
				if (isset($_GET['xml']) && $done_something)
1978
				{
1979
					restore_error_handler();
1980
					return $upcontext['current_debug_item_num'] >= $upcontext['debug_items'] ? true : false;
1981
				}
1982
1983
				if ($do_current)
1984
				{
1985
					if (trim($line) == '---#' && $command_line)
1986
						echo ' done.', $endl;
1987
					elseif ($command_line)
1988
						echo ' +++ ', rtrim(substr($line, 4));
1989
					elseif (trim($line) != '---#')
1990
					{
1991
						if ($is_debug)
1992
							$upcontext['actioned_items'][] = $upcontext['current_debug_item_name'];
1993
					}
1994
				}
1995
1996
				if ($substep < $_GET['substep'] && $substep + 1 >= $_GET['substep'])
1997
				{
1998
					if ($command_line)
1999
						echo ' * ';
2000
					else
2001
						$upcontext['actioned_items'][] = $last_step;
2002
				}
2003
2004
				// Small step - only if we're actually doing stuff.
2005
				if ($do_current)
2006
					nextSubstep(++$substep);
2007
				else
2008
					$substep++;
2009
			}
2010
			elseif ($type == '{')
2011
				$current_type = 'code';
2012
			elseif ($type == '}')
2013
			{
2014
				$current_type = 'sql';
2015
2016
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2017
				{
2018
					$current_data = '';
2019
2020
					// Avoid confusion when skipping something we normally would have done
2021
					if ($do_current)
2022
						$done_something = true;
2023
2024
					continue;
2025
				}
2026
2027
				// @todo Update this to a try/catch for PHP 7+, because eval() now throws an exception for parse errors instead of returning false
2028
				if (eval('global $db_prefix, $modSettings, $smcFunc, $txt, $upcontext, $db_name; ' . $current_data) === false)
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
2029
				{
2030
					$upcontext['error_message'] = 'Error in upgrade script ' . basename($filename) . ' on line ' . $line_number . '!' . $endl;
2031
					if ($command_line)
2032
						echo $upcontext['error_message'];
2033
				}
2034
2035
				// Done with code!
2036
				$current_data = '';
2037
				$done_something = true;
2038
			}
2039
2040
			continue;
2041
		}
2042
2043
		$current_data .= $line;
2044
		if (substr(rtrim($current_data), -1) === ';' && $current_type === 'sql')
2045
		{
2046
			if ((!$support_js || isset($_GET['xml'])))
2047
			{
2048
				if (!$do_current || !empty($upcontext['skip_db_substeps']))
2049
				{
2050
					$current_data = '';
2051
2052
					if ($do_current)
2053
						$done_something = true;
2054
2055
					continue;
2056
				}
2057
2058
				$current_data = strtr(substr(rtrim($current_data), 0, -1), array('{$db_prefix}' => $db_prefix, '{$boarddir}' => $boarddir, '{$sboarddir}' => addslashes($boarddir), '{$boardurl}' => $boardurl, '{$db_collation}' => $db_collation));
2059
2060
				upgrade_query($current_data);
2061
2062
				// @todo This will be how it kinda does it once mysql all stripped out - needed for postgre (etc).
2063
				/*
2064
				$result = $smcFunc['db_query']('', $current_data, false, false);
2065
				// Went wrong?
2066
				if (!$result)
2067
				{
2068
					// Bit of a bodge - do we want the error?
2069
					if (!empty($upcontext['return_error']))
2070
					{
2071
						$upcontext['error_message'] = $smcFunc['db_error']($db_connection);
2072
						return false;
2073
					}
2074
				}*/
2075
				$done_something = true;
2076
			}
2077
			$current_data = '';
2078
		}
2079
		// If this is xml based and we're just getting the item name then that's grand.
2080
		elseif ($support_js && !isset($_GET['xml']) && $upcontext['current_debug_item_name'] != '' && $do_current)
2081
		{
2082
			restore_error_handler();
2083
			return false;
2084
		}
2085
2086
		// Clean up by cleaning any step info.
2087
		$step_progress = array();
2088
		$custom_warning = '';
2089
	}
2090
2091
	// Put back the error handler.
2092
	restore_error_handler();
2093
2094
	if ($command_line)
2095
	{
2096
		echo ' Successful.' . "\n";
2097
		flush();
2098
	}
2099
2100
	$_GET['substep'] = 0;
2101
	return true;
2102
}
2103
2104
function upgrade_query($string, $unbuffered = false)
2105
{
2106
	global $db_connection, $db_server, $db_user, $db_passwd, $db_type;
2107
	global $command_line, $upcontext, $upgradeurl, $modSettings;
2108
	global $db_name, $db_unbuffered, $smcFunc, $txt;
2109
2110
	// Get the query result - working around some SMF specific security - just this once!
2111
	$modSettings['disableQueryCheck'] = true;
2112
	$db_unbuffered = $unbuffered;
2113
	$ignore_insert_error = false;
2114
2115
	// If we got an old pg version and use a insert ignore query
2116
	if ($db_type == 'postgresql' && !$smcFunc['db_native_replace']() && strpos($string, 'ON CONFLICT DO NOTHING') !== false)
2117
	{
2118
		$ignore_insert_error = true;
2119
		$string = str_replace('ON CONFLICT DO NOTHING', '', $string);
2120
	}
2121
	$result = $smcFunc['db_query']('', $string, array('security_override' => true, 'db_error_skip' => true));
2122
	$db_unbuffered = false;
2123
2124
	// Failure?!
2125
	if ($result !== false)
2126
		return $result;
2127
2128
	$db_error_message = $smcFunc['db_error']($db_connection);
2129
	// If MySQL we do something more clever.
2130
	if ($db_type == 'mysql')
2131
	{
2132
		$mysqli_errno = mysqli_errno($db_connection);
2133
		$error_query = in_array(substr(trim($string), 0, 11), array('INSERT INTO', 'UPDATE IGNO', 'ALTER TABLE', 'DROP TABLE ', 'ALTER IGNOR'));
2134
2135
		// Error numbers:
2136
		//    1016: Can't open file '....MYI'
2137
		//    1050: Table already exists.
2138
		//    1054: Unknown column name.
2139
		//    1060: Duplicate column name.
2140
		//    1061: Duplicate key name.
2141
		//    1062: Duplicate entry for unique key.
2142
		//    1068: Multiple primary keys.
2143
		//    1072: Key column '%s' doesn't exist in table.
2144
		//    1091: Can't drop key, doesn't exist.
2145
		//    1146: Table doesn't exist.
2146
		//    2013: Lost connection to server during query.
2147
2148
		if ($mysqli_errno == 1016)
2149
		{
2150
			if (preg_match('~\'([^\.\']+)~', $db_error_message, $match) != 0 && !empty($match[1]))
2151
			{
2152
				mysqli_query($db_connection, 'REPAIR TABLE `' . $match[1] . '`');
2153
				$result = mysqli_query($db_connection, $string);
2154
				if ($result !== false)
2155
					return $result;
2156
			}
2157
		}
2158
		elseif ($mysqli_errno == 2013)
2159
		{
2160
			$db_connection = mysqli_connect($db_server, $db_user, $db_passwd);
2161
			mysqli_select_db($db_connection, $db_name);
0 ignored issues
show
Bug introduced by
It seems like $db_connection can also be of type false; however, parameter $link of mysqli_select_db() does only seem to accept mysqli, maybe add an additional type check? ( Ignorable by Annotation )

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

2161
			mysqli_select_db(/** @scrutinizer ignore-type */ $db_connection, $db_name);
Loading history...
2162
			if ($db_connection)
2163
			{
2164
				$result = mysqli_query($db_connection, $string);
2165
				if ($result !== false)
2166
					return $result;
2167
			}
2168
		}
2169
		// Duplicate column name... should be okay ;).
2170
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2171
			return false;
2172
		// Duplicate insert... make sure it's the proper type of query ;).
2173
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2174
			return false;
2175
		// Creating an index on a non-existent column.
2176
		elseif ($mysqli_errno == 1072)
2177
			return false;
2178
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2179
			return false;
2180
	}
2181
	// If a table already exists don't go potty.
2182
	else
2183
	{
2184
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2185
		{
2186
			if (strpos($db_error_message, 'exist') !== false)
2187
				return true;
2188
		}
2189
		elseif (strpos(trim($string), 'INSERT ') !== false)
2190
		{
2191
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2192
				return true;
2193
		}
2194
	}
2195
2196
	// Get the query string so we pass everything.
2197
	$query_string = '';
2198
	foreach ($_GET as $k => $v)
2199
		$query_string .= ';' . $k . '=' . $v;
2200
	if (strlen($query_string) != 0)
2201
		$query_string = '?' . substr($query_string, 1);
2202
2203
	if ($command_line)
2204
	{
2205
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2206
		die;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
2207
	}
2208
2209
	// Bit of a bodge - do we want the error?
2210
	if (!empty($upcontext['return_error']))
2211
	{
2212
		$upcontext['error_message'] = $db_error_message;
2213
		$upcontext['error_string'] = $string;
2214
		return false;
2215
	}
2216
2217
	// Otherwise we have to display this somewhere appropriate if possible.
2218
	$upcontext['forced_error_message'] = '
2219
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2220
2221
			<div style="margin: 2ex;">
2222
				' . $txt['upgrade_thisquery'] . '
2223
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2224
2225
				' . $txt['upgrade_causerror'] . '
2226
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2227
			</div>
2228
2229
			<form action="' . $upgradeurl . $query_string . '" method="post">
2230
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2231
			</form>
2232
		</div>';
2233
2234
	upgradeExit();
2235
}
2236
2237
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2238
function protected_alter($change, $substep, $is_test = false)
2239
{
2240
	global $db_prefix, $smcFunc;
2241
2242
	db_extend('packages');
2243
2244
	// Firstly, check whether the current index/column exists.
2245
	$found = false;
2246
	if ($change['type'] === 'column')
2247
	{
2248
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2249
		foreach ($columns as $column)
2250
		{
2251
			// Found it?
2252
			if ($column['name'] === $change['name'])
2253
			{
2254
				$found |= true;
2255
				// Do some checks on the data if we have it set.
2256
				if (isset($change['col_type']))
2257
					$found &= $change['col_type'] === $column['type'];
2258
				if (isset($change['null_allowed']))
2259
					$found &= $column['null'] == $change['null_allowed'];
2260
				if (isset($change['default']))
2261
					$found &= $change['default'] === $column['default'];
2262
			}
2263
		}
2264
	}
2265
	elseif ($change['type'] === 'index')
2266
	{
2267
		$request = upgrade_query('
2268
			SHOW INDEX
2269
			FROM ' . $db_prefix . $change['table']);
2270
		if ($request !== false)
2271
		{
2272
			$cur_index = array();
2273
2274
			while ($row = $smcFunc['db_fetch_assoc']($request))
2275
				if ($row['Key_name'] === $change['name'])
2276
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2277
2278
			ksort($cur_index, SORT_NUMERIC);
2279
			$found = array_values($cur_index) === $change['target_columns'];
2280
2281
			$smcFunc['db_free_result']($request);
2282
		}
2283
	}
2284
2285
	// If we're trying to add and it's added, we're done.
2286
	if ($found && in_array($change['method'], array('add', 'change')))
0 ignored issues
show
Bug Best Practice introduced by
The expression $found of type false|integer is loosely compared to true; this is ambiguous if the integer can be 0. You might want to explicitly use !== false instead.

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

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

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

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

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

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

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

// It is often better to use strict comparison
0 === false // false
0 === null  // false
Loading history...
2290
		return true;
2291
	// Otherwise is it just a test?
2292
	elseif ($is_test)
2293
		return false;
2294
2295
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2296
	$running = false;
2297
	$found = false;
2298
	while (1 == 1)
2299
	{
2300
		$request = upgrade_query('
2301
			SHOW FULL PROCESSLIST');
2302
		while ($row = $smcFunc['db_fetch_assoc']($request))
2303
		{
2304
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2305
				$found = true;
2306
		}
2307
2308
		// Can't find it? Then we need to run it fools!
2309
		if (!$found && !$running)
2310
		{
2311
			$smcFunc['db_free_result']($request);
2312
2313
			$success = upgrade_query('
2314
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2315
				' . $change['text'], true) !== false;
2316
2317
			if (!$success)
2318
				return false;
2319
2320
			// Return
2321
			$running = true;
2322
		}
2323
		// What if we've not found it, but we'd ran it already? Must of completed.
2324
		elseif (!$found)
2325
		{
2326
			$smcFunc['db_free_result']($request);
2327
			return true;
2328
		}
2329
2330
		// Pause execution for a sec or three.
2331
		sleep(3);
2332
2333
		// Can never be too well protected.
2334
		nextSubstep($substep);
2335
	}
2336
2337
	// Protect it.
2338
	nextSubstep($substep);
2339
}
2340
2341
/**
2342
 * Alter a text column definition preserving its character set.
2343
 *
2344
 * @param array $change
2345
 * @param int $substep
2346
 */
2347
function textfield_alter($change, $substep)
2348
{
2349
	global $db_prefix, $smcFunc;
2350
2351
	$request = $smcFunc['db_query']('', '
2352
		SHOW FULL COLUMNS
2353
		FROM {db_prefix}' . $change['table'] . '
2354
		LIKE {string:column}',
2355
		array(
2356
			'column' => $change['column'],
2357
			'db_error_skip' => true,
2358
		)
2359
	);
2360
	if ($smcFunc['db_num_rows']($request) === 0)
2361
		die('Unable to find column ' . $change['column'] . ' inside table ' . $db_prefix . $change['table']);
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
2362
	$table_row = $smcFunc['db_fetch_assoc']($request);
2363
	$smcFunc['db_free_result']($request);
2364
2365
	// If something of the current column definition is different, fix it.
2366
	$column_fix = $table_row['Type'] !== $change['type'] || (strtolower($table_row['Null']) === 'yes') !== $change['null_allowed'] || ($table_row['Default'] === null) !== !isset($change['default']) || (isset($change['default']) && $change['default'] !== $table_row['Default']);
2367
2368
	// Columns that previously allowed null, need to be converted first.
2369
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2370
2371
	// Get the character set that goes with the collation of the column.
2372
	if ($column_fix && !empty($table_row['Collation']))
2373
	{
2374
		$request = $smcFunc['db_query']('', '
2375
			SHOW COLLATION
2376
			LIKE {string:collation}',
2377
			array(
2378
				'collation' => $table_row['Collation'],
2379
				'db_error_skip' => true,
2380
			)
2381
		);
2382
		// No results? Just forget it all together.
2383
		if ($smcFunc['db_num_rows']($request) === 0)
2384
			unset($table_row['Collation']);
2385
		else
2386
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2387
		$smcFunc['db_free_result']($request);
2388
	}
2389
2390
	if ($column_fix)
2391
	{
2392
		// Make sure there are no NULL's left.
2393
		if ($null_fix)
2394
			$smcFunc['db_query']('', '
2395
				UPDATE {db_prefix}' . $change['table'] . '
2396
				SET ' . $change['column'] . ' = {string:default}
2397
				WHERE ' . $change['column'] . ' IS NULL',
2398
				array(
2399
					'default' => isset($change['default']) ? $change['default'] : '',
2400
					'db_error_skip' => true,
2401
				)
2402
			);
2403
2404
		// Do the actual alteration.
2405
		$smcFunc['db_query']('', '
2406
			ALTER TABLE {db_prefix}' . $change['table'] . '
2407
			CHANGE COLUMN ' . $change['column'] . ' ' . $change['column'] . ' ' . $change['type'] . (isset($collation_info['Charset']) ? ' CHARACTER SET ' . $collation_info['Charset'] . ' COLLATE ' . $collation_info['Collation'] : '') . ($change['null_allowed'] ? '' : ' NOT NULL') . (isset($change['default']) ? ' default {string:default}' : ''),
2408
			array(
2409
				'default' => isset($change['default']) ? $change['default'] : '',
2410
				'db_error_skip' => true,
2411
			)
2412
		);
2413
	}
2414
	nextSubstep($substep);
2415
}
2416
2417
// Check if we need to alter this query.
2418
function checkChange(&$change)
2419
{
2420
	global $smcFunc, $db_type, $databases;
2421
	static $database_version, $where_field_support;
2422
2423
	// Attempt to find a database_version.
2424
	if (empty($database_version))
2425
	{
2426
		$database_version = $databases[$db_type]['version_check'];
2427
		$where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version, '<=');
2428
	}
2429
2430
	// Not a column we need to check on?
2431
	if (!in_array($change['name'], array('memberGroups', 'passwordSalt')))
2432
		return;
2433
2434
	// Break it up you (six|seven).
2435
	$temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text']));
2436
2437
	// Can we support a shortcut method?
2438
	if ($where_field_support)
2439
	{
2440
		// Get the details about this change.
2441
		$request = $smcFunc['db_query']('', '
2442
			SHOW FIELDS
2443
			FROM {db_prefix}{raw:table}
2444
			WHERE Field = {string:old_name} OR Field = {string:new_name}',
2445
			array(
2446
				'table' => $change['table'],
2447
				'old_name' => $temp[1],
2448
				'new_name' => $temp[2],
2449
			)
2450
		);
2451
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2452
		if ($smcFunc['db_num_rows'] != 1)
2453
			return;
2454
2455
		list (, $current_type) = $smcFunc['db_fetch_assoc']($request);
2456
		$smcFunc['db_free_result']($request);
2457
	}
2458
	else
2459
	{
2460
		// Do this the old fashion, sure method way.
2461
		$request = $smcFunc['db_query']('', '
2462
			SHOW FIELDS
2463
			FROM {db_prefix}{raw:table}',
2464
			array(
2465
				'table' => $change['table'],
2466
			)
2467
		);
2468
		// Mayday!
2469
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2470
		if ($smcFunc['db_num_rows'] == 0)
2471
			return;
2472
2473
		// Oh where, oh where has my little field gone. Oh where can it be...
2474
		while ($row = $smcFunc['db_query']($request))
2475
			if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2])
2476
			{
2477
				$current_type = $row['Type'];
2478
				break;
2479
			}
2480
	}
2481
2482
	// If this doesn't match, the column may of been altered for a reason.
2483
	if (trim($current_type) != trim($temp[3]))
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $current_type does not seem to be defined for all execution paths leading up to this point.
Loading history...
2484
		$temp[3] = $current_type;
2485
2486
	// Piece this back together.
2487
	$change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp));
2488
}
2489
2490
// The next substep.
2491
function nextSubstep($substep)
2492
{
2493
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2494
	global $step_progress, $is_debug, $upcontext;
2495
2496
	if ($_GET['substep'] < $substep)
2497
		$_GET['substep'] = $substep;
2498
2499
	if ($command_line)
2500
	{
2501
		if (time() - $start_time > 1 && empty($is_debug))
2502
		{
2503
			echo '.';
2504
			$start_time = time();
2505
		}
2506
		return;
2507
	}
2508
2509
	@set_time_limit(300);
2510
	if (function_exists('apache_reset_timeout'))
2511
		@apache_reset_timeout();
2512
2513
	if (time() - $start_time <= $timeLimitThreshold)
2514
		return;
2515
2516
	// Do we have some custom step progress stuff?
2517
	if (!empty($step_progress))
2518
	{
2519
		$upcontext['substep_progress'] = 0;
2520
		$upcontext['substep_progress_name'] = $step_progress['name'];
2521
		if ($step_progress['current'] > $step_progress['total'])
2522
			$upcontext['substep_progress'] = 99.9;
2523
		else
2524
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2525
2526
		// Make it nicely rounded.
2527
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2528
	}
2529
2530
	// If this is XML we just exit right away!
2531
	if (isset($_GET['xml']))
2532
		return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
2533
2534
	// We're going to pause after this!
2535
	$upcontext['pause'] = true;
2536
2537
	$upcontext['query_string'] = '';
2538
	foreach ($_GET as $k => $v)
2539
	{
2540
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2541
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2542
	}
2543
2544
	// Custom warning?
2545
	if (!empty($custom_warning))
2546
		$upcontext['custom_warning'] = $custom_warning;
2547
2548
	upgradeExit();
2549
}
2550
2551
function cmdStep0()
2552
{
2553
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2554
	global $is_debug;
2555
	$start_time = time();
2556
2557
	ob_end_clean();
2558
	ob_implicit_flush(1);
2559
	@set_time_limit(600);
2560
2561
	if (!isset($_SERVER['argv']))
2562
		$_SERVER['argv'] = array();
2563
	$_GET['maint'] = 1;
2564
2565
	foreach ($_SERVER['argv'] as $i => $arg)
2566
	{
2567
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2568
			$upcontext['lang'] = $match[1];
2569
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2570
			continue;
2571
		elseif ($arg == '--no-maintenance')
2572
			$_GET['maint'] = 0;
2573
		elseif ($arg == '--debug')
2574
			$is_debug = true;
2575
		elseif ($arg == '--backup')
2576
			$_POST['backup'] = 1;
2577
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2578
			$_GET['conv'] = 1;
2579
		elseif ($i != 0)
2580
		{
2581
			echo 'SMF Command-line Upgrader
2582
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2583
2584
	--language=LANG         Reset the forum\'s language to LANG.
2585
	--no-maintenance        Don\'t put the forum into maintenance mode.
2586
	--debug                 Output debugging information.
2587
	--backup                Create backups of tables with "backup_" prefix.';
2588
			echo "\n";
2589
			exit;
0 ignored issues
show
Best Practice introduced by
Using exit here is not recommended.

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

Loading history...
2590
		}
2591
	}
2592
2593
	if (!php_version_check())
2594
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
2595
	if (!db_version_check())
2596
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2597
2598
	// Do some checks to make sure they have proper privileges
2599
	db_extend('packages');
2600
2601
	// CREATE
2602
	$create = $smcFunc['db_create_table']('{db_prefix}priv_check', array(array('name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true)), array(array('columns' => array('id_test'), 'primary' => true)), array(), 'overwrite');
2603
2604
	// ALTER
2605
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2606
2607
	// DROP
2608
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2609
2610
	// Sorry... we need CREATE, ALTER and DROP
2611
	if (!$create || !$alter || !$drop)
2612
		print_error("The " . $databases[$db_type]['name'] . " user you have set in Settings.php does not have proper privileges.\n\nPlease ask your host to give this user the ALTER, CREATE, and DROP privileges.", true);
2613
2614
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2615
		&& @file_exists($sourcedir . '/QueryString.php')
2616
		&& @file_exists($sourcedir . '/ManageBoards.php');
2617
	if (!$check && !isset($modSettings['smfVersion']))
2618
		print_error('Error: Some files are missing or out-of-date.', true);
2619
2620
	// Do a quick version spot check.
2621
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
0 ignored issues
show
Bug introduced by
It seems like @file($boarddir . '/index.php') can also be of type false; however, parameter $pieces of implode() does only seem to accept array, maybe add an additional type check? ( Ignorable by Annotation )

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

2621
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
2622
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2623
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2624
		print_error('Error: Some files have not yet been updated properly.');
2625
2626
	// Make sure Settings.php is writable.
2627
	quickFileWritable($boarddir . '/Settings.php');
2628
	if (!is_writable($boarddir . '/Settings.php'))
2629
		print_error('Error: Unable to obtain write access to "Settings.php".', true);
2630
2631
	// Make sure Settings_bak.php is writable.
2632
	quickFileWritable($boarddir . '/Settings_bak.php');
2633
	if (!is_writable($boarddir . '/Settings_bak.php'))
2634
		print_error('Error: Unable to obtain write access to "Settings_bak.php".');
2635
2636
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
2637
		print_error('Error: Unable to obtain write access to "agreement.txt".');
2638
	elseif (isset($modSettings['agreement']))
2639
	{
2640
		$fp = fopen($boarddir . '/agreement.txt', 'w');
2641
		fwrite($fp, $modSettings['agreement']);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fwrite() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

2641
		fwrite(/** @scrutinizer ignore-type */ $fp, $modSettings['agreement']);
Loading history...
2642
		fclose($fp);
0 ignored issues
show
Bug introduced by
It seems like $fp can also be of type false; however, parameter $handle of fclose() does only seem to accept resource, maybe add an additional type check? ( Ignorable by Annotation )

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

2642
		fclose(/** @scrutinizer ignore-type */ $fp);
Loading history...
2643
	}
2644
2645
	// Make sure Themes is writable.
2646
	quickFileWritable($modSettings['theme_dir']);
2647
2648
	if (!is_writable($modSettings['theme_dir']) && !isset($modSettings['smfVersion']))
2649
		print_error('Error: Unable to obtain write access to "Themes".');
2650
2651
	// Make sure cache directory exists and is writable!
2652
	$cachedir_temp = empty($cachedir) ? $boarddir . '/cache' : $cachedir;
2653
	if (!file_exists($cachedir_temp))
2654
		@mkdir($cachedir_temp);
2655
2656
	// Make sure the cache temp dir is writable.
2657
	quickFileWritable($cachedir_temp);
2658
2659
	if (!is_writable($cachedir_temp))
2660
		print_error('Error: Unable to obtain write access to "cache".', true);
2661
2662
	// Make sure db_last_error.php is writable.
2663
	quickFileWritable($cachedir_temp . '/db_last_error.php');
2664
	if (!is_writable($cachedir_temp . '/db_last_error.php'))
2665
		print_error('Error: Unable to obtain write access to "db_last_error.php".');
2666
2667
	if (!file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php'))
2668
		print_error('Error: Unable to find language files!', true);
2669
	else
2670
	{
2671
		$temp = substr(@implode('', @file($modSettings['theme_dir'] . '/languages/index.' . $upcontext['language'] . '.php')), 0, 4096);
2672
		preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
2673
2674
		if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
2675
			print_error('Error: Language files out of date.', true);
2676
		if (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php'))
2677
			print_error('Error: Install language is missing for selected language.', true);
2678
2679
		// Otherwise include it!
2680
		require_once($modSettings['theme_dir'] . '/languages/Install.' . $upcontext['language'] . '.php');
2681
	}
2682
2683
	// Make sure we skip the HTML for login.
2684
	$_POST['upcont'] = true;
2685
	$upcontext['current_step'] = 1;
2686
}
2687
2688
/**
2689
 * Handles converting your database to UTF-8
2690
 */
2691
function ConvertUtf8()
2692
{
2693
	global $upcontext, $db_character_set, $sourcedir, $smcFunc, $modSettings, $language;
2694
	global $db_prefix, $db_type, $command_line, $support_js, $txt;
2695
2696
	// Done it already?
2697
	if (!empty($_POST['utf8_done']))
2698
		return true;
2699
2700
	// First make sure they aren't already on UTF-8 before we go anywhere...
2701
	if ($db_type == 'postgresql' || ($db_character_set === 'utf8' && !empty($modSettings['global_character_set']) && $modSettings['global_character_set'] === 'UTF-8'))
2702
	{
2703
		$smcFunc['db_insert']('replace',
2704
			'{db_prefix}settings',
2705
			array('variable' => 'string', 'value' => 'string'),
2706
			array(array('global_character_set', 'UTF-8')),
2707
			array('variable')
2708
		);
2709
2710
		return true;
2711
	}
2712
	else
2713
	{
2714
		$upcontext['page_title'] = $txt['converting_utf8'];
2715
		$upcontext['sub_template'] = isset($_GET['xml']) ? 'convert_xml' : 'convert_utf8';
2716
2717
		// The character sets used in SMF's language files with their db equivalent.
2718
		$charsets = array(
2719
			// Armenian
2720
			'armscii8' => 'armscii8',
2721
			// Chinese-traditional.
2722
			'big5' => 'big5',
2723
			// Chinese-simplified.
2724
			'gbk' => 'gbk',
2725
			// West European.
2726
			'ISO-8859-1' => 'latin1',
2727
			// Romanian.
2728
			'ISO-8859-2' => 'latin2',
2729
			// Turkish.
2730
			'ISO-8859-9' => 'latin5',
2731
			// Latvian
2732
			'ISO-8859-13' => 'latin7',
2733
			// West European with Euro sign.
2734
			'ISO-8859-15' => 'latin9',
2735
			// Thai.
2736
			'tis-620' => 'tis620',
2737
			// Persian, Chinese, etc.
2738
			'UTF-8' => 'utf8',
2739
			// Russian.
2740
			'windows-1251' => 'cp1251',
2741
			// Greek.
2742
			'windows-1253' => 'utf8',
2743
			// Hebrew.
2744
			'windows-1255' => 'utf8',
2745
			// Arabic.
2746
			'windows-1256' => 'cp1256',
2747
		);
2748
2749
		// Get a list of character sets supported by your MySQL server.
2750
		$request = $smcFunc['db_query']('', '
2751
			SHOW CHARACTER SET',
2752
			array(
2753
			)
2754
		);
2755
		$db_charsets = array();
2756
		while ($row = $smcFunc['db_fetch_assoc']($request))
2757
			$db_charsets[] = $row['Charset'];
2758
2759
		$smcFunc['db_free_result']($request);
2760
2761
		// Character sets supported by both MySQL and SMF's language files.
2762
		$charsets = array_intersect($charsets, $db_charsets);
2763
2764
		// Use the messages.body column as indicator for the database charset.
2765
		$request = $smcFunc['db_query']('', '
2766
			SHOW FULL COLUMNS
2767
			FROM {db_prefix}messages
2768
			LIKE {string:body_like}',
2769
			array(
2770
				'body_like' => 'body',
2771
			)
2772
		);
2773
		$column_info = $smcFunc['db_fetch_assoc']($request);
2774
		$smcFunc['db_free_result']($request);
2775
2776
		// A collation looks like latin1_swedish. We only need the character set.
2777
		list($upcontext['database_charset']) = explode('_', $column_info['Collation']);
2778
		$upcontext['database_charset'] = in_array($upcontext['database_charset'], $charsets) ? array_search($upcontext['database_charset'], $charsets) : $upcontext['database_charset'];
2779
2780
		// Detect whether a fulltext index is set.
2781
		$request = $smcFunc['db_query']('', '
2782
			SHOW INDEX
2783
			FROM {db_prefix}messages',
2784
			array(
2785
			)
2786
		);
2787
2788
		$upcontext['dropping_index'] = false;
2789
2790
		// If there's a fulltext index, we need to drop it first...
2791
		if ($request !== false || $smcFunc['db_num_rows']($request) != 0)
2792
		{
2793
			while ($row = $smcFunc['db_fetch_assoc']($request))
2794
				if ($row['Column_name'] == 'body' && (isset($row['Index_type']) && $row['Index_type'] == 'FULLTEXT' || isset($row['Comment']) && $row['Comment'] == 'FULLTEXT'))
2795
					$upcontext['fulltext_index'][] = $row['Key_name'];
2796
			$smcFunc['db_free_result']($request);
2797
2798
			if (isset($upcontext['fulltext_index']))
2799
				$upcontext['fulltext_index'] = array_unique($upcontext['fulltext_index']);
2800
		}
2801
2802
		// Drop it and make a note...
2803
		if (!empty($upcontext['fulltext_index']))
2804
		{
2805
			$upcontext['dropping_index'] = true;
2806
2807
			$smcFunc['db_query']('', '
2808
				ALTER TABLE {db_prefix}messages
2809
				DROP INDEX ' . implode(',
2810
				DROP INDEX ', $upcontext['fulltext_index']),
2811
				array(
2812
					'db_error_skip' => true,
2813
				)
2814
			);
2815
2816
			// Update the settings table
2817
			$smcFunc['db_insert']('replace',
2818
				'{db_prefix}settings',
2819
				array('variable' => 'string', 'value' => 'string'),
2820
				array('db_search_index', ''),
2821
				array('variable')
2822
			);
2823
		}
2824
2825
		// Figure out what charset we should be converting from...
2826
		$lang_charsets = array(
2827
			'arabic' => 'windows-1256',
2828
			'armenian_east' => 'armscii-8',
2829
			'armenian_west' => 'armscii-8',
2830
			'azerbaijani_latin' => 'ISO-8859-9',
2831
			'bangla' => 'UTF-8',
2832
			'belarusian' => 'ISO-8859-5',
2833
			'bulgarian' => 'windows-1251',
2834
			'cambodian' => 'UTF-8',
2835
			'chinese_simplified' => 'gbk',
2836
			'chinese_traditional' => 'big5',
2837
			'croation' => 'ISO-8859-2',
2838
			'czech' => 'ISO-8859-2',
2839
			'czech_informal' => 'ISO-8859-2',
2840
			'english_pirate' => 'UTF-8',
2841
			'esperanto' => 'ISO-8859-3',
2842
			'estonian' => 'ISO-8859-15',
2843
			'filipino_tagalog' => 'UTF-8',
2844
			'filipino_vasayan' => 'UTF-8',
2845
			'georgian' => 'UTF-8',
2846
			'greek' => 'ISO-8859-3',
2847
			'hebrew' => 'windows-1255',
2848
			'hungarian' => 'ISO-8859-2',
2849
			'irish' => 'UTF-8',
2850
			'japanese' => 'UTF-8',
2851
			'khmer' => 'UTF-8',
2852
			'korean' => 'UTF-8',
2853
			'kurdish_kurmanji' => 'ISO-8859-9',
2854
			'kurdish_sorani' => 'windows-1256',
2855
			'lao' => 'tis-620',
2856
			'latvian' => 'ISO-8859-13',
2857
			'lithuanian' => 'ISO-8859-4',
2858
			'macedonian' => 'UTF-8',
2859
			'malayalam' => 'UTF-8',
2860
			'mongolian' => 'UTF-8',
2861
			'nepali' => 'UTF-8',
2862
			'persian' => 'UTF-8',
2863
			'polish' => 'ISO-8859-2',
2864
			'romanian' => 'ISO-8859-2',
2865
			'russian' => 'windows-1252',
2866
			'sakha' => 'UTF-8',
2867
			'serbian_cyrillic' => 'ISO-8859-5',
2868
			'serbian_latin' => 'ISO-8859-2',
2869
			'sinhala' => 'UTF-8',
2870
			'slovak' => 'ISO-8859-2',
2871
			'slovenian' => 'ISO-8859-2',
2872
			'telugu' => 'UTF-8',
2873
			'thai' => 'tis-620',
2874
			'turkish' => 'ISO-8859-9',
2875
			'turkmen' => 'ISO-8859-9',
2876
			'ukranian' => 'windows-1251',
2877
			'urdu' => 'UTF-8',
2878
			'uzbek_cyrillic' => 'ISO-8859-5',
2879
			'uzbek_latin' => 'ISO-8859-5',
2880
			'vietnamese' => 'UTF-8',
2881
			'yoruba' => 'UTF-8'
2882
		);
2883
2884
		// Default to ISO-8859-1 unless we detected another supported charset
2885
		$upcontext['charset_detected'] = (isset($lang_charsets[$language]) && isset($charsets[strtr(strtolower($upcontext['charset_detected']), array('utf' => 'UTF', 'iso' => 'ISO'))])) ? $lang_charsets[$language] : 'ISO-8859-1';
2886
2887
		$upcontext['charset_list'] = array_keys($charsets);
2888
2889
		// Translation table for the character sets not native for MySQL.
2890
		$translation_tables = array(
2891
			'windows-1255' => array(
2892
				'0x81' => '\'\'',		'0x8A' => '\'\'',		'0x8C' => '\'\'',
2893
				'0x8D' => '\'\'',		'0x8E' => '\'\'',		'0x8F' => '\'\'',
2894
				'0x90' => '\'\'',		'0x9A' => '\'\'',		'0x9C' => '\'\'',
2895
				'0x9D' => '\'\'',		'0x9E' => '\'\'',		'0x9F' => '\'\'',
2896
				'0xCA' => '\'\'',		'0xD9' => '\'\'',		'0xDA' => '\'\'',
2897
				'0xDB' => '\'\'',		'0xDC' => '\'\'',		'0xDD' => '\'\'',
2898
				'0xDE' => '\'\'',		'0xDF' => '\'\'',		'0xFB' => '0xD792',
2899
				'0xFC' => '0xE282AC',		'0xFF' => '0xD6B2',		'0xC2' => '0xFF',
2900
				'0x80' => '0xFC',		'0xE2' => '0xFB',		'0xA0' => '0xC2A0',
2901
				'0xA1' => '0xC2A1',		'0xA2' => '0xC2A2',		'0xA3' => '0xC2A3',
2902
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
2903
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
2904
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
2905
				'0xAF' => '0xC2AF',		'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',
2906
				'0xB2' => '0xC2B2',		'0xB3' => '0xC2B3',		'0xB4' => '0xC2B4',
2907
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
2908
				'0xB8' => '0xC2B8',		'0xB9' => '0xC2B9',		'0xBB' => '0xC2BB',
2909
				'0xBC' => '0xC2BC',		'0xBD' => '0xC2BD',		'0xBE' => '0xC2BE',
2910
				'0xBF' => '0xC2BF',		'0xD7' => '0xD7B3',		'0xD1' => '0xD781',
2911
				'0xD4' => '0xD7B0',		'0xD5' => '0xD7B1',		'0xD6' => '0xD7B2',
2912
				'0xE0' => '0xD790',		'0xEA' => '0xD79A',		'0xEC' => '0xD79C',
2913
				'0xED' => '0xD79D',		'0xEE' => '0xD79E',		'0xEF' => '0xD79F',
2914
				'0xF0' => '0xD7A0',		'0xF1' => '0xD7A1',		'0xF2' => '0xD7A2',
2915
				'0xF3' => '0xD7A3',		'0xF5' => '0xD7A5',		'0xF6' => '0xD7A6',
2916
				'0xF7' => '0xD7A7',		'0xF8' => '0xD7A8',		'0xF9' => '0xD7A9',
2917
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
2918
				'0x86' => '0xE280A0',	'0x87' => '0xE280A1',	'0x89' => '0xE280B0',
2919
				'0x8B' => '0xE280B9',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
2920
				'0x95' => '0xE280A2',	'0x97' => '0xE28094',	'0x99' => '0xE284A2',
2921
				'0xC0' => '0xD6B0',		'0xC1' => '0xD6B1',		'0xC3' => '0xD6B3',
2922
				'0xC4' => '0xD6B4',		'0xC5' => '0xD6B5',		'0xC6' => '0xD6B6',
2923
				'0xC7' => '0xD6B7',		'0xC8' => '0xD6B8',		'0xC9' => '0xD6B9',
2924
				'0xCB' => '0xD6BB',		'0xCC' => '0xD6BC',		'0xCD' => '0xD6BD',
2925
				'0xCE' => '0xD6BE',		'0xCF' => '0xD6BF',		'0xD0' => '0xD780',
2926
				'0xD2' => '0xD782',		'0xE3' => '0xD793',		'0xE4' => '0xD794',
2927
				'0xE5' => '0xD795',		'0xE7' => '0xD797',		'0xE9' => '0xD799',
2928
				'0xFD' => '0xE2808E',	'0xFE' => '0xE2808F',	'0x92' => '0xE28099',
2929
				'0x83' => '0xC692',		'0xD3' => '0xD783',		'0x88' => '0xCB86',
2930
				'0x98' => '0xCB9C',		'0x91' => '0xE28098',	'0x96' => '0xE28093',
2931
				'0xBA' => '0xC3B7',		'0x9B' => '0xE280BA',	'0xAA' => '0xC397',
2932
				'0xA4' => '0xE282AA',	'0xE1' => '0xD791',		'0xE6' => '0xD796',
2933
				'0xE8' => '0xD798',		'0xEB' => '0xD79B',		'0xF4' => '0xD7A4',
2934
				'0xFA' => '0xD7AA',
2935
			),
2936
			'windows-1253' => array(
2937
				'0x81' => '\'\'',			'0x88' => '\'\'',			'0x8A' => '\'\'',
2938
				'0x8C' => '\'\'',			'0x8D' => '\'\'',			'0x8E' => '\'\'',
2939
				'0x8F' => '\'\'',			'0x90' => '\'\'',			'0x98' => '\'\'',
2940
				'0x9A' => '\'\'',			'0x9C' => '\'\'',			'0x9D' => '\'\'',
2941
				'0x9E' => '\'\'',			'0x9F' => '\'\'',			'0xAA' => '\'\'',
2942
				'0xD2' => '0xE282AC',			'0xFF' => '0xCE92',			'0xCE' => '0xCE9E',
2943
				'0xB8' => '0xCE88',		'0xBA' => '0xCE8A',		'0xBC' => '0xCE8C',
2944
				'0xBE' => '0xCE8E',		'0xBF' => '0xCE8F',		'0xC0' => '0xCE90',
2945
				'0xC8' => '0xCE98',		'0xCA' => '0xCE9A',		'0xCC' => '0xCE9C',
2946
				'0xCD' => '0xCE9D',		'0xCF' => '0xCE9F',		'0xDA' => '0xCEAA',
2947
				'0xE8' => '0xCEB8',		'0xEA' => '0xCEBA',		'0xEC' => '0xCEBC',
2948
				'0xEE' => '0xCEBE',		'0xEF' => '0xCEBF',		'0xC2' => '0xFF',
2949
				'0xBD' => '0xC2BD',		'0xED' => '0xCEBD',		'0xB2' => '0xC2B2',
2950
				'0xA0' => '0xC2A0',		'0xA3' => '0xC2A3',		'0xA4' => '0xC2A4',
2951
				'0xA5' => '0xC2A5',		'0xA6' => '0xC2A6',		'0xA7' => '0xC2A7',
2952
				'0xA8' => '0xC2A8',		'0xA9' => '0xC2A9',		'0xAB' => '0xC2AB',
2953
				'0xAC' => '0xC2AC',		'0xAD' => '0xC2AD',		'0xAE' => '0xC2AE',
2954
				'0xB0' => '0xC2B0',		'0xB1' => '0xC2B1',		'0xB3' => '0xC2B3',
2955
				'0xB5' => '0xC2B5',		'0xB6' => '0xC2B6',		'0xB7' => '0xC2B7',
2956
				'0xBB' => '0xC2BB',		'0xE2' => '0xCEB2',		'0x80' => '0xD2',
2957
				'0x82' => '0xE2809A',	'0x84' => '0xE2809E',	'0x85' => '0xE280A6',
2958
				'0x86' => '0xE280A0',	'0xA1' => '0xCE85',		'0xA2' => '0xCE86',
2959
				'0x87' => '0xE280A1',	'0x89' => '0xE280B0',	'0xB9' => '0xCE89',
2960
				'0x8B' => '0xE280B9',	'0x91' => '0xE28098',	'0x99' => '0xE284A2',
2961
				'0x92' => '0xE28099',	'0x93' => '0xE2809C',	'0x94' => '0xE2809D',
2962
				'0x95' => '0xE280A2',	'0x96' => '0xE28093',	'0x97' => '0xE28094',
2963
				'0x9B' => '0xE280BA',	'0xAF' => '0xE28095',	'0xB4' => '0xCE84',
2964
				'0xC1' => '0xCE91',		'0xC3' => '0xCE93',		'0xC4' => '0xCE94',
2965
				'0xC5' => '0xCE95',		'0xC6' => '0xCE96',		'0x83' => '0xC692',
2966
				'0xC7' => '0xCE97',		'0xC9' => '0xCE99',		'0xCB' => '0xCE9B',
2967
				'0xD0' => '0xCEA0',		'0xD1' => '0xCEA1',		'0xD3' => '0xCEA3',
2968
				'0xD4' => '0xCEA4',		'0xD5' => '0xCEA5',		'0xD6' => '0xCEA6',
2969
				'0xD7' => '0xCEA7',		'0xD8' => '0xCEA8',		'0xD9' => '0xCEA9',
2970
				'0xDB' => '0xCEAB',		'0xDC' => '0xCEAC',		'0xDD' => '0xCEAD',
2971
				'0xDE' => '0xCEAE',		'0xDF' => '0xCEAF',		'0xE0' => '0xCEB0',
2972
				'0xE1' => '0xCEB1',		'0xE3' => '0xCEB3',		'0xE4' => '0xCEB4',
2973
				'0xE5' => '0xCEB5',		'0xE6' => '0xCEB6',		'0xE7' => '0xCEB7',
2974
				'0xE9' => '0xCEB9',		'0xEB' => '0xCEBB',		'0xF0' => '0xCF80',
2975
				'0xF1' => '0xCF81',		'0xF2' => '0xCF82',		'0xF3' => '0xCF83',
2976
				'0xF4' => '0xCF84',		'0xF5' => '0xCF85',		'0xF6' => '0xCF86',
2977
				'0xF7' => '0xCF87',		'0xF8' => '0xCF88',		'0xF9' => '0xCF89',
2978
				'0xFA' => '0xCF8A',		'0xFB' => '0xCF8B',		'0xFC' => '0xCF8C',
2979
				'0xFD' => '0xCF8D',		'0xFE' => '0xCF8E',
2980
			),
2981
		);
2982
2983
		// Make some preparations.
2984
		if (isset($translation_tables[$upcontext['charset_detected']]))
2985
		{
2986
			$replace = '%field%';
2987
2988
			// Build a huge REPLACE statement...
2989
			foreach ($translation_tables[$upcontext['charset_detected']] as $from => $to)
2990
				$replace = 'REPLACE(' . $replace . ', ' . $from . ', ' . $to . ')';
2991
		}
2992
2993
		// Get a list of table names ahead of time... This makes it easier to set our substep and such
2994
		db_extend();
2995
		$queryTables = $smcFunc['db_list_tables'](false, $db_prefix . '%');
2996
2997
		$upcontext['table_count'] = count($queryTables);
2998
2999
		// What ones have we already done?
3000
		foreach ($queryTables as $id => $table)
3001
			if ($id < $_GET['substep'])
3002
				$upcontext['previous_tables'][] = $table;
3003
3004
		$upcontext['cur_table_num'] = $_GET['substep'];
3005
		$upcontext['cur_table_name'] = str_replace($db_prefix, '', $queryTables[$_GET['substep']]);
3006
		$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3007
3008
		// Make sure we're ready & have painted the template before proceeding
3009
		if ($support_js && !isset($_GET['xml']))
3010
		{
3011
			$_GET['substep'] = 0;
3012
			return false;
3013
		}
3014
3015
		// We want to start at the first table.
3016
		for ($substep = $_GET['substep'], $n = count($queryTables); $substep < $n; $substep++)
3017
		{
3018
			$table = $queryTables[$substep];
3019
3020
			$getTableStatus = $smcFunc['db_query']('', '
3021
				SHOW TABLE STATUS
3022
				LIKE {string:table_name}',
3023
				array(
3024
					'table_name' => str_replace('_', '\_', $table)
3025
				)
3026
			);
3027
3028
			// Only one row so we can just fetch_assoc and free the result...
3029
			$table_info = $smcFunc['db_fetch_assoc']($getTableStatus);
3030
			$smcFunc['db_free_result']($getTableStatus);
3031
3032
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($queryTables[$substep + 1]) ? $queryTables[$substep + 1] : $queryTables[$substep]));
3033
			$upcontext['cur_table_num'] = $substep + 1;
3034
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3035
3036
			// Do we need to pause?
3037
			nextSubstep($substep);
3038
3039
			// Just to make sure it doesn't time out.
3040
			if (function_exists('apache_reset_timeout'))
3041
				@apache_reset_timeout();
3042
3043
			$table_charsets = array();
3044
3045
			// Loop through each column.
3046
			$queryColumns = $smcFunc['db_query']('', '
3047
				SHOW FULL COLUMNS
3048
				FROM ' . $table_info['Name'],
3049
				array(
3050
				)
3051
			);
3052
			while ($column_info = $smcFunc['db_fetch_assoc']($queryColumns))
3053
			{
3054
				// Only text'ish columns have a character set and need converting.
3055
				if (strpos($column_info['Type'], 'text') !== false || strpos($column_info['Type'], 'char') !== false)
3056
				{
3057
					$collation = empty($column_info['Collation']) || $column_info['Collation'] === 'NULL' ? $table_info['Collation'] : $column_info['Collation'];
3058
					if (!empty($collation) && $collation !== 'NULL')
3059
					{
3060
						list($charset) = explode('_', $collation);
3061
3062
						// Build structure of columns to operate on organized by charset; only operate on columns not yet utf8
3063
						if ($charset != 'utf8')
3064
						{
3065
							if (!isset($table_charsets[$charset]))
3066
								$table_charsets[$charset] = array();
3067
3068
							$table_charsets[$charset][] = $column_info;
3069
						}
3070
					}
3071
				}
3072
			}
3073
			$smcFunc['db_free_result']($queryColumns);
3074
3075
			// Only change the non-utf8 columns identified above
3076
			if (count($table_charsets) > 0)
3077
			{
3078
				$updates_blob = '';
3079
				$updates_text = '';
3080
				foreach ($table_charsets as $charset => $columns)
3081
				{
3082
					if ($charset !== $charsets[$upcontext['charset_detected']])
3083
					{
3084
						foreach ($columns as $column)
3085
						{
3086
							$updates_blob .= '
3087
								CHANGE COLUMN `' . $column['Field'] . '` `' . $column['Field'] . '` ' . strtr($column['Type'], array('text' => 'blob', 'char' => 'binary')) . ($column['Null'] === 'YES' ? ' NULL' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
3088
							$updates_text .= '
3089
								CHANGE COLUMN `' . $column['Field'] . '` `' . $column['Field'] . '` ' . $column['Type'] . ' CHARACTER SET ' . $charsets[$upcontext['charset_detected']] . ($column['Null'] === 'YES' ? '' : ' NOT NULL') . (strpos($column['Type'], 'char') === false ? '' : ' default \'' . $column['Default'] . '\'') . ',';
3090
						}
3091
					}
3092
				}
3093
3094
				// Change the columns to binary form.
3095
				$smcFunc['db_query']('', '
3096
					ALTER TABLE {raw:table_name}{raw:updates_blob}',
3097
					array(
3098
						'table_name' => $table_info['Name'],
3099
						'updates_blob' => substr($updates_blob, 0, -1),
3100
					)
3101
				);
3102
3103
				// Convert the character set if MySQL has no native support for it.
3104
				if (isset($translation_tables[$upcontext['charset_detected']]))
3105
				{
3106
					$update = '';
3107
					foreach ($table_charsets as $charset => $columns)
3108
						foreach ($columns as $column)
3109
							$update .= '
3110
								' . $column['Field'] . ' = ' . strtr($replace, array('%field%' => $column['Field'])) . ',';
0 ignored issues
show
Comprehensibility Best Practice introduced by
The variable $replace does not seem to be defined for all execution paths leading up to this point.
Loading history...
3111
3112
					$smcFunc['db_query']('', '
3113
						UPDATE {raw:table_name}
3114
						SET {raw:updates}',
3115
						array(
3116
							'table_name' => $table_info['Name'],
3117
							'updates' => substr($update, 0, -1),
3118
						)
3119
					);
3120
				}
3121
3122
				// Change the columns back, but with the proper character set.
3123
				$smcFunc['db_query']('', '
3124
					ALTER TABLE {raw:table_name}{raw:updates_text}',
3125
					array(
3126
						'table_name' => $table_info['Name'],
3127
						'updates_text' => substr($updates_text, 0, -1),
3128
					)
3129
				);
3130
			}
3131
3132
			// Now do the actual conversion (if still needed).
3133
			if ($charsets[$upcontext['charset_detected']] !== 'utf8')
3134
			{
3135
				if ($command_line)
3136
					echo 'Converting table ' . $table_info['Name'] . ' to UTF-8...';
3137
3138
				$smcFunc['db_query']('', '
3139
					ALTER TABLE {raw:table_name}
3140
					CONVERT TO CHARACTER SET utf8',
3141
					array(
3142
						'table_name' => $table_info['Name'],
3143
					)
3144
				);
3145
3146
				if ($command_line)
3147
					echo " done.\n";
3148
			}
3149
			// If this is XML to keep it nice for the user do one table at a time anyway!
3150
			if (isset($_GET['xml']) && $upcontext['cur_table_num'] < $upcontext['table_count'])
3151
				return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
3152
		}
3153
3154
		$prev_charset = empty($translation_tables[$upcontext['charset_detected']]) ? $charsets[$upcontext['charset_detected']] : $translation_tables[$upcontext['charset_detected']];
3155
3156
		$smcFunc['db_insert']('replace',
3157
			'{db_prefix}settings',
3158
			array('variable' => 'string', 'value' => 'string'),
3159
			array(array('global_character_set', 'UTF-8'), array('previousCharacterSet', $prev_charset)),
3160
			array('variable')
3161
		);
3162
3163
		// Store it in Settings.php too because it's needed before db connection.
3164
		// Hopefully this works...
3165
		require_once($sourcedir . '/Subs-Admin.php');
3166
		updateSettingsFile(array('db_character_set' => '\'utf8\''));
3167
3168
		// The conversion might have messed up some serialized strings. Fix them!
3169
		$request = $smcFunc['db_query']('', '
3170
			SELECT id_action, extra
3171
			FROM {db_prefix}log_actions
3172
			WHERE action IN ({string:remove}, {string:delete})',
3173
			array(
3174
				'remove' => 'remove',
3175
				'delete' => 'delete',
3176
			)
3177
		);
3178
		while ($row = $smcFunc['db_fetch_assoc']($request))
3179
		{
3180
			if (@safe_unserialize($row['extra']) === false && preg_match('~^(a:3:{s:5:"topic";i:\d+;s:7:"subject";s:)(\d+):"(.+)"(;s:6:"member";s:5:"\d+";})$~', $row['extra'], $matches) === 1)
3181
				$smcFunc['db_query']('', '
3182
					UPDATE {db_prefix}log_actions
3183
					SET extra = {string:extra}
3184
					WHERE id_action = {int:current_action}',
3185
					array(
3186
						'current_action' => $row['id_action'],
3187
						'extra' => $matches[1] . strlen($matches[3]) . ':"' . $matches[3] . '"' . $matches[4],
3188
					)
3189
				);
3190
		}
3191
		$smcFunc['db_free_result']($request);
3192
3193
		if ($upcontext['dropping_index'] && $command_line)
3194
		{
3195
			echo "\n" . '', $txt['upgrade_fulltext_error'], '';
3196
			flush();
3197
		}
3198
	}
3199
	$_GET['substep'] = 0;
3200
	return false;
3201
}
3202
3203
/**
3204
 * Attempts to repair corrupted serialized data strings
3205
 *
3206
 * @param string $string Serialized data that has been corrupted
3207
 * @return string|bool A working version of the serialized data, or the original if the repair failed
3208
 */
3209
function fix_serialized_data($string)
3210
{
3211
	// If its not broken, don't fix it.
3212
	if (!is_string($string) || !preg_match('/^[bidsa]:/', $string) || @safe_unserialize($string) !== false)
0 ignored issues
show
introduced by
The condition is_string($string) is always true.
Loading history...
3213
		return $string;
3214
3215
	// This bit fixes incorrect string lengths, which can happen if the character encoding was changed (e.g. conversion to UTF-8)
3216
	$new_string = preg_replace_callback('~\bs:(\d+):"(.*?)";(?=$|[bidsa]:|[{}]|N;)~s', function ($matches) {return 's:' . strlen($matches[2]) . ':"' . $matches[2] . '";';}, $string);
3217
3218
	// @todo Add more possible fixes here. For example, fix incorrect array lengths, try to handle truncated strings gracefully, etc.
3219
3220
	// Did it work?
3221
	if (@safe_unserialize($new_string) !== false)
0 ignored issues
show
introduced by
The condition @safe_unserialize($new_string) !== false is always false.
Loading history...
3222
		return $new_string;
3223
	else
3224
		return $string;
3225
}
3226
3227
function serialize_to_json()
3228
{
3229
	global $command_line, $smcFunc, $modSettings, $sourcedir, $upcontext, $support_js, $txt;
3230
3231
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'serialize_json_xml' : 'serialize_json';
3232
	// First thing's first - did we already do this?
3233
	if (!empty($modSettings['json_done']))
3234
	{
3235
		if ($command_line)
3236
			return ConvertUtf8();
3237
		else
3238
			return true;
3239
	}
3240
3241
	// Done it already - js wise?
3242
	if (!empty($_POST['json_done']))
3243
		return true;
3244
3245
	// List of tables affected by this function
3246
	// name => array('key', col1[,col2|true[,col3]])
3247
	// If 3rd item in array is true, it indicates that col1 could be empty...
3248
	$tables = array(
3249
		'background_tasks' => array('id_task', 'task_data'),
3250
		'log_actions' => array('id_action', 'extra'),
3251
		'log_online' => array('session', 'url'),
3252
		'log_packages' => array('id_install', 'db_changes', 'failed_steps', 'credits'),
3253
		'log_spider_hits' => array('id_hit', 'url'),
3254
		'log_subscribed' => array('id_sublog', 'pending_details'),
3255
		'pm_rules' => array('id_rule', 'criteria', 'actions'),
3256
		'qanda' => array('id_question', 'answers'),
3257
		'subscriptions' => array('id_subscribe', 'cost'),
3258
		'user_alerts' => array('id_alert', 'extra', true),
3259
		'user_drafts' => array('id_draft', 'to_list', true),
3260
		// These last two are a bit different - we'll handle those separately
3261
		'settings' => array(),
3262
		'themes' => array()
3263
	);
3264
3265
	// Set up some context stuff...
3266
	// Because we're not using numeric indices, we need this to figure out the current table name...
3267
	$keys = array_keys($tables);
3268
3269
	$upcontext['page_title'] = $txt['converting_json'];
3270
	$upcontext['table_count'] = count($keys);
3271
	$upcontext['cur_table_num'] = $_GET['substep'];
3272
	$upcontext['cur_table_name'] = isset($keys[$_GET['substep']]) ? $keys[$_GET['substep']] : $keys[0];
3273
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3274
3275
	foreach ($keys as $id => $table)
3276
		if ($id < $_GET['substep'])
3277
			$upcontext['previous_tables'][] = $table;
3278
3279
	if ($command_line)
3280
		echo 'Converting data from serialize() to json_encode().';
3281
3282
	if (!$support_js || isset($_GET['xml']))
3283
	{
3284
		// Fix the data in each table
3285
		for ($substep = $_GET['substep']; $substep < $upcontext['table_count']; $substep++)
3286
		{
3287
			$upcontext['cur_table_name'] = isset($keys[$substep + 1]) ? $keys[$substep + 1] : $keys[$substep];
3288
			$upcontext['cur_table_num'] = $substep + 1;
3289
3290
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
3291
3292
			// Do we need to pause?
3293
			nextSubstep($substep);
3294
3295
			// Initialize a few things...
3296
			$where = '';
3297
			$vars = array();
3298
			$table = $keys[$substep];
3299
			$info = $tables[$table];
3300
3301
			// Now the fun - build our queries and all that fun stuff
3302
			if ($table == 'settings')
3303
			{
3304
				// Now a few settings...
3305
				$serialized_settings = array(
3306
					'attachment_basedirectories',
3307
					'attachmentUploadDir',
3308
					'cal_today_birthday',
3309
					'cal_today_event',
3310
					'cal_today_holiday',
3311
					'displayFields',
3312
					'last_attachments_directory',
3313
					'memberlist_cache',
3314
					'search_custom_index_config',
3315
					'spider_name_cache'
3316
				);
3317
3318
				// Loop through and fix these...
3319
				$new_settings = array();
3320
				if ($command_line)
3321
					echo "\n" . 'Fixing some settings...';
3322
3323
				foreach ($serialized_settings as $var)
3324
				{
3325
					if (isset($modSettings[$var]))
3326
					{
3327
						// Attempt to unserialize the setting
3328
						$temp = @safe_unserialize($modSettings[$var]);
3329
						// Maybe conversion to UTF-8 corrupted it
3330
						if ($temp === false)
3331
							$temp = @safe_unserialize(fix_serialized_data($modSettings[$var]));
3332
3333
						if (!$temp && $command_line)
3334
							echo "\n - Failed to unserialize the '" . $var . "' setting. Skipping.";
3335
						elseif ($temp !== false)
3336
							$new_settings[$var] = json_encode($temp);
3337
					}
3338
				}
3339
3340
				// Update everything at once
3341
				if (!function_exists('cache_put_data'))
3342
					require_once($sourcedir . '/Load.php');
3343
				updateSettings($new_settings, true);
3344
3345
				if ($command_line)
3346
					echo ' done.';
3347
			}
3348
			elseif ($table == 'themes')
3349
			{
3350
				// Finally, fix the admin prefs. Unfortunately this is stored per theme, but hopefully they only have one theme installed at this point...
3351
				$query = $smcFunc['db_query']('', '
3352
					SELECT id_member, id_theme, value FROM {db_prefix}themes
3353
					WHERE variable = {string:admin_prefs}',
3354
					array(
3355
						'admin_prefs' => 'admin_preferences'
3356
					)
3357
				);
3358
3359
				if ($smcFunc['db_num_rows']($query) != 0)
3360
				{
3361
					while ($row = $smcFunc['db_fetch_assoc']($query))
3362
					{
3363
						$temp = @safe_unserialize($row['value']);
3364
						if ($temp === false)
3365
							$temp = @safe_unserialize(fix_serialized_data($row['value']));
3366
3367
						if ($command_line)
3368
						{
3369
							if ($temp === false)
3370
								echo "\n" . 'Unserialize of admin_preferences for user ' . $row['id_member'] . ' failed. Skipping.';
3371
							else
3372
								echo "\n" . 'Fixing admin preferences...';
3373
						}
3374
3375
						if ($temp !== false)
3376
						{
3377
							$row['value'] = json_encode($temp);
3378
3379
							// Even though we have all values from the table, UPDATE is still faster than REPLACE
3380
							$smcFunc['db_query']('', '
3381
								UPDATE {db_prefix}themes
3382
								SET value = {string:prefs}
3383
								WHERE id_theme = {int:theme}
3384
									AND id_member = {int:member}
3385
									AND variable = {string:admin_prefs}',
3386
								array(
3387
									'prefs' => $row['value'],
3388
									'theme' => $row['id_theme'],
3389
									'member' => $row['id_member'],
3390
									'admin_prefs' => 'admin_preferences'
3391
								)
3392
							);
3393
3394
							if ($command_line)
3395
								echo ' done.';
3396
						}
3397
					}
3398
3399
					$smcFunc['db_free_result']($query);
3400
				}
3401
			}
3402
			else
3403
			{
3404
				// First item is always the key...
3405
				$key = $info[0];
3406
				unset($info[0]);
3407
3408
				// Now we know what columns we have and such...
3409
				if (count($info) == 2 && $info[2] === true)
3410
				{
3411
					$col_select = $info[1];
3412
					$where = ' WHERE ' . $info[1] . ' != {empty}';
3413
				}
3414
				else
3415
				{
3416
					$col_select = implode(', ', $info);
3417
				}
3418
3419
				$query = $smcFunc['db_query']('', '
3420
					SELECT ' . $key . ', ' . $col_select . '
3421
					FROM {db_prefix}' . $table . $where,
3422
					array()
3423
				);
3424
3425
				if ($smcFunc['db_num_rows']($query) != 0)
3426
				{
3427
					if ($command_line)
3428
					{
3429
						echo "\n" . ' +++ Fixing the "' . $table . '" table...';
3430
						flush();
3431
					}
3432
3433
					while ($row = $smcFunc['db_fetch_assoc']($query))
3434
					{
3435
						$update = '';
3436
3437
						// We already know what our key is...
3438
						foreach ($info as $col)
3439
						{
3440
							if ($col !== true && $row[$col] != '')
3441
							{
3442
								$temp = @safe_unserialize($row[$col]);
3443
3444
								// Maybe we can fix the data?
3445
								if ($temp === false)
3446
									$temp = @safe_unserialize(fix_serialized_data($row[$col]));
3447
3448
								// Maybe the data is already JSON?
3449
								if ($temp === false)
3450
									$temp = smf_json_decode($row[$col], true, false);
3451
3452
								// Oh well...
3453
								if ($temp === null)
3454
								{
3455
									$temp = array();
3456
3457
									if ($command_line)
3458
										echo "\nFailed to unserialize " . $row[$col] . ". Setting to empty value.\n";
3459
								}
3460
3461
								$row[$col] = json_encode($temp);
3462
3463
								// Build our SET string and variables array
3464
								$update .= (empty($update) ? '' : ', ') . $col . ' = {string:' . $col . '}';
3465
								$vars[$col] = $row[$col];
3466
							}
3467
						}
3468
3469
						$vars[$key] = $row[$key];
3470
3471
						// In a few cases, we might have empty data, so don't try to update in those situations...
3472
						if (!empty($update))
3473
						{
3474
							$smcFunc['db_query']('', '
3475
								UPDATE {db_prefix}' . $table . '
3476
								SET ' . $update . '
3477
								WHERE ' . $key . ' = {' . ($key == 'session' ? 'string' : 'int') . ':' . $key . '}',
3478
								$vars
3479
							);
3480
						}
3481
					}
3482
3483
					if ($command_line)
3484
						echo ' done.';
3485
3486
					// Free up some memory...
3487
					$smcFunc['db_free_result']($query);
3488
				}
3489
			}
3490
			// If this is XML to keep it nice for the user do one table at a time anyway!
3491
			if (isset($_GET['xml']))
3492
				return upgradeExit();
0 ignored issues
show
Bug introduced by
Are you sure the usage of upgradeExit() is correct as it seems to always return null.

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

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

}

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

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

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

Loading history...
3493
		}
3494
3495
		if ($command_line)
3496
		{
3497
			echo "\n" . 'Successful.' . "\n";
3498
			flush();
3499
		}
3500
		$upcontext['step_progress'] = 100;
3501
3502
		// Last but not least, insert a dummy setting so we don't have to do this again in the future...
3503
		updateSettings(array('json_done' => true));
3504
3505
		$_GET['substep'] = 0;
3506
		// Make sure we move on!
3507
		if ($command_line)
3508
			return ConvertUtf8();
3509
3510
		return true;
3511
	}
3512
3513
	// If this fails we just move on to deleting the upgrade anyway...
3514
	$_GET['substep'] = 0;
3515
	return false;
3516
}
3517
3518
/**
3519
 * As of 2.1, we want to store db_last_error.php in the cache
3520
 * To make that happen, Settings.php needs to ensure the $cachedir path is correct before trying to write to db_last_error.php
3521
 */
3522
function move_db_last_error_to_cachedir()
3523
{
3524
	$settings = file_get_contents(dirname(__FILE__) . '/Settings.php');
3525
3526
	$regex = <<<'EOT'
3527
(\s*#\s*Make\s+sure\s+the\s+paths\s+are\s+correct\.\.\.\s+at\s+least\s+try\s+to\s+fix\s+them\.\s+)?if\s*\(\!file_exists\(\$boarddir\)\s+&&\s+file_exists\(dirname\(__FILE__\)\s+\.\s+'/agreement\.txt'\)\)\s+\$boarddir\s*\=\s*dirname\(__FILE__\);\s+if\s*\(\!file_exists\(\$sourcedir\)\s+&&\s+file_exists\(\$boarddir\s*\.\s*'/Sources'\)\)\s+\$sourcedir\s*\=\s*\$boarddir\s*\.\s*'/Sources';\s+if\s*\(\!file_exists\(\$cachedir\)\s+&&\s+file_exists\(\$boarddir\s*\.\s*'/cache'\)\)\s+\$cachedir\s*\=\s*\$boarddir\s*\.\s*'/cache';
3528
EOT;
3529
3530
	$replacement = <<<'EOT'
3531
# Make sure the paths are correct... at least try to fix them.
3532
if (!file_exists($boarddir) && file_exists(dirname(__FILE__) . '/agreement.txt'))
3533
	$boarddir = dirname(__FILE__);
3534
if (!file_exists($sourcedir) && file_exists($boarddir . '/Sources'))
3535
	$sourcedir = $boarddir . '/Sources';
3536
if (!file_exists($cachedir) && file_exists($boarddir . '/cache'))
3537
	$cachedir = $boarddir . '/cache';
3538
3539
3540
EOT;
3541
3542
	if (preg_match('~' . $regex . '~', $settings) && preg_match('~(#+\s*Error-Catching\s*#+)~', $settings))
3543
	{
3544
		$settings = preg_replace('~' . $regex . '~', '', $settings);
3545
		$settings = preg_replace('~(#+\s*Error-Catching\s*#+)~', $replacement . '$1', $settings);
3546
		$settings = preg_replace('~dirname(__FILE__) . \'/db_last_error.php\'~', '(isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\'', $settings);
3547
3548
		// Blank out the file - done to fix a oddity with some servers.
3549
		file_put_contents(dirname(__FILE__) . '/Settings.php', '');
3550
3551
		file_put_contents(dirname(__FILE__) . '/Settings.php', $settings);
3552
	}
3553
}
3554
3555
/* ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
3556
						Templates are below this point
3557
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ */
3558
3559
// This is what is displayed if there's any chmod to be done. If not it returns nothing...
3560
function template_chmod()
3561
{
3562
	global $upcontext, $txt, $settings;
3563
3564
	// Don't call me twice!
3565
	if (!empty($upcontext['chmod_called']))
3566
		return;
3567
3568
	$upcontext['chmod_called'] = true;
3569
3570
	// Nothing?
3571
	if (empty($upcontext['chmod']['files']) && empty($upcontext['chmod']['ftp_error']))
3572
		return;
3573
3574
	// Was it a problem with Windows?
3575
	if (!empty($upcontext['chmod']['ftp_error']) && $upcontext['chmod']['ftp_error'] == 'total_mess')
3576
	{
3577
		echo '
3578
		<div class="error">
3579
			<p>', $txt['upgrade_writable_files'], '</p>
3580
			<ul class="error_content">
3581
				<li>' . implode('</li>
3582
				<li>', $upcontext['chmod']['files']) . '</li>
3583
			</ul>
3584
		</div>';
3585
3586
		return false;
3587
	}
3588
3589
	echo '
3590
		<div class="panel">
3591
			<h2>', $txt['upgrade_ftp_login'], '</h2>
3592
			<h3>', $txt['upgrade_ftp_perms'], '</h3>
3593
			<script>
3594
				function warning_popup()
3595
				{
3596
					popup = window.open(\'\',\'popup\',\'height=150,width=400,scrollbars=yes\');
3597
					var content = popup.document;
3598
					content.write(\'<!DOCTYPE html>\n\');
3599
					content.write(\'<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>\n\t<head>\n\t\t<meta name="robots" content="noindex">\n\t\t\');
3600
					content.write(\'<title>', $txt['upgrade_ftp_warning'], '</title>\n\t\t<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">\n\t</head>\n\t<body id="popup">\n\t\t\');
3601
					content.write(\'<div class="windowbg description">\n\t\t\t<h4>', $txt['upgrade_ftp_files'], '</h4>\n\t\t\t\');
3602
					content.write(\'<p>', implode('<br>\n\t\t\t', $upcontext['chmod']['files']), '</p>\n\t\t\t\');';
3603
3604
	if (isset($upcontext['systemos']) && $upcontext['systemos'] == 'linux')
3605
		echo '
3606
					content.write(\'<hr>\n\t\t\t\');
3607
					content.write(\'<p>', $txt['upgrade_ftp_shell'], '</p>\n\t\t\t\');
3608
					content.write(\'<tt># chmod a+w ', implode(' ', $upcontext['chmod']['files']), '</tt>\n\t\t\t\');';
3609
3610
	echo '
3611
					content.write(\'<a href="javascript:self.close();">close</a>\n\t\t</div>\n\t</body>\n</html>\');
3612
					content.close();
3613
				}
3614
			</script>';
3615
3616
	if (!empty($upcontext['chmod']['ftp_error']))
3617
		echo '
3618
			<div class="error">
3619
				<p>', $txt['upgrade_ftp_error'], '<p>
3620
				<code>', $upcontext['chmod']['ftp_error'], '</code>
3621
			</div>';
3622
3623
	if (empty($upcontext['chmod_in_form']))
3624
		echo '
3625
			<form action="', $upcontext['form_url'], '" method="post">';
3626
3627
	echo '
3628
				<dl class="settings">
3629
					<dt>
3630
						<label for="ftp_server">', $txt['ftp_server'], ':</label>
3631
					</dt>
3632
					<dd>
3633
						<div class="floatright">
3634
							<label for="ftp_port" class="textbox"><strong>', $txt['ftp_port'], ':</strong></label>
3635
							<input type="text" size="3" name="ftp_port" id="ftp_port" value="', isset($upcontext['chmod']['port']) ? $upcontext['chmod']['port'] : '21', '">
3636
						</div>
3637
						<input type="text" size="30" name="ftp_server" id="ftp_server" value="', isset($upcontext['chmod']['server']) ? $upcontext['chmod']['server'] : 'localhost', '">
3638
						<div class="smalltext">', $txt['ftp_server_info'], '</div>
3639
					</dd>
3640
					<dt>
3641
						<label for="ftp_username">', $txt['ftp_username'], ':</label>
3642
					</dt>
3643
					<dd>
3644
						<input type="text" size="30" name="ftp_username" id="ftp_username" value="', isset($upcontext['chmod']['username']) ? $upcontext['chmod']['username'] : '', '">
3645
						<div class="smalltext">', $txt['ftp_username_info'], '</div>
3646
					</dd>
3647
					<dt>
3648
						<label for="ftp_password">', $txt['ftp_password'], ':</label>
3649
					</dt>
3650
					<dd>
3651
						<input type="password" size="30" name="ftp_password" id="ftp_password">
3652
						<div class="smalltext">', $txt['ftp_password_info'], '</div>
3653
					</dd>
3654
					<dt>
3655
						<label for="ftp_path">', $txt['ftp_path'], ':</label>
3656
					</dt>
3657
					<dd>
3658
						<input type="text" size="30" name="ftp_path" id="ftp_path" value="', isset($upcontext['chmod']['path']) ? $upcontext['chmod']['path'] : '', '">
3659
						<div class="smalltext">', !empty($upcontext['chmod']['path']) ? $txt['ftp_path_found_info'] : $txt['ftp_path_info'], '</div>
3660
					</dd>
3661
				</dl>
3662
3663
				<div class="righttext buttons">
3664
					<input type="submit" value="', $txt['ftp_connect'], '" class="button">
3665
				</div>';
3666
3667
	if (empty($upcontext['chmod_in_form']))
3668
		echo '
3669
			</form>';
3670
3671
	echo '
3672
		</div><!-- .panel -->';
3673
}
3674
3675
function template_upgrade_above()
3676
{
3677
	global $modSettings, $txt, $settings, $upcontext, $upgradeurl;
3678
3679
	echo '<!DOCTYPE html>
3680
<html', $txt['lang_rtl'] == true ? ' dir="rtl"' : '', '>
3681
<head>
3682
	<meta charset="', isset($txt['lang_character_set']) ? $txt['lang_character_set'] : 'UTF-8', '">
3683
	<meta name="robots" content="noindex">
3684
	<title>', $txt['upgrade_upgrade_utility'], '</title>
3685
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/index.css">
3686
	<link rel="stylesheet" href="', $settings['default_theme_url'], '/css/install.css">
3687
	', $txt['lang_rtl'] == true ? '<link rel="stylesheet" href="' . $settings['default_theme_url'] . '/css/rtl.css">' : '', '
3688
	<script src="https://ajax.googleapis.com/ajax/libs/jquery/2.1.4/jquery.min.js"></script>
3689
	<script src="', $settings['default_theme_url'], '/scripts/script.js"></script>
3690
	<script>
3691
		var smf_scripturl = \'', $upgradeurl, '\';
3692
		var smf_charset = \'', (empty($modSettings['global_character_set']) ? (empty($txt['lang_character_set']) ? 'UTF-8' : $txt['lang_character_set']) : $modSettings['global_character_set']), '\';
3693
		var startPercent = ', $upcontext['overall_percent'], ';
3694
3695
		// This function dynamically updates the step progress bar - and overall one as required.
3696
		function updateStepProgress(current, max, overall_weight)
3697
		{
3698
			// What out the actual percent.
3699
			var width = parseInt((current / max) * 100);
3700
			if (document.getElementById(\'step_progress\'))
3701
			{
3702
				document.getElementById(\'step_progress\').style.width = width + "%";
3703
				setInnerHTML(document.getElementById(\'step_text\'), width + "%");
3704
			}
3705
			if (overall_weight && document.getElementById(\'overall_progress\'))
3706
			{
3707
				overall_width = parseInt(startPercent + width * (overall_weight / 100));
3708
				document.getElementById(\'overall_progress\').style.width = overall_width + "%";
3709
				setInnerHTML(document.getElementById(\'overall_text\'), overall_width + "%");
3710
			}
3711
		}
3712
	</script>
3713
</head>
3714
<body>
3715
	<div id="footerfix">
3716
	<div id="header">
3717
		<h1 class="forumtitle">', $txt['upgrade_upgrade_utility'], '</h1>
3718
		<img id="smflogo" src="', $settings['default_theme_url'], '/images/smflogo.svg" alt="Simple Machines Forum" title="Simple Machines Forum">
3719
	</div>
3720
	<div id="wrapper">
3721
		<div id="content_section">
3722
			<div id="main_content_section">
3723
				<div id="main_steps">
3724
					<h2>', $txt['upgrade_progress'], '</h2>
3725
					<ul class="steps_list">';
3726
3727
	foreach ($upcontext['steps'] as $num => $step)
3728
		echo '
3729
						<li', $num == $upcontext['current_step'] ? ' class="stepcurrent"' : '', '>
3730
							', $txt['upgrade_step'], ' ', $step[0], ': ', $txt[$step[1]], '
3731
						</li>';
3732
3733
	echo '
3734
					</ul>
3735
				</div><!-- #main_steps -->
3736
3737
				<div id="install_progress">
3738
					<div id="progress_bar" class="progress_bar progress_green">
3739
						<h3>', $txt['upgrade_overall_progress'], '</h3>
3740
						<div id="overall_progress" class="bar" style="width: ', $upcontext['overall_percent'], '%;"></div>
3741
						<span id="overall_text">', $upcontext['overall_percent'], '%</span>
3742
					</div>';
3743
3744
	if (isset($upcontext['step_progress']))
3745
		echo '
3746
					<div id="progress_bar_step" class="progress_bar progress_yellow">
3747
						<h3>', $txt['upgrade_step_progress'], '</h3>
3748
						<div id="step_progress" class="bar" style="width: ', $upcontext['step_progress'], '%;"></div>
3749
						<span id="step_text">', $upcontext['step_progress'], '%</span>
3750
					</div>';
3751
3752
	echo '
3753
					<div id="substep_bar_div" class="progress_bar ', isset($upcontext['substep_progress']) ? '' : 'hidden', '">
3754
						<h3 id="substep_name">', isset($upcontext['substep_progress_name']) ? trim(strtr($upcontext['substep_progress_name'], array('.' => ''))) : '', '</h3>
3755
						<div id="substep_progress" class="bar" style="width: ', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%;"></div>
3756
						<span id="substep_text">', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : 0, '%</span>
3757
					</div>';
3758
3759
	// How long have we been running this?
3760
	$elapsed = time() - $upcontext['started'];
3761
	$mins = (int) ($elapsed / 60);
3762
	$seconds = $elapsed - $mins * 60;
3763
	echo '
3764
					<div class="smalltext time_elapsed">
3765
						', $txt['upgrade_time_elapsed'], ':
3766
						<span id="mins_elapsed">', $mins, '</span> ', $txt['upgrade_time_mins'], ', <span id="secs_elapsed">', $seconds, '</span> ', $txt['upgrade_time_secs'], '.
3767
					</div>';
3768
	echo '
3769
				</div><!-- #install_progress -->
3770
				<div id="main_screen" class="clear">
3771
					<h2>', $upcontext['page_title'], '</h2>
3772
					<div class="panel">';
3773
}
3774
3775
function template_upgrade_below()
3776
{
3777
	global $upcontext, $txt;
3778
3779
	if (!empty($upcontext['pause']))
3780
		echo '
3781
							<em>', $txt['upgrade_incomplete'], '.</em><br>
3782
3783
							<h2 style="margin-top: 2ex;">', $txt['upgrade_not_quite_done'], '</h2>
3784
							<h3>
3785
								', $txt['upgrade_paused_overload'], '
3786
							</h3>';
3787
3788
	if (!empty($upcontext['custom_warning']))
3789
		echo '
3790
							<div class="errorbox">
3791
								<h3>', $txt['upgrade_note'], '</h3>
3792
								', $upcontext['custom_warning'], '
3793
							</div>';
3794
3795
	echo '
3796
							<div class="righttext buttons">';
3797
3798
	if (!empty($upcontext['continue']))
3799
		echo '
3800
								<input type="submit" id="contbutt" name="contbutt" value="', $txt['upgrade_continue'], '"', $upcontext['continue'] == 2 ? ' disabled' : '', ' class="button">';
3801
	if (!empty($upcontext['skip']))
3802
		echo '
3803
								<input type="submit" id="skip" name="skip" value="', $txt['upgrade_skip'], '" onclick="dontSubmit = true; document.getElementById(\'contbutt\').disabled = \'disabled\'; return true;" class="button">';
3804
3805
	echo '
3806
							</div>
3807
						</form>
3808
					</div><!-- .panel -->
3809
				</div><!-- #main_screen -->
3810
			</div><!-- #main_content_section -->
3811
		</div><!-- #content_section -->
3812
	</div><!-- #wrapper -->
3813
	</div><!-- #footerfix -->
3814
	<div id="footer">
3815
		<ul>
3816
			<li class="copyright"><a href="https://www.simplemachines.org/" title="Simple Machines Forum" target="_blank" rel="noopener">SMF &copy; ' . SMF_SOFTWARE_YEAR . ', Simple Machines</a></li>
3817
		</ul>
3818
	</div>';
3819
3820
	// Are we on a pause?
3821
	if (!empty($upcontext['pause']))
3822
	{
3823
		echo '
3824
	<script>
3825
		window.onload = doAutoSubmit;
3826
		var countdown = 3;
3827
		var dontSubmit = false;
3828
3829
		function doAutoSubmit()
3830
		{
3831
			if (countdown == 0 && !dontSubmit)
3832
				document.upform.submit();
3833
			else if (countdown == -1)
3834
				return;
3835
3836
			document.getElementById(\'contbutt\').value = "', $txt['upgrade_continue'], ' (" + countdown + ")";
3837
			countdown--;
3838
3839
			setTimeout("doAutoSubmit();", 1000);
3840
		}
3841
	</script>';
3842
	}
3843
3844
	echo '
3845
</body>
3846
</html>';
3847
}
3848
3849
function template_xml_above()
3850
{
3851
	global $upcontext;
3852
3853
	echo '<', '?xml version="1.0" encoding="UTF-8"?', '>
3854
	<smf>';
3855
3856
	if (!empty($upcontext['get_data']))
3857
		foreach ($upcontext['get_data'] as $k => $v)
3858
			echo '
3859
		<get key="', $k, '">', $v, '</get>';
3860
}
3861
3862
function template_xml_below()
3863
{
3864
	echo '
3865
	</smf>';
3866
}
3867
3868
function template_error_message()
3869
{
3870
	global $upcontext;
3871
3872
	echo '
3873
	<div class="error">
3874
		', $upcontext['error_msg'], '
3875
		<br>
3876
		<a href="', $_SERVER['PHP_SELF'], '">Click here to try again.</a>
3877
	</div>';
3878
}
3879
3880
function template_welcome_message()
3881
{
3882
	global $upcontext, $disable_security, $settings, $txt;
3883
3884
	echo '
3885
				<script src="https://www.simplemachines.org/smf/current-version.js?version=' . SMF_VERSION . '"></script>
3886
3887
				<h3>', sprintf($txt['upgrade_ready_proceed'], SMF_VERSION), '</h3>
3888
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">
3889
					<input type="hidden" name="', $upcontext['login_token_var'], '" value="', $upcontext['login_token'], '">
3890
3891
					<div id="version_warning" class="noticebox hidden">
3892
						<h3>', $txt['upgrade_warning'], '</h3>
3893
						', sprintf($txt['upgrade_warning_out_of_date'], SMF_VERSION, 'https://www.simplemachines.org'), '
3894
					</div>';
3895
3896
	$upcontext['chmod_in_form'] = true;
3897
	template_chmod();
3898
3899
	// For large, pre 1.1 RC2 forums give them a warning about the possible impact of this upgrade!
3900
	if ($upcontext['is_large_forum'])
3901
		echo '
3902
					<div class="errorbox">
3903
						<h3>', $txt['upgrade_warning'], '</h3>
3904
						', $txt['upgrade_warning_lots_data'], '
3905
					</div>';
3906
3907
	// A warning message?
3908
	if (!empty($upcontext['warning']))
3909
		echo '
3910
					<div class="errorbox">
3911
						<h3>', $txt['upgrade_warning'], '</h3>
3912
						', $upcontext['warning'], '
3913
					</div>';
3914
3915
	// Paths are incorrect?
3916
	echo '
3917
					<div class="errorbox', (file_exists($settings['default_theme_dir'] . '/scripts/script.js') ? ' hidden' : ''), '" id="js_script_missing_error">
3918
						<h3>', $txt['upgrade_critical_error'], '</h3>
3919
						', sprintf($txt['upgrade_error_script_js'], 'https://download.simplemachines.org/?tools'), '
3920
					</div>';
3921
3922
	// Is there someone already doing this?
3923
	if (!empty($upcontext['user']['id']) && (time() - $upcontext['started'] < 72600 || time() - $upcontext['updated'] < 3600))
3924
	{
3925
		$ago = time() - $upcontext['started'];
3926
		$ago_hours = floor($ago / 3600);
3927
		$ago_minutes = intval(($ago / 60) % 60);
3928
		$ago_seconds = intval($ago % 60);
3929
		$agoTxt = $ago < 60 ? 'upgrade_time_s' : ($ago < 3600 ? 'upgrade_time_ms' : 'upgrade_time_hms');
3930
3931
		$updated = time() - $upcontext['updated'];
3932
		$updated_hours = floor($updated / 3600);
3933
		$updated_minutes = intval(($updated / 60) % 60);
3934
		$updated_seconds = intval($updated % 60);
3935
		$updatedTxt = $updated < 60 ? 'upgrade_time_updated_s' : ($updated < 3600 ? 'upgrade_time_updated_hm' : 'upgrade_time_updated_hms');
3936
3937
		echo '
3938
					<div class="errorbox">
3939
						<h3>', $txt['upgrade_warning'], '</h3>
3940
						<p>', sprintf($txt['upgrade_time_user'], $upcontext['user']['name']), '</p>
3941
						<p>', sprintf($txt[$agoTxt], $ago_seconds, $ago_minutes, $ago_hours), '</p>
3942
						<p>', sprintf($txt[$updatedTxt], $updated_seconds, $updated_minutes, $updated_hours), '</p>';
3943
3944
		if ($updated < 600)
3945
			echo '
3946
						<p>', $txt['upgrade_run_script'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_run_script2'], '</p>';
3947
3948
		if ($updated > $upcontext['inactive_timeout'])
3949
			echo '
3950
						<p>', $txt['upgrade_run'], '</p>';
3951
		else
3952
			echo '
3953
						<p>', $txt['upgrade_script_timeout'], ' ', $upcontext['user']['name'], ' ', $txt['upgrade_script_timeout2'], ' ', ($upcontext['inactive_timeout'] > 120 ? round($upcontext['inactive_timeout'] / 60, 1) . ' minutes!' : $upcontext['inactive_timeout'] . ' seconds!'), '</p>';
3954
3955
		echo '
3956
					</div>';
3957
	}
3958
3959
	echo '
3960
					<strong>', $txt['upgrade_admin_login'], ' ', $disable_security ? '(DISABLED)' : '', '</strong>
3961
					<h3>', $txt['upgrade_sec_login'], '</h3>
3962
					<dl class="settings adminlogin">
3963
						<dt>
3964
							<label for="user"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_username'], '</label>
3965
						</dt>
3966
						<dd>
3967
							<input type="text" name="user" value="', !empty($upcontext['username']) ? $upcontext['username'] : '', '"', $disable_security ? ' disabled' : '', '>';
3968
3969
	if (!empty($upcontext['username_incorrect']))
3970
		echo '
3971
							<div class="smalltext red">', $txt['upgrade_wrong_username'], '</div>';
3972
3973
	echo '
3974
						</dd>
3975
						<dt>
3976
							<label for="passwrd"', $disable_security ? ' disabled' : '', '>', $txt['upgrade_password'], '</label>
3977
						</dt>
3978
						<dd>
3979
							<input type="password" name="passwrd" value=""', $disable_security ? ' disabled' : '', '>
3980
							<input type="hidden" name="hash_passwrd" value="">';
3981
3982
	if (!empty($upcontext['password_failed']))
3983
		echo '
3984
							<div class="smalltext red">', $txt['upgrade_wrong_password'], '</div>';
3985
3986
	echo '
3987
						</dd>';
3988
3989
	// Can they continue?
3990
	if (!empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] >= $upcontext['inactive_timeout'] && $upcontext['user']['step'] > 1)
3991
	{
3992
		echo '
3993
						<dd>
3994
							<label for="cont"><input type="checkbox" id="cont" name="cont" checked>', $txt['upgrade_continue_step'], '</label>
3995
						</dd>';
3996
	}
3997
3998
	echo '
3999
					</dl>
4000
					<span class="smalltext">
4001
						', $txt['upgrade_bypass'], '
4002
					</span>
4003
					<input type="hidden" name="login_attempt" id="login_attempt" value="1">
4004
					<input type="hidden" name="js_works" id="js_works" value="0">';
4005
4006
	// Say we want the continue button!
4007
	$upcontext['continue'] = !empty($upcontext['user']['id']) && time() - $upcontext['user']['updated'] < $upcontext['inactive_timeout'] ? 2 : 1;
4008
4009
	// This defines whether javascript is going to work elsewhere :D
4010
	echo '
4011
					<script>
4012
						if (\'XMLHttpRequest\' in window && document.getElementById(\'js_works\'))
4013
							document.getElementById(\'js_works\').value = 1;
4014
4015
						// Latest version?
4016
						function smfCurrentVersion()
4017
						{
4018
							var smfVer, yourVer;
4019
4020
							if (!(\'smfVersion\' in window))
4021
								return;
4022
4023
							window.smfVersion = window.smfVersion.replace(/SMF\s?/g, \'\');
4024
4025
							smfVer = document.getElementById(\'smfVersion\');
4026
							yourVer = document.getElementById(\'yourVersion\');
4027
4028
							setInnerHTML(smfVer, window.smfVersion);
4029
4030
							var currentVersion = getInnerHTML(yourVer);
4031
							if (currentVersion < window.smfVersion)
4032
								document.getElementById(\'version_warning\').classList.remove(\'hidden\');
4033
						}
4034
						addLoadEvent(smfCurrentVersion);
4035
4036
						// This checks that the script file even exists!
4037
						if (typeof(smfSelectText) == \'undefined\')
4038
							document.getElementById(\'js_script_missing_error\').classList.remove(\'hidden\');
4039
4040
					</script>';
4041
}
4042
4043
function template_upgrade_options()
4044
{
4045
	global $upcontext, $modSettings, $db_prefix, $mmessage, $mtitle, $txt;
4046
4047
	echo '
4048
				<h3>', $txt['upgrade_areyouready'], '</h3>
4049
				<form action="', $upcontext['form_url'], '" method="post" name="upform" id="upform">';
4050
4051
	// Warning message?
4052
	if (!empty($upcontext['upgrade_options_warning']))
4053
		echo '
4054
				<div class="errorbox">
4055
					<h3>', $txt['upgrade_warning'], '</h3>
4056
					', $upcontext['upgrade_options_warning'], '
4057
				</div>';
4058
4059
	echo '
4060
				<ul class="upgrade_settings">
4061
					<li>
4062
						<input type="checkbox" name="backup" id="backup" value="1">
4063
						<label for="backup">', $txt['upgrade_backup_table'], ' &quot;backup_' . $db_prefix . '&quot;.</label>
4064
						(', $txt['upgrade_recommended'], ')
4065
					</li>
4066
					<li>
4067
						<input type="checkbox" name="maint" id="maint" value="1" checked>
4068
						<label for="maint">', $txt['upgrade_maintenance'], '</label>
4069
						<span class="smalltext">(<a href="javascript:void(0)" onclick="document.getElementById(\'mainmess\').classList.toggle(\'hidden\')">', $txt['upgrade_customize'], '</a>)</span>
4070
						<div id="mainmess" class="hidden">
4071
							<strong class="smalltext">', $txt['upgrade_maintenance_title'], ' </strong><br>
4072
							<input type="text" name="maintitle" size="30" value="', htmlspecialchars($mtitle), '"><br>
4073
							<strong class="smalltext">', $txt['upgrade_maintenance_message'], ' </strong><br>
4074
							<textarea name="mainmessage" rows="3" cols="50">', htmlspecialchars($mmessage), '</textarea>
4075
						</div>
4076
					</li>
4077
					<li>
4078
						<input type="checkbox" name="debug" id="debug" value="1">
4079
						<label for="debug">'.$txt['upgrade_debug_info'], '</label>
4080
					</li>
4081
					<li>
4082
						<input type="checkbox" name="empty_error" id="empty_error" value="1">
4083
						<label for="empty_error">', $txt['upgrade_empty_errorlog'], '</label>
4084
					</li>';
4085
4086
	if (!empty($upcontext['karma_installed']['good']) || !empty($upcontext['karma_installed']['bad']))
4087
		echo '
4088
					<li>
4089
						<input type="checkbox" name="delete_karma" id="delete_karma" value="1">
4090
						<label for="delete_karma">', $txt['upgrade_delete_karma'], '</label>
4091
					</li>';
4092
4093
	echo '
4094
					<li>
4095
						<input type="checkbox" name="stats" id="stats" value="1"', empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']) ? '' : ' checked="checked"', '>
4096
						<label for="stat">
4097
							', $txt['upgrade_stats_collection'], '<br>
4098
							<span class="smalltext">', sprintf($txt['upgrade_stats_info'], 'https://www.simplemachines.org/about/stats.php'), '</a></span>
4099
						</label>
4100
					</li>
4101
					<li>
4102
						<input type="checkbox" name="migrateSettings" id="migrateSettings" value="1"', empty($upcontext['migrateSettingsNeeded']) ? '' : ' checked="checked"', '>
4103
						<label for="migrateSettings">
4104
							', $txt['upgrade_migrate_settings_file'], '
4105
						</label>
4106
					</li>
4107
				</ul>
4108
				<input type="hidden" name="upcont" value="1">';
4109
4110
	// We need a normal continue button here!
4111
	$upcontext['continue'] = 1;
4112
}
4113
4114
// Template for the database backup tool/
4115
function template_backup_database()
4116
{
4117
	global $upcontext, $support_js, $is_debug, $txt;
4118
4119
	echo '
4120
				<h3>', $txt['upgrade_wait'], '</h3>';
4121
4122
	echo '
4123
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4124
					<input type="hidden" name="backup_done" id="backup_done" value="0">
4125
					<strong>', sprintf($txt['upgrade_completedtables_outof'], $upcontext['cur_table_num'], $upcontext['table_count']), '</strong>
4126
					<div id="debug_section">
4127
						<span id="debuginfo"></span>
4128
					</div>';
4129
4130
	// Dont any tables so far?
4131
	if (!empty($upcontext['previous_tables']))
4132
		foreach ($upcontext['previous_tables'] as $table)
4133
			echo '
4134
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4135
4136
	echo '
4137
					<h3 id="current_tab">
4138
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4139
					</h3>
4140
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">Backup Complete! Click Continue to Proceed.</p>';
4141
4142
	// Continue please!
4143
	$upcontext['continue'] = $support_js ? 2 : 1;
4144
4145
	// If javascript allows we want to do this using XML.
4146
	if ($support_js)
4147
	{
4148
		echo '
4149
					<script>
4150
						var lastTable = ', $upcontext['cur_table_num'], ';
4151
						function getNextTables()
4152
						{
4153
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4154
						}
4155
4156
						// Got an update!
4157
						function onBackupUpdate(oXMLDoc)
4158
						{
4159
							var sCurrentTableName = "";
4160
							var iTableNum = 0;
4161
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4162
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4163
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4164
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4165
4166
							// Update the page.
4167
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4168
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4169
							lastTable = iTableNum;
4170
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4171
4172
		// If debug flood the screen.
4173
		if ($is_debug)
4174
			echo '
4175
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4176
4177
							if (document.getElementById(\'debug_section\').scrollHeight)
4178
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4179
4180
		echo '
4181
							// Get the next update...
4182
							if (iTableNum == ', $upcontext['table_count'], ')
4183
							{
4184
								document.getElementById(\'commess\').classList.remove("hidden");
4185
								document.getElementById(\'current_tab\').classList.add("hidden");
4186
								document.getElementById(\'contbutt\').disabled = 0;
4187
								document.getElementById(\'backup_done\').value = 1;
4188
							}
4189
							else
4190
								getNextTables();
4191
						}
4192
						getNextTables();
4193
					//# sourceURL=dynamicScript-bkup.js
4194
					</script>';
4195
	}
4196
}
4197
4198
function template_backup_xml()
4199
{
4200
	global $upcontext;
4201
4202
	echo '
4203
		<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4204
}
4205
4206
// Here is the actual "make the changes" template!
4207
function template_database_changes()
4208
{
4209
	global $upcontext, $support_js, $is_debug, $timeLimitThreshold, $txt;
4210
4211
	if (empty($is_debug) && !empty($upcontext['upgrade_status']['debug']))
4212
		$is_debug = true;
4213
4214
	echo '
4215
				<h3>', $txt['upgrade_db_changes'], '</h3>
4216
				<h4><em>', $txt['upgrade_db_patient'], '</em></h4>';
4217
4218
	echo '
4219
				<form action="', $upcontext['form_url'], '&amp;filecount=', $upcontext['file_count'], '" name="upform" id="upform" method="post">
4220
					<input type="hidden" name="database_done" id="database_done" value="0">';
4221
4222
	// No javascript looks rubbish!
4223
	if (!$support_js)
4224
	{
4225
		foreach ($upcontext['actioned_items'] as $num => $item)
4226
		{
4227
			if ($num != 0)
4228
				echo ' Successful!';
4229
			echo '<br>' . $item;
4230
		}
4231
4232
		// Only tell deubbers how much time they wasted waiting for the upgrade because they don't have javascript.
4233
		if (!empty($upcontext['changes_complete']))
4234
		{
4235
			if ($is_debug)
4236
			{
4237
				$active = time() - $upcontext['started'];
4238
				$hours = floor($active / 3600);
4239
				$minutes = intval(($active / 60) % 60);
4240
				$seconds = intval($active % 60);
4241
4242
				echo '', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '<br>';
4243
			}
4244
			else
4245
				echo '', $txt['upgrade_success'], '<br>';
4246
4247
			echo '
4248
					<p id="commess">', $txt['upgrade_db_complete'], '</p>';
4249
		}
4250
	}
4251
	else
4252
	{
4253
		// Tell them how many files we have in total.
4254
		if ($upcontext['file_count'] > 1)
4255
			echo '
4256
					<strong id="info1">', $txt['upgrade_script'], ' <span id="file_done">', $upcontext['cur_file_num'], '</span> of ', $upcontext['file_count'], '.</strong>';
4257
4258
		echo '
4259
					<h3 id="info2">
4260
						<strong>', $txt['upgrade_executing'], '</strong> &quot;<span id="cur_item_name">', $upcontext['current_item_name'], '</span>&quot; (<span id="item_num">', $upcontext['current_item_num'], '</span> ', $txt['upgrade_of'], ' <span id="total_items"><span id="item_count">', $upcontext['total_items'], '</span>', $upcontext['file_count'] > 1 ? ' - of this script' : '', ')</span>
4261
					</h3>
4262
					<p id="commess" class="', !empty($upcontext['changes_complete']) || $upcontext['current_debug_item_num'] == $upcontext['debug_items'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_db_complete2'], '</p>';
4263
4264
		if ($is_debug)
4265
		{
4266
			// Let our debuggers know how much time was spent, but not wasted since JS handled refreshing the page!
4267
			if ($upcontext['current_debug_item_num'] == $upcontext['debug_items'])
4268
			{
4269
				$active = time() - $upcontext['started'];
4270
				$hours = floor($active / 3600);
4271
				$minutes = intval(($active / 60) % 60);
4272
				$seconds = intval($active % 60);
4273
4274
				echo '
4275
					<p id="upgradeCompleted">', sprintf($txt['upgrade_success_time_db'], $seconds, $minutes, $hours), '</p>';
4276
			}
4277
			else
4278
				echo '
4279
					<p id="upgradeCompleted"></p>';
4280
4281
			echo '
4282
					<div id="debug_section">
4283
						<span id="debuginfo"></span>
4284
					</div>';
4285
		}
4286
	}
4287
4288
	// Place for the XML error message.
4289
	echo '
4290
					<div id="error_block" class="errorbox', empty($upcontext['error_message']) ? ' hidden' : '', '">
4291
						<h3>', $txt['upgrade_error'], '</h3>
4292
						<div id="error_message">', isset($upcontext['error_message']) ? $upcontext['error_message'] : $txt['upgrade_unknown_error'], '</div>
4293
					</div>';
4294
4295
	// We want to continue at some point!
4296
	$upcontext['continue'] = $support_js ? 2 : 1;
4297
4298
	// If javascript allows we want to do this using XML.
4299
	if ($support_js)
4300
	{
4301
		echo '
4302
					<script>
4303
						var lastItem = ', $upcontext['current_debug_item_num'], ';
4304
						var sLastString = "', strtr($upcontext['current_debug_item_name'], array('"' => '&quot;')), '";
4305
						var iLastSubStepProgress = -1;
4306
						var curFile = ', $upcontext['cur_file_num'], ';
4307
						var totalItems = 0;
4308
						var prevFile = 0;
4309
						var retryCount = 0;
4310
						var testvar = 0;
4311
						var timeOutID = 0;
4312
						var getData = "";
4313
						var debugItems = ', $upcontext['debug_items'], ';';
4314
4315
		if ($is_debug)
4316
			echo '
4317
						var upgradeStartTime = ' . $upcontext['started'] . ';';
4318
4319
		echo '
4320
						function getNextItem()
4321
						{
4322
							// We want to track this...
4323
							if (timeOutID)
4324
								clearTimeout(timeOutID);
4325
							timeOutID = window.setTimeout("retTimeout()", ', (10 * $timeLimitThreshold), '000);
4326
4327
							getXMLDocument(\'', $upcontext['form_url'], '&xml&filecount=', $upcontext['file_count'], '&substep=\' + lastItem + getData, onItemUpdate);
4328
						}
4329
4330
						// Got an update!
4331
						function onItemUpdate(oXMLDoc)
4332
						{
4333
							var sItemName = "";
4334
							var sDebugName = "";
4335
							var iItemNum = 0;
4336
							var iSubStepProgress = -1;
4337
							var iDebugNum = 0;
4338
							var bIsComplete = 0;
4339
							var bSkipped = 0;
4340
							getData = "";
4341
4342
							// We\'ve got something - so reset the timeout!
4343
							if (timeOutID)
4344
								clearTimeout(timeOutID);
4345
4346
							// Assume no error at this time...
4347
							document.getElementById("error_block").classList.add("hidden");
4348
4349
							// Are we getting some duff info?
4350
							if (!oXMLDoc.getElementsByTagName("item")[0])
4351
							{
4352
								// Too many errors?
4353
								if (retryCount > 15)
4354
								{
4355
									document.getElementById("error_block").classList.remove("hidden");
4356
									setInnerHTML(document.getElementById("error_message"), "Error retrieving information on step: " + (sDebugName == "" ? sLastString : sDebugName));';
4357
4358
		if ($is_debug)
4359
			echo '
4360
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4361
4362
		echo '
4363
								}
4364
								else
4365
								{
4366
									retryCount++;
4367
									getNextItem();
4368
								}
4369
								return false;
4370
							}
4371
4372
							// Never allow loops.
4373
							if (curFile == prevFile)
4374
							{
4375
								retryCount++;
4376
								if (retryCount > 10)
4377
								{
4378
									document.getElementById("error_block").classList.remove("hidden");
4379
									setInnerHTML(document.getElementById("error_message"), "', $txt['upgrade_loop'], '" + sDebugName);';
4380
4381
		if ($is_debug)
4382
			echo '
4383
									setOuterHTML(document.getElementById(\'debuginfo\'), \'<span class="red">failed<\' + \'/span><span id="debuginfo"><\' + \'/span>\');';
4384
4385
		echo '
4386
								}
4387
							}
4388
							retryCount = 0;
4389
4390
							for (var i = 0; i < oXMLDoc.getElementsByTagName("item")[0].childNodes.length; i++)
4391
								sItemName += oXMLDoc.getElementsByTagName("item")[0].childNodes[i].nodeValue;
4392
							for (var i = 0; i < oXMLDoc.getElementsByTagName("debug")[0].childNodes.length; i++)
4393
								sDebugName += oXMLDoc.getElementsByTagName("debug")[0].childNodes[i].nodeValue;
4394
							for (var i = 0; i < oXMLDoc.getElementsByTagName("get").length; i++)
4395
							{
4396
								getData += "&" + oXMLDoc.getElementsByTagName("get")[i].getAttribute("key") + "=";
4397
								for (var j = 0; j < oXMLDoc.getElementsByTagName("get")[i].childNodes.length; j++)
4398
								{
4399
									getData += oXMLDoc.getElementsByTagName("get")[i].childNodes[j].nodeValue;
4400
								}
4401
							}
4402
4403
							iItemNum = oXMLDoc.getElementsByTagName("item")[0].getAttribute("num");
4404
							iDebugNum = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("num"));
4405
							bIsComplete = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("complete"));
4406
							bSkipped = parseInt(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("skipped"));
4407
							iSubStepProgress = parseFloat(oXMLDoc.getElementsByTagName("debug")[0].getAttribute("percent"));
4408
							sLastString = sDebugName + " (Item: " + iDebugNum + ")";
4409
4410
							curFile = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("num"));
4411
							debugItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("debug_items"));
4412
							totalItems = parseInt(oXMLDoc.getElementsByTagName("file")[0].getAttribute("items"));
4413
4414
							// If we have an error we haven\'t completed!
4415
							if (oXMLDoc.getElementsByTagName("error")[0] && bIsComplete)
4416
								iDebugNum = lastItem;
4417
4418
							// Do we have the additional progress bar?
4419
							if (iSubStepProgress != -1)
4420
							{
4421
								document.getElementById("substep_bar_div").classList.remove("hidden");
4422
								document.getElementById("substep_progress").style.width = iSubStepProgress + "%";
4423
								setInnerHTML(document.getElementById("substep_text"), iSubStepProgress + "%");
4424
								setInnerHTML(document.getElementById("substep_name"), sDebugName.replace(/\./g, ""));
4425
							}
4426
							else
4427
							{
4428
								document.getElementById("substep_bar_div").classList.add("hidden");
4429
							}
4430
4431
							// Move onto the next item?
4432
							if (bIsComplete)
4433
								lastItem = iDebugNum;
4434
							else
4435
								lastItem = iDebugNum - 1;
4436
4437
							// Are we finished?
4438
							if (bIsComplete && iDebugNum == -1 && curFile >= ', $upcontext['file_count'], ')
4439
							{';
4440
4441
		// Database Changes, tell us how much time we spen to do this.  If this gets updated via JS.
4442
		if ($is_debug)
4443
			echo '
4444
								document.getElementById(\'debug_section\').classList.add("hidden");
4445
4446
								var upgradeFinishedTime = parseInt(oXMLDoc.getElementsByTagName("curtime")[0].childNodes[0].nodeValue);
4447
								var diffTime = upgradeFinishedTime - upgradeStartTime;
4448
								var diffHours = Math.floor(diffTime / 3600);
4449
								var diffMinutes = parseInt((diffTime / 60) % 60);
4450
								var diffSeconds = parseInt(diffTime % 60);
4451
4452
								var completedTxt = "', $txt['upgrade_success_time_db'], '";
4453
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4454
4455
								completedTxt = completedTxt.replace("%1$d", diffSeconds).replace("%2$d", diffMinutes).replace("%3$d", diffHours);
4456
console.log(completedTxt, upgradeFinishedTime, diffTime, diffHours, diffMinutes, diffSeconds);
4457
								setInnerHTML(document.getElementById("upgradeCompleted"), completedTxt);';
4458
4459
		echo '
4460
4461
								document.getElementById(\'commess\').classList.remove("hidden");
4462
								document.getElementById(\'contbutt\').disabled = 0;
4463
								document.getElementById(\'database_done\').value = 1;';
4464
4465
		if ($upcontext['file_count'] > 1)
4466
			echo '
4467
								document.getElementById(\'info1\').classList.add(\'hidden\');';
4468
4469
		echo '
4470
								document.getElementById(\'info2\').classList.add(\'hidden\');
4471
								updateStepProgress(100, 100, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4472
								return true;
4473
							}
4474
							// Was it the last step in the file?
4475
							else if (bIsComplete && iDebugNum == -1)
4476
							{
4477
								lastItem = 0;
4478
								prevFile = curFile;';
4479
4480
		if ($is_debug)
4481
			echo '
4482
								setOuterHTML(document.getElementById(\'debuginfo\'), \'Moving to next script file...done<br><span id="debuginfo"><\' + \'/span>\');';
4483
4484
		echo '
4485
								getNextItem();
4486
								return true;
4487
							}';
4488
4489
		// If debug scroll the screen.
4490
		if ($is_debug)
4491
			echo '
4492
							if (iLastSubStepProgress == -1)
4493
							{
4494
								// Give it consistent dots.
4495
								dots = sDebugName.match(/\./g);
4496
								numDots = dots ? dots.length : 0;
4497
								for (var i = numDots; i < 3; i++)
4498
									sDebugName += ".";
4499
								setOuterHTML(document.getElementById(\'debuginfo\'), sDebugName + \'<span id="debuginfo"><\' + \'/span>\');
4500
							}
4501
							iLastSubStepProgress = iSubStepProgress;
4502
4503
							if (bIsComplete && bSkipped)
4504
								setOuterHTML(document.getElementById(\'debuginfo\'), \'skipped<br><span id="debuginfo"><\' + \'/span>\');
4505
							else if (bIsComplete)
4506
								setOuterHTML(document.getElementById(\'debuginfo\'), \'done<br><span id="debuginfo"><\' + \'/span>\');
4507
							else
4508
								setOuterHTML(document.getElementById(\'debuginfo\'), \'...<span id="debuginfo"><\' + \'/span>\');
4509
4510
							if (document.getElementById(\'debug_section\').scrollHeight)
4511
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4512
4513
		echo '
4514
							// Update the page.
4515
							setInnerHTML(document.getElementById(\'item_num\'), iItemNum);
4516
							setInnerHTML(document.getElementById(\'cur_item_name\'), sItemName);';
4517
4518
		if ($upcontext['file_count'] > 1)
4519
		{
4520
			echo '
4521
							setInnerHTML(document.getElementById(\'file_done\'), curFile);
4522
							setInnerHTML(document.getElementById(\'item_count\'), totalItems);';
4523
		}
4524
4525
		echo '
4526
							// Is there an error?
4527
							if (oXMLDoc.getElementsByTagName("error")[0])
4528
							{
4529
								var sErrorMsg = "";
4530
								for (var i = 0; i < oXMLDoc.getElementsByTagName("error")[0].childNodes.length; i++)
4531
									sErrorMsg += oXMLDoc.getElementsByTagName("error")[0].childNodes[i].nodeValue;
4532
								document.getElementById("error_block").classList.remove("hidden");
4533
								setInnerHTML(document.getElementById("error_message"), sErrorMsg);
4534
								return false;
4535
							}
4536
4537
							// Get the progress bar right.
4538
							barTotal = debugItems * ', $upcontext['file_count'], ';
4539
							barDone = (debugItems * (curFile - 1)) + lastItem;
4540
4541
							updateStepProgress(barDone, barTotal, ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');
4542
4543
							// Finally - update the time here as it shows the server is responding!
4544
							curTime = new Date();
4545
							iElapsed = (curTime.getTime() / 1000 - ', $upcontext['started'], ');
4546
							mins = parseInt(iElapsed / 60);
4547
							secs = parseInt(iElapsed - mins * 60);
4548
							setInnerHTML(document.getElementById("mins_elapsed"), mins);
4549
							setInnerHTML(document.getElementById("secs_elapsed"), secs);
4550
4551
							getNextItem();
4552
							return true;
4553
						}
4554
4555
						// What if we timeout?!
4556
						function retTimeout(attemptAgain)
4557
						{
4558
							// Oh noes...
4559
							if (!attemptAgain)
4560
							{
4561
								document.getElementById("error_block").classList.remove("hidden");
4562
								setInnerHTML(document.getElementById("error_message"), "', sprintf($txt['upgrade_respondtime'], ($timeLimitThreshold * 10)), '" + "<a href=\"#\" onclick=\"retTimeout(true); return false;\">', $txt['upgrade_respondtime_clickhere'], '</a>");
4563
							}
4564
							else
4565
							{
4566
								document.getElementById("error_block").classList.add("hidden");
4567
								getNextItem();
4568
							}
4569
						}';
4570
4571
		// Start things off assuming we've not errored.
4572
		if (empty($upcontext['error_message']))
4573
			echo '
4574
						getNextItem();';
4575
4576
		echo '
4577
					//# sourceURL=dynamicScript-dbch.js
4578
					</script>';
4579
	}
4580
	return;
4581
}
4582
4583
function template_database_xml()
4584
{
4585
	global $is_debug, $upcontext;
4586
4587
	echo '
4588
	<file num="', $upcontext['cur_file_num'], '" items="', $upcontext['total_items'], '" debug_items="', $upcontext['debug_items'], '">', $upcontext['cur_file_name'], '</file>
4589
	<item num="', $upcontext['current_item_num'], '">', $upcontext['current_item_name'], '</item>
4590
	<debug num="', $upcontext['current_debug_item_num'], '" percent="', isset($upcontext['substep_progress']) ? $upcontext['substep_progress'] : '-1', '" complete="', empty($upcontext['completed_step']) ? 0 : 1, '" skipped="', empty($upcontext['skip_db_substeps']) ? 0 : 1, '">', $upcontext['current_debug_item_name'], '</debug>';
4591
4592
	if (!empty($upcontext['error_message']))
4593
		echo '
4594
	<error>', $upcontext['error_message'], '</error>';
4595
4596
	if (!empty($upcontext['error_string']))
4597
		echo '
4598
	<sql>', $upcontext['error_string'], '</sql>';
4599
4600
	if ($is_debug)
4601
		echo '
4602
	<curtime>', time(), '</curtime>';
4603
}
4604
4605
// Template for the UTF-8 conversion step. Basically a copy of the backup stuff with slight modifications....
4606
function template_convert_utf8()
4607
{
4608
	global $upcontext, $support_js, $is_debug, $txt;
4609
4610
	echo '
4611
				<h3>', $txt['upgrade_wait2'], '</h3>
4612
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4613
					<input type="hidden" name="utf8_done" id="utf8_done" value="0">
4614
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4615
					<div id="debug_section">
4616
						<span id="debuginfo"></span>
4617
					</div>';
4618
4619
	// Done any tables so far?
4620
	if (!empty($upcontext['previous_tables']))
4621
		foreach ($upcontext['previous_tables'] as $table)
4622
			echo '
4623
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4624
4625
	echo '
4626
					<h3 id="current_tab">
4627
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4628
					</h3>';
4629
4630
	// If we dropped their index, let's let them know
4631
	if ($upcontext['dropping_index'])
4632
		echo '
4633
					<p id="indexmsg" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '>', $txt['upgrade_fulltext'], '</p>';
4634
4635
	// Completion notification
4636
	echo '
4637
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_conversion_proceed'], '</p>';
4638
4639
	// Continue please!
4640
	$upcontext['continue'] = $support_js ? 2 : 1;
4641
4642
	// If javascript allows we want to do this using XML.
4643
	if ($support_js)
4644
	{
4645
		echo '
4646
					<script>
4647
						var lastTable = ', $upcontext['cur_table_num'], ';
4648
						function getNextTables()
4649
						{
4650
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onConversionUpdate);
4651
						}
4652
4653
						// Got an update!
4654
						function onConversionUpdate(oXMLDoc)
4655
						{
4656
							var sCurrentTableName = "";
4657
							var iTableNum = 0;
4658
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4659
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4660
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4661
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4662
4663
							// Update the page.
4664
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4665
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4666
							lastTable = iTableNum;
4667
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4668
4669
		// If debug flood the screen.
4670
		if ($is_debug)
4671
			echo '
4672
						setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>Completed Table: &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4673
4674
						if (document.getElementById(\'debug_section\').scrollHeight)
4675
							document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4676
4677
		echo '
4678
						// Get the next update...
4679
						if (iTableNum == ', $upcontext['table_count'], ')
4680
						{
4681
							document.getElementById(\'commess\').classList.remove(\'hidden\');
4682
							if (document.getElementById(\'indexmsg\') != null) {
4683
								document.getElementById(\'indexmsg\').classList.remove(\'hidden\');
4684
							}
4685
							document.getElementById(\'current_tab\').classList.add(\'hidden\');
4686
							document.getElementById(\'contbutt\').disabled = 0;
4687
							document.getElementById(\'utf8_done\').value = 1;
4688
						}
4689
						else
4690
							getNextTables();
4691
					}
4692
					getNextTables();
4693
				//# sourceURL=dynamicScript-conv.js
4694
				</script>';
4695
	}
4696
}
4697
4698
function template_convert_xml()
4699
{
4700
	global $upcontext;
4701
4702
	echo '
4703
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4704
}
4705
4706
// Template for the database backup tool/
4707
function template_serialize_json()
4708
{
4709
	global $upcontext, $support_js, $is_debug, $txt;
4710
4711
	echo '
4712
				<h3>', $txt['upgrade_convert_datajson'], '</h3>
4713
				<form action="', $upcontext['form_url'], '" name="upform" id="upform" method="post">
4714
					<input type="hidden" name="json_done" id="json_done" value="0">
4715
					<strong>', $txt['upgrade_completed'], ' <span id="tab_done">', $upcontext['cur_table_num'], '</span> ', $txt['upgrade_outof'], ' ', $upcontext['table_count'], ' ', $txt['upgrade_tables'], '</strong>
4716
					<div id="debug_section">
4717
						<span id="debuginfo"></span>
4718
					</div>';
4719
4720
	// Dont any tables so far?
4721
	if (!empty($upcontext['previous_tables']))
4722
		foreach ($upcontext['previous_tables'] as $table)
4723
			echo '
4724
					<br>', $txt['upgrade_completed_table'], ' &quot;', $table, '&quot;.';
4725
4726
	echo '
4727
					<h3 id="current_tab">
4728
						', $txt['upgrade_current_table'], ' &quot;<span id="current_table">', $upcontext['cur_table_name'], '</span>&quot;
4729
					</h3>
4730
					<p id="commess" class="', $upcontext['cur_table_num'] == $upcontext['table_count'] ? 'inline_block' : 'hidden', '">', $txt['upgrade_json_completed'], '</p>';
4731
4732
	// Try to make sure substep was reset.
4733
	if ($upcontext['cur_table_num'] == $upcontext['table_count'])
4734
		echo '
4735
					<input type="hidden" name="substep" id="substep" value="0">';
4736
4737
	// Continue please!
4738
	$upcontext['continue'] = $support_js ? 2 : 1;
4739
4740
	// If javascript allows we want to do this using XML.
4741
	if ($support_js)
4742
	{
4743
		echo '
4744
					<script>
4745
						var lastTable = ', $upcontext['cur_table_num'], ';
4746
						function getNextTables()
4747
						{
4748
							getXMLDocument(\'', $upcontext['form_url'], '&xml&substep=\' + lastTable, onBackupUpdate);
4749
						}
4750
4751
						// Got an update!
4752
						function onBackupUpdate(oXMLDoc)
4753
						{
4754
							var sCurrentTableName = "";
4755
							var iTableNum = 0;
4756
							var sCompletedTableName = getInnerHTML(document.getElementById(\'current_table\'));
4757
							for (var i = 0; i < oXMLDoc.getElementsByTagName("table")[0].childNodes.length; i++)
4758
								sCurrentTableName += oXMLDoc.getElementsByTagName("table")[0].childNodes[i].nodeValue;
4759
							iTableNum = oXMLDoc.getElementsByTagName("table")[0].getAttribute("num");
4760
4761
							// Update the page.
4762
							setInnerHTML(document.getElementById(\'tab_done\'), iTableNum);
4763
							setInnerHTML(document.getElementById(\'current_table\'), sCurrentTableName);
4764
							lastTable = iTableNum;
4765
							updateStepProgress(iTableNum, ', $upcontext['table_count'], ', ', $upcontext['step_weight'] * ((100 - $upcontext['step_progress']) / 100), ');';
4766
4767
		// If debug flood the screen.
4768
		if ($is_debug)
4769
			echo '
4770
							setOuterHTML(document.getElementById(\'debuginfo\'), \'<br>', $txt['upgrade_completed_table'], ' &quot;\' + sCompletedTableName + \'&quot;.<span id="debuginfo"><\' + \'/span>\');
4771
4772
							if (document.getElementById(\'debug_section\').scrollHeight)
4773
								document.getElementById(\'debug_section\').scrollTop = document.getElementById(\'debug_section\').scrollHeight';
4774
4775
		echo '
4776
							// Get the next update...
4777
							if (iTableNum == ', $upcontext['table_count'], ')
4778
							{
4779
								document.getElementById(\'commess\').classList.remove("hidden");
4780
								document.getElementById(\'current_tab\').classList.add("hidden");
4781
								document.getElementById(\'contbutt\').disabled = 0;
4782
								document.getElementById(\'json_done\').value = 1;
4783
							}
4784
							else
4785
								getNextTables();
4786
						}
4787
						getNextTables();
4788
					//# sourceURL=dynamicScript-json.js
4789
					</script>';
4790
	}
4791
}
4792
4793
function template_serialize_json_xml()
4794
{
4795
	global $upcontext;
4796
4797
	echo '
4798
	<table num="', $upcontext['cur_table_num'], '">', $upcontext['cur_table_name'], '</table>';
4799
}
4800
4801
function template_upgrade_complete()
4802
{
4803
	global $upcontext, $upgradeurl, $settings, $boardurl, $is_debug, $txt;
4804
4805
	echo '
4806
				<h3>', $txt['upgrade_done'], ' <a href="', $boardurl, '/index.php">', $txt['upgrade_done2'], '</a>.  ', $txt['upgrade_done3'], '</h3>
4807
				<form action="', $boardurl, '/index.php">';
4808
4809
	if (!empty($upcontext['can_delete_script']))
4810
		echo '
4811
					<label>
4812
						<input type="checkbox" id="delete_self" onclick="doTheDelete(this);"> ', $txt['upgrade_delete_now'], '
4813
					</label>
4814
					<em>', $txt['upgrade_delete_server'], '</em>
4815
					<script>
4816
						function doTheDelete(theCheck)
4817
						{
4818
							var theImage = document.getElementById ? document.getElementById("delete_upgrader") : document.all.delete_upgrader;
4819
							theImage.src = "', $upgradeurl, '?delete=1&ts_" + (new Date().getTime());
4820
							theCheck.disabled = true;
4821
						}
4822
					</script>
4823
					<img src="', $settings['default_theme_url'], '/images/blank.png" alt="" id="delete_upgrader"><br>';
4824
4825
	// Show Upgrade time in debug mode when we completed the upgrade process totally
4826
	if ($is_debug)
4827
	{
4828
		$active = time() - $upcontext['started'];
4829
		$hours = floor($active / 3600);
4830
		$minutes = intval(($active / 60) % 60);
4831
		$seconds = intval($active % 60);
4832
4833
		if ($hours > 0)
4834
			echo '', sprintf($txt['upgrade_completed_time_hms'], $seconds, $minutes, $hours), '';
4835
		elseif ($minutes > 0)
4836
			echo '', sprintf($txt['upgrade_completed_time_ms'], $seconds, $minutes), '';
4837
		elseif ($seconds > 0)
4838
			echo '', sprintf($txt['upgrade_completed_time_s'], $seconds), '';
4839
	}
4840
4841
	echo '
4842
					<p>
4843
						', sprintf($txt['upgrade_problems'], 'http://simplemachines.org'), '
4844
						<br>
4845
						', $txt['upgrade_luck'], '<br>
4846
						Simple Machines
4847
					</p>';
4848
}
4849
4850
/**
4851
 * Convert MySQL (var)char ip col to binary
4852
 *
4853
 * @param string $targetTable The table to perform the operation on
4854
 * @param string $oldCol The old column to gather data from
4855
 * @param string $newCol The new column to put data in
4856
 * @param int $limit The amount of entries to handle at once.
4857
 * @param int $setSize The amount of entries after which to update the database.
4858
 *
4859
 * newCol needs to be a varbinary(16) null able field
4860
 * @return bool
4861
 */
4862
function MySQLConvertOldIp($targetTable, $oldCol, $newCol, $limit = 50000, $setSize = 100)
4863
{
4864
	global $smcFunc, $step_progress;
4865
4866
	$current_substep = $_GET['substep'];
4867
4868
	if (empty($_GET['a']))
4869
		$_GET['a'] = 0;
4870
	$step_progress['name'] = 'Converting ips';
4871
	$step_progress['current'] = $_GET['a'];
4872
4873
	// Skip this if we don't have the column
4874
	$request = $smcFunc['db_query']('', '
4875
		SHOW FIELDS
4876
		FROM {db_prefix}{raw:table}
4877
		WHERE Field = {string:name}',
4878
		array(
4879
			'table' => $targetTable,
4880
			'name' => $oldCol,
4881
		)
4882
	);
4883
	if ($smcFunc['db_num_rows']($request) !== 1)
4884
	{
4885
		$smcFunc['db_free_result']($request);
4886
		return;
4887
	}
4888
	$smcFunc['db_free_result']($request);
4889
4890
	$is_done = false;
4891
	while (!$is_done)
4892
	{
4893
		// Keep looping at the current step.
4894
		nextSubstep($current_substep);
4895
4896
		// mysql default max length is 1mb https://dev.mysql.com/doc/refman/5.1/en/packet-too-large.html
4897
		$arIp = array();
4898
4899
		$request = $smcFunc['db_query']('', '
4900
			SELECT DISTINCT {raw:old_col}
4901
			FROM {db_prefix}{raw:table_name}
4902
			WHERE {raw:new_col} IS NULL AND
4903
				{raw:old_col} != {string:unknown} AND
4904
				{raw:old_col} != {string:empty}
4905
			LIMIT {int:limit}',
4906
			array(
4907
				'old_col' => $oldCol,
4908
				'new_col' => $newCol,
4909
				'table_name' => $targetTable,
4910
				'empty' => '',
4911
				'limit' => $limit,
4912
				'unknown' => 'unknown',
4913
			)
4914
		);
4915
		while ($row = $smcFunc['db_fetch_assoc']($request))
4916
			$arIp[] = $row[$oldCol];
4917
4918
		$smcFunc['db_free_result']($request);
4919
4920
		// Special case, null ip could keep us in a loop.
4921
		if (is_null($arIp[0]))
4922
			unset($arIp[0]);
4923
4924
		if (empty($arIp))
4925
			$is_done = true;
4926
4927
		$updates = array();
4928
		$cases = array();
4929
		$count = count($arIp);
4930
		for ($i = 0; $i < $count; $i++)
4931
		{
4932
			$arIp[$i] = trim($arIp[$i]);
4933
4934
			if (empty($arIp[$i]))
4935
				continue;
4936
4937
			$updates['ip' . $i] = $arIp[$i];
4938
			$cases[$arIp[$i]] = 'WHEN ' . $oldCol . ' = {string:ip' . $i . '} THEN {inet:ip' . $i . '}';
4939
4940
			if ($setSize > 0 && $i % $setSize === 0)
4941
			{
4942
				if (count($updates) == 1)
4943
					continue;
4944
4945
				$updates['whereSet'] = array_values($updates);
4946
				$smcFunc['db_query']('', '
4947
					UPDATE {db_prefix}' . $targetTable . '
4948
					SET ' . $newCol . ' = CASE ' .
4949
					implode('
4950
						', $cases) . '
4951
						ELSE NULL
4952
					END
4953
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
4954
					$updates
4955
				);
4956
4957
				$updates = array();
4958
				$cases = array();
4959
			}
4960
		}
4961
4962
		// Incase some extras made it through.
4963
		if (!empty($updates))
4964
		{
4965
			if (count($updates) == 1)
4966
			{
4967
				foreach ($updates as $key => $ip)
4968
				{
4969
					$smcFunc['db_query']('', '
4970
						UPDATE {db_prefix}' . $targetTable . '
4971
						SET ' . $newCol . ' = {inet:ip}
4972
						WHERE ' . $oldCol . ' = {string:ip}',
4973
						array(
4974
							'ip' => $ip
4975
						)
4976
					);
4977
				}
4978
			}
4979
			else
4980
			{
4981
				$updates['whereSet'] = array_values($updates);
4982
				$smcFunc['db_query']('', '
4983
					UPDATE {db_prefix}' . $targetTable . '
4984
					SET ' . $newCol . ' = CASE ' .
4985
					implode('
4986
						', $cases) . '
4987
						ELSE NULL
4988
					END
4989
					WHERE ' . $oldCol . ' IN ({array_string:whereSet})',
4990
					$updates
4991
				);
4992
			}
4993
		}
4994
		else
4995
			$is_done = true;
4996
4997
		$_GET['a'] += $limit;
4998
		$step_progress['current'] = $_GET['a'];
4999
	}
5000
5001
	unset($_GET['a']);
5002
}
5003
5004
/**
5005
 * Get the column info. This is basically the same as smf_db_list_columns but we get 1 column, force detail and other checks.
5006
 *
5007
 * @param string $targetTable The table to perform the operation on
5008
 * @param string $column The column we are looking for.
5009
 *
5010
 * @return array Info on the table.
5011
 */
5012
function upgradeGetColumnInfo($targetTable, $column)
5013
{
5014
	global $smcFunc;
5015
5016
	// This should already be here, but be safe.
5017
	db_extend('packages');
5018
5019
	$columns = $smcFunc['db_list_columns']($targetTable, true);
5020
5021
	if (isset($columns[$column]))
5022
		return $columns[$column];
5023
	else
5024
		return null;
5025
}
5026
5027
/**
5028
 * Takes the changes to be made during the upgradeOptions step, grabs all known Settings data from Settings.php, then runs
5029
 * through a process to rebuild onto a brand new Settings template.  This should only be done if detection believes the
5030
 * settings file isn't using any advanced configuration setups in the Settings.php file.  A copy is made as Settings_org.php
5031
 * to preserve all changes prior to migration.
5032
 *
5033
 * @param array $config_vars An array of one or more variables to update
5034
 *
5035
 * @return void We either successfully update the Settings file, or throw a error here.
5036
 */
5037
function migrateSettingsFile($changes)
5038
{
5039
	global $boarddir, $cachedir, $txt;
5040
5041
	// Try to find all of these settings.
5042
	$settingsVars = array(
5043
		'maintenance' => 'int',
5044
		'mtitle' => 'string',
5045
		'mmessage' => 'string',
5046
		'mbname' => 'string',
5047
		'language' => 'string',
5048
		'boardurl' => 'string',
5049
		'webmaster_email' => 'string',
5050
		'cookiename' => 'string',
5051
		'db_type' => 'string',
5052
		'db_port' => 'int',
5053
		'db_server' => 'string_fatal',
5054
		'db_name' => 'string_fatal',
5055
		'db_user' => 'string_fatal',
5056
		'db_passwd' => 'string_fatal',
5057
		'ssi_db_user' => 'string',
5058
		'ssi_db_passwd' => 'string',
5059
		'db_prefix' => 'string_fatal',
5060
		'db_persist' => 'int',
5061
		'db_error_send' => 'int',
5062
		'db_mb4' => 'null',
5063
		'cache_accelerator' => 'string',
5064
		'cache_enable' => 'int',
5065
		'cache_memcached' => 'string',
5066
		'cachedir' => 'string',
5067
		'image_proxy_enabled' => 'bool',
5068
		'image_proxy_secret' => 'string',
5069
		'image_proxy_maxsize' => 'int',
5070
		'boarddir' => 'string',
5071
		'sourcedir' => 'string',
5072
		'packagesdir' => 'string',
5073
		'tasksdir' => 'string',
5074
		'db_character_set' => 'string',
5075
	);
5076
5077
	// The Settings file, in an array as if it was handled by updateSettingsFile
5078
	$settingsArray = array(
5079
		'<' . '?' . 'php',
5080
		'',
5081
		'/**',
5082
		' * The settings file contains all of the basic settings that need to be present when a database/cache is not available.',
5083
		' *',
5084
		' * Simple Machines Forum (SMF)',
5085
		' *',
5086
		' * @package SMF',
5087
		' * @author Simple Machines http://www.simplemachines.org',
5088
		' * @copyright ' . SMF_SOFTWARE_YEAR . ' Simple Machines and individual contributors',
5089
		' * @license http://www.simplemachines.org/about/smf/license.php BSD',
5090
		' *',
5091
		' * @version ' . SMF_VERSION,
5092
		' */',
5093
		'',
5094
		'########## Maintenance ##########',
5095
		'/**',
5096
		' * The maintenance "mode"',
5097
		' * Set to 1 to enable Maintenance Mode, 2 to make the forum untouchable. (you\'ll have to make it 0 again manually!)',
5098
		' * 0 is default and disables maintenance mode.',
5099
		' * @var int 0, 1, 2',
5100
		' * @global int $maintenance',
5101
		' */',
5102
		'$maintenance = 0;',
5103
		'/**',
5104
		' * Title for the Maintenance Mode message.',
5105
		' * @var string',
5106
		' * @global int $mtitle',
5107
		' */',
5108
		'$mtitle = \'Maintenance Mode\';',
5109
		'/**',
5110
		' * Description of why the forum is in maintenance mode.',
5111
		' * @var string',
5112
		' * @global string $mmessage',
5113
		' */',
5114
		'$mmessage = \'Okay faithful users...we\\\'re attempting to restore an older backup of the database...news will be posted once we\\\'re back!\';',
5115
		'',
5116
		'########## Forum Info ##########',
5117
		'/**',
5118
		' * The name of your forum.',
5119
		' * @var string',
5120
		' */',
5121
		'$mbname = \'My Community\';',
5122
		'/**',
5123
		' * The default language file set for the forum.',
5124
		' * @var string',
5125
		' */',
5126
		'$language = \'english\';',
5127
		'/**',
5128
		' * URL to your forum\'s folder. (without the trailing /!)',
5129
		' * @var string',
5130
		' */',
5131
		'$boardurl = \'http://127.0.0.1/smf\';',
5132
		'/**',
5133
		' * Email address to send emails from. (like [email protected].)',
5134
		' * @var string',
5135
		' */',
5136
		'$webmaster_email = \'[email protected]\';',
5137
		'/**',
5138
		' * Name of the cookie to set for authentication.',
5139
		' * @var string',
5140
		' */',
5141
		'$cookiename = \'SMFCookie21\';',
5142
		'',
5143
		'########## Database Info ##########',
5144
		'/**',
5145
		' * The database type',
5146
		' * Default options: mysql, postgresql',
5147
		' * @var string',
5148
		' */',
5149
		'$db_type = \'mysql\';',
5150
		'/**',
5151
		' * The database port',
5152
		' * 0 to use default port for the database type',
5153
		' * @var int',
5154
		' */',
5155
		'$db_port = 0;',
5156
		'/**',
5157
		' * The server to connect to (or a Unix socket)',
5158
		' * @var string',
5159
		' */',
5160
		'$db_server = \'localhost\';',
5161
		'/**',
5162
		' * The database name',
5163
		' * @var string',
5164
		' */',
5165
		'$db_name = \'smf\';',
5166
		'/**',
5167
		' * Database username',
5168
		' * @var string',
5169
		' */',
5170
		'$db_user = \'root\';',
5171
		'/**',
5172
		' * Database password',
5173
		' * @var string',
5174
		' */',
5175
		'$db_passwd = \'\';',
5176
		'/**',
5177
		' * Database user for when connecting with SSI',
5178
		' * @var string',
5179
		' */',
5180
		'$ssi_db_user = \'\';',
5181
		'/**',
5182
		' * Database password for when connecting with SSI',
5183
		' * @var string',
5184
		' */',
5185
		'$ssi_db_passwd = \'\';',
5186
		'/**',
5187
		' * A prefix to put in front of your table names.',
5188
		' * This helps to prevent conflicts',
5189
		' * @var string',
5190
		' */',
5191
		'$db_prefix = \'smf_\';',
5192
		'/**',
5193
		' * Use a persistent database connection',
5194
		' * @var int|bool',
5195
		' */',
5196
		'$db_persist = 0;',
5197
		'/**',
5198
		' *',
5199
		' * @var int|bool',
5200
		' */',
5201
		'$db_error_send = 0;',
5202
		'/**',
5203
		' * Override the default behavior of the database layer for mb4 handling',
5204
		' * null keep the default behavior untouched',
5205
		' * @var null|bool',
5206
		' */',
5207
		'$db_mb4 = null;',
5208
		'',
5209
		'########## Cache Info ##########',
5210
		'/**',
5211
		' * Select a cache system. You want to leave this up to the cache area of the admin panel for',
5212
		' * proper detection of apc, memcached, output_cache, smf, or xcache',
5213
		' * (you can add more with a mod).',
5214
		' * @var string',
5215
		' */',
5216
		'$cache_accelerator = \'\';',
5217
		'/**',
5218
		' * The level at which you would like to cache. Between 0 (off) through 3 (cache a lot).',
5219
		' * @var int',
5220
		' */',
5221
		'$cache_enable = 0;',
5222
		'/**',
5223
		' * This is only used for memcache / memcached. Should be a string of \'server:port,server:port\'',
5224
		' * @var array',
5225
		' */',
5226
		'$cache_memcached = \'\';',
5227
		'/**',
5228
		' * This is only for the \'smf\' file cache system. It is the path to the cache directory.',
5229
		' * It is also recommended that you place this in /tmp/ if you are going to use this.',
5230
		' * @var string',
5231
		' */',
5232
		'$cachedir = dirname(__FILE__) . \'/cache\';',
5233
		'',
5234
		'########## Image Proxy ##########',
5235
		'# This is done entirely in Settings.php to avoid loading the DB while serving the images',
5236
		'/**',
5237
		' * Whether the proxy is enabled or not',
5238
		' * @var bool',
5239
		' */',
5240
		'$image_proxy_enabled = true;',
5241
		'',
5242
		'/**',
5243
		' * Secret key to be used by the proxy',
5244
		' * @var string',
5245
		' */',
5246
		'$image_proxy_secret = \'smfisawesome\';',
5247
		'',
5248
		'/**',
5249
		' * Maximum file size (in KB) for individual files',
5250
		' * @var int',
5251
		' */',
5252
		'$image_proxy_maxsize = 5192;',
5253
		'',
5254
		'########## Directories/Files ##########',
5255
		'# Note: These directories do not have to be changed unless you move things.',
5256
		'/**',
5257
		' * The absolute path to the forum\'s folder. (not just \'.\'!)',
5258
		' * @var string',
5259
		' */',
5260
		'$boarddir = dirname(__FILE__);',
5261
		'/**',
5262
		' * Path to the Sources directory.',
5263
		' * @var string',
5264
		' */',
5265
		'$sourcedir = dirname(__FILE__) . \'/Sources\';',
5266
		'/**',
5267
		' * Path to the Packages directory.',
5268
		' * @var string',
5269
		' */',
5270
		'$packagesdir = dirname(__FILE__) . \'/Packages\';',
5271
		'/**',
5272
		' * Path to the tasks directory.',
5273
		' * @var string',
5274
		' */',
5275
		'$tasksdir = $sourcedir . \'/tasks\';',
5276
		'',
5277
		'# Make sure the paths are correct... at least try to fix them.',
5278
		'if (!file_exists($boarddir) && file_exists(dirname(__FILE__) . \'/agreement.txt\'))',
5279
		'	$boarddir = dirname(__FILE__);',
5280
		'if (!file_exists($sourcedir) && file_exists($boarddir . \'/Sources\'))',
5281
		'	$sourcedir = $boarddir . \'/Sources\';',
5282
		'if (!file_exists($cachedir) && file_exists($boarddir . \'/cache\'))',
5283
		'	$cachedir = $boarddir . \'/cache\';',
5284
		'',
5285
		'######### Legacy Settings #########',
5286
		'# UTF-8 is now the only character set supported in 2.1.',
5287
		'$db_character_set = \'utf8\';',
5288
		'',
5289
		'########## Error-Catching ##########',
5290
		'# Note: You shouldn\'t touch these settings.',
5291
		'if (file_exists((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\'))',
5292
		'	include((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\');',
5293
		'',
5294
		'if (!isset($db_last_error))',
5295
		'{',
5296
		'	// File does not exist so lets try to create it',
5297
		'	file_put_contents((isset($cachedir) ? $cachedir : dirname(__FILE__)) . \'/db_last_error.php\', \'<\' . \'?\' . "php\n" . \'$db_last_error = 0;\' . "\n" . \'?\' . \'>\');',
5298
		'	$db_last_error = 0;',
5299
		'}',
5300
		'',
5301
		'?' . '>',
5302
	);
5303
5304
	// Now, find all of the original settings.  Mark those for the "change".
5305
	$original = array();
5306
	foreach ($settingsVars as $setVar => $setType)
5307
	{
5308
		global $$setVar;
5309
5310
		// Find the setting.
5311
		if ($setType == 'string' || $setType == 'string_fatal')
5312
			$original[$setVar] = isset($$setVar) ? '\'' . addcslashes($$setVar, '\'\\') . '\'' : (strpos('fatal', $setType) ? null : '\'\'');
5313
		elseif ($setType == 'int' || $setType == 'int_fatal')
5314
			$original[$setVar] = isset($$setVar) ? (int) $$setVar : (strpos('fatal', $setType) ? null : 0);
5315
		elseif ($setType == 'bool' || $setType == 'bool_fatal')
5316
			$original[$setVar] = isset($$setVar) && in_array($$setVar, array(1, true)) ? 'true' : (strpos('fatal', $setType) ? null : 'false');
5317
		elseif ($setType == 'null' || $setType == 'null_fatal')
5318
			$original[$setVar] = isset($$setVar) && in_array($$setVar, array(1, true)) ? 'true' : (strpos('fatal', $setType) ? null : 'null');
5319
5320
		// Well this isn't good.  Do we fix it or bail?
5321
		if (is_null($original) && $setType != 'null' && strpos('fatal', $setType) > -1)
5322
			return throw_error(sprintf($txt['error_settings_migration_no_var'], $setVar));
5323
	}
5324
5325
	// Finally, merge the changes with the new ones.
5326
	$config_vars = $original;
5327
	foreach ($changes as $setVar => $value)
5328
	{
5329
		// Nothing needed here.
5330
		if ($setVar != 'upgradeData' && $config_vars[$setVar] == $changes[$setVar])
5331
			continue;
5332
5333
		$config_vars[$setVar] = $value;
5334
	}
5335
5336
	/*
5337
		It would be nice to call updateSettingsFile and be done with this. However the function doesn't support passing in the entire file. We also want to backup with a different name, just incase.
5338
	*/
5339
5340
	// When was Settings.php last changed?
5341
	$last_settings_change = filemtime($boarddir . '/Settings.php');
5342
5343
	// remove any \r's that made their way in here
5344
	foreach ($settingsArray as $k => $dummy)
5345
		$settingsArray[$k] = strtr($dummy, array("\r" => '')) . "\n";
5346
5347
	// go line by line and see whats changing
5348
	for ($i = 0, $n = count($settingsArray); $i < $n; $i++)
5349
	{
5350
		// Don't trim or bother with it if it's not a variable.
5351
		if (substr($settingsArray[$i], 0, 1) != '$')
5352
			continue;
5353
5354
		$settingsArray[$i] = trim($settingsArray[$i]) . "\n";
5355
5356
		// Look through the variables to set....
5357
		foreach ($config_vars as $var => $val)
5358
		{
5359
			// be sure someone is not updating db_last_error this with a group
5360
			if ($var === 'db_last_error')
5361
				unset($config_vars[$var]);
5362
			elseif (strncasecmp($settingsArray[$i], '$' . $var, 1 + strlen($var)) == 0)
5363
			{
5364
				$comment = strstr(substr($settingsArray[$i], strpos($settingsArray[$i], ';')), '#');
5365
				$settingsArray[$i] = '$' . $var . ' = ' . $val . ';' . ($comment == '' ? '' : "\t\t" . rtrim($comment)) . "\n";
5366
5367
				// This one's been 'used', so to speak.
5368
				unset($config_vars[$var]);
5369
			}
5370
		}
5371
5372
		// End of the file ... maybe
5373
		if (substr(trim($settingsArray[$i]), 0, 2) == '?' . '>')
5374
			$end = $i;
5375
	}
5376
5377
	// This should never happen, but apparently it is happening.
5378
	if (empty($end) || $end < 10)
5379
		$end = count($settingsArray) - 1;
5380
5381
	// Still more variables to go?  Then lets add them at the end.
5382
	if (!empty($config_vars))
5383
	{
5384
		if (trim($settingsArray[$end]) == '?' . '>')
5385
			$settingsArray[$end++] = '';
5386
		else
5387
			$end++;
5388
5389
		// Add in any newly defined vars that were passed
5390
		foreach ($config_vars as $var => $val)
5391
			$settingsArray[$end++] = '$' . $var . ' = ' . $val . ';' . "\n";
5392
5393
		$settingsArray[$end] = '?' . '>';
5394
	}
5395
	else
5396
		$settingsArray[$end] = trim($settingsArray[$end]);
5397
5398
	// Sanity error checking: the file needs to be at least 12 lines.
5399
	if (count($settingsArray) < 12)
5400
		return throw_error($txt['error_settings_migration_too_short']);
5401
5402
	// Try to avoid a few pitfalls:
5403
	//  - like a possible race condition,
5404
	//  - or a failure to write at low diskspace
5405
	//
5406
	// Check before you act: if cache is enabled, we can do a simple write test
5407
	// to validate that we even write things on this filesystem.
5408
	if ((empty($cachedir) || !file_exists($cachedir)) && file_exists($boarddir . '/cache'))
5409
		$cachedir = $boarddir . '/cache';
5410
5411
	$test_fp = @fopen($cachedir . '/settings_update.tmp', "w+");
5412
	if ($test_fp !== false)
5413
	{
5414
		fclose($test_fp);
5415
		$written_bytes = file_put_contents($cachedir . '/settings_update.tmp', 'test', LOCK_EX);
5416
		@unlink($cachedir . '/settings_update.tmp');
5417
5418
		// Oops. Low disk space, perhaps. Don't mess with Settings.php then.
5419
		// No means no. :P
5420
		if ($written_bytes !== 4)
5421
			return throw_error($txt['error_settings_migration_write_failed']);
5422
	}
5423
5424
	// Protect me from what I want! :P
5425
	clearstatcache();
5426
	if (filemtime($boarddir . '/Settings.php') === $last_settings_change)
5427
	{
5428
		// save the old before we do anything
5429
		$settings_backup_fail = !@is_writable($boarddir . '/Settings_org.php') || !@copy($boarddir . '/Settings.php', $boarddir . '/Settings_org.php');
5430
		$settings_backup_fail = !$settings_backup_fail ? (!file_exists($boarddir . '/Settings_org.php') || filesize($boarddir . '/Settings_org.php') === 0) : $settings_backup_fail;
5431
5432
		// write out the new
5433
		$write_settings = implode('', $settingsArray);
5434
		$written_bytes = file_put_contents($boarddir . '/Settings.php', $write_settings, LOCK_EX);
5435
5436
		// survey says ...
5437
		if ($written_bytes !== strlen($write_settings) && !$settings_backup_fail)
5438
		{
5439
			if (file_exists($boarddir . '/Settings_bak.php'))
5440
				@copy($boarddir . '/Settings_bak.php', $boarddir . '/Settings.php');
5441
5442
			return throw_error($txt['error_settings_migration_general']);
5443
		}
5444
	}
5445
5446
	// Even though on normal installations the filemtime should prevent this being used by the installer incorrectly
5447
	// it seems that there are times it might not. So let's MAKE it dump the cache.
5448
	if (function_exists('opcache_invalidate'))
5449
		opcache_invalidate($boarddir . '/Settings.php', true);
5450
}
5451
5452
/**
5453
 * Determine if we should auto select the migrate Settings file.  This is determined by a variety of missing settings.
5454
 * Prior to checking these settings, we look for advanced setups such as integrations or if variables have been moved
5455
 * to another file.  If these are detected, we abort.
5456
 *
5457
 * @param array $config_vars An array of one or more variables to update
5458
 *
5459
 * @return bool We either successfully update the Settings file, or throw a error here.
5460
 */
5461
function detectSettingsFileMigrationNeeded()
5462
{
5463
	global $boarddir, $packagesdir, $tasksdir, $db_server, $db_type, $image_proxy_enabled, $db_show_debug;
5464
5465
	// We should not migrate if db_show_debug is in there, some dev stuff going on here.
5466
	if (isset($db_show_debug))
5467
		return false;
5468
5469
	$file_contents = file_get_contents($boarddir . '/Settings.php');
5470
5471
	// Is there a include statement somewhere in there? Some advanced handling of the variables elsewhere?
5472
	// Try our best to stay away from the cachedir match.
5473
	if (preg_match('~\sinclude\((?:(?!\(isset\(\$cachedir))~im', $file_contents))
5474
		return false;
5475
5476
	// If we find a mention of $GLOBALS, there may be a integration going on.
5477
	if (preg_match('~\$GLOBALS\[~im', $file_contents))
5478
		return false;
5479
5480
	// If these are not set, it makes us a candidate to migrate.
5481
	if (!isset($packagesdir, $tasksdir, $db_server, $db_type, $image_proxy_enabled))
5482
		return true;
5483
5484
	return false;
5485
}
5486
5487
?>