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

template_upgrade_options()   B

Complexity

Conditions 7
Paths 4

Size

Total Lines 69
Code Lines 29

Duplication

Lines 0
Ratio 0 %

Importance

Changes 0
Metric Value
cc 7
eloc 29
nc 4
nop 0
dl 0
loc 69
rs 8.5226
c 0
b 0
f 0

How to fix   Long Method   

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

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

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

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

Loading history...
346
	}
347
348
	// Handle the progress of the step, if any.
349
	if (!empty($upcontext['step_progress']) && isset($upcontext['steps'][$upcontext['current_step']]))
350
	{
351
		$upcontext['step_progress'] = round($upcontext['step_progress'], 1);
352
		$upcontext['overall_percent'] += $upcontext['step_progress'] * ($upcontext['steps'][$upcontext['current_step']][3] / 100);
353
	}
354
	$upcontext['overall_percent'] = (int) $upcontext['overall_percent'];
355
356
	// We usually dump our templates out.
357
	if (!$fallThrough)
358
	{
359
		// This should not happen my dear... HELP ME DEVELOPERS!!
360
		if (!empty($command_line))
361
		{
362
			if (function_exists('debug_print_backtrace'))
363
				debug_print_backtrace();
364
365
			printf($txt['error_unexpected_template_call'], isset($upcontext['sub_template']) ? $upcontext['sub_template'] : '');
366
			flush();
367
			die();
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...
368
		}
369
370
		if (!isset($_GET['xml']))
371
			template_upgrade_above();
372
		else
373
		{
374
			header('content-type: text/xml; charset=UTF-8');
375
			// Sadly we need to retain the $_GET data thanks to the old upgrade scripts.
376
			$upcontext['get_data'] = array();
377
			foreach ($_GET as $k => $v)
378
			{
379
				if (substr($k, 0, 3) != 'amp' && !in_array($k, array('xml', 'substep', 'lang', 'data', 'step', 'filecount')))
380
				{
381
					$upcontext['get_data'][$k] = $v;
382
				}
383
			}
384
			template_xml_above();
385
		}
386
387
		// Call the template.
388
		if (isset($upcontext['sub_template']))
389
		{
390
			$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
391
			$upcontext['form_url'] = $upgradeurl . '?step=' . $upcontext['current_step'] . '&amp;substep=' . $_GET['substep'] . '&amp;data=' . base64_encode(json_encode($upcontext['upgrade_status']));
392
393
			// Custom stuff to pass back?
394
			if (!empty($upcontext['query_string']))
395
				$upcontext['form_url'] .= $upcontext['query_string'];
396
397
			// Call the appropriate subtemplate
398
			if (is_callable('template_' . $upcontext['sub_template']))
399
				call_user_func('template_' . $upcontext['sub_template']);
400
			else
401
				die(sprintf($txt['error_invalid_template'], $upcontext['sub_template']));
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...
402
		}
403
404
		// Was there an error?
405
		if (!empty($upcontext['forced_error_message']))
406
			echo $upcontext['forced_error_message'];
407
408
		// Show the footer.
409
		if (!isset($_GET['xml']))
410
			template_upgrade_below();
411
		else
412
			template_xml_below();
413
	}
414
415
	// Show the upgrade time for CLI when we are completely done, if in debug mode.
416
	if (!empty($command_line) && $is_debug)
417
	{
418
		$active = time() - $upcontext['started'];
419
		$hours = floor($active / 3600);
420
		$minutes = intval(($active / 60) % 60);
421
		$seconds = intval($active % 60);
422
423
		if ($hours > 0)
424
			echo "\n" . '', sprintf($txt['upgrade_completed_time_hms'], $hours, $minutes, $seconds), '' . "\n";
425
		elseif ($minutes > 0)
426
			echo "\n" . '', sprintf($txt['upgrade_completed_time_ms'], $minutes, $seconds), '' . "\n";
427
		elseif ($seconds > 0)
428
			echo "\n" . '', sprintf($txt['upgrade_completed_time_s'], $seconds), '' . "\n";
429
	}
430
431
	// Bang - gone!
432
	die();
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...
433
}
434
435
// Load the list of language files, and the current language file.
436
function load_lang_file()
437
{
438
	global $txt, $upcontext, $language, $modSettings;
439
440
	static $lang_dir = '', $detected_languages = array(), $loaded_langfile = '';
441
442
	// Do we know where to look for the language files, or shall we just guess for now?
443
	$temp = isset($modSettings['theme_dir']) ? $modSettings['theme_dir'] . '/languages' : dirname(__FILE__) . '/Themes/default/languages';
444
445
	if ($lang_dir != $temp)
446
	{
447
		$lang_dir = $temp;
448
		$detected_languages = array();
449
	}
450
451
	// Override the language file?
452
	if (isset($upcontext['language']))
453
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['language'] . '.php';
454
	elseif (isset($upcontext['lang']))
455
		$_SESSION['upgrader_langfile'] = 'Install.' . $upcontext['lang'] . '.php';
456
	elseif (isset($language))
457
		$_SESSION['upgrader_langfile'] = 'Install.' . $language . '.php';
458
459
	// Avoid pointless repetition
460
	if (isset($_SESSION['upgrader_langfile']) && $loaded_langfile == $lang_dir . '/' . $_SESSION['upgrader_langfile'])
461
		return;
462
463
	// Now try to find the language files
464
	if (empty($detected_languages))
465
	{
466
		// Make sure the languages directory actually exists.
467
		if (file_exists($lang_dir))
468
		{
469
			// Find all the "Install" language files in the directory.
470
			$dir = dir($lang_dir);
471
			while ($entry = $dir->read())
472
			{
473
				// Skip any old '-utf8' language files that might be lying around
474
				if (strpos($entry, '-utf8') !== false)
475
					continue;
476
477
				if (substr($entry, 0, 8) == 'Install.' && substr($entry, -4) == '.php')
478
					$detected_languages[$entry] = ucfirst(substr($entry, 8, strlen($entry) - 12));
479
			}
480
			$dir->close();
481
		}
482
		// Our guess was wrong, but that's fine. We'll try again after $modSettings['theme_dir'] is defined.
483
		elseif (!isset($modSettings['theme_dir']))
484
		{
485
			// Define a few essential strings for now.
486
			$txt['error_db_connect_settings'] = 'Cannot connect to the database server.<br><br>Please check that the database info variables are correct in Settings.php.';
487
			$txt['error_sourcefile_missing'] = 'Unable to find the Sources/%1$s file. Please make sure it was uploaded properly, and then try again.';
488
489
			$txt['warning_lang_old'] = 'The language files for your selected language, %1$s, have not been updated to the latest version. Upgrade will continue with the forum default, %2$s.';
490
			$txt['warning_lang_missing'] = 'The upgrader could not find the &quot;Install&quot; language file for your selected language, %1$s. Upgrade will continue with the forum default, %2$s.';
491
492
			return;
493
		}
494
	}
495
496
	// Didn't find any, show an error message!
497
	if (empty($detected_languages))
498
	{
499
		$from = explode('/', $_SERVER['PHP_SELF']);
500
		$to = explode('/', $lang_dir);
501
		$relPath = $to;
502
503
		foreach($from as $depth => $dir)
504
		{
505
			if ($dir === $to[$depth])
506
				array_shift($relPath);
507
			else
508
			{
509
				$remaining = count($from) - $depth;
510
				if ($remaining > 1)
511
				{
512
					$padLength = (count($relPath) + $remaining - 1) * -1;
513
					$relPath = array_pad($relPath, $padLength, '..');
514
					break;
515
				}
516
				else
517
					$relPath[0] = './' . $relPath[0];
518
			}
519
		}
520
		$relPath = implode(DIRECTORY_SEPARATOR, $relPath);
521
522
		// Let's not cache this message, eh?
523
		header('Expires: Mon, 26 Jul 1997 05:00:00 GMT');
524
		header('Last-Modified: ' . gmdate('D, d M Y H:i:s') . ' GMT');
525
		header('Cache-Control: no-cache');
526
527
		echo '<!DOCTYPE html>
528
			<html>
529
				<head>
530
					<title>SMF Upgrader: Error!</title>
531
						<style>
532
							body {
533
								font-family: sans-serif;
534
								max-width: 700px; }
535
536
								h1 {
537
									font-size: 14pt; }
538
539
								.directory {
540
									margin: 0.3em;
541
									font-family: monospace;
542
									font-weight: bold; }
543
						</style>
544
				</head>
545
				<body>
546
					<h1>A critical error has occurred.</h1>
547
						<p>This upgrader was unable to find the upgrader\'s language file or files.  They should be found under:</p>
548
						<div class="directory">', $relPath, '</div>
549
						<p>In some cases, FTP clients do not properly upload files with this many folders. Please double check to make sure you <strong>have uploaded all the files in the distribution</strong>.</p>
550
						<p>If that doesn\'t help, please make sure this upgrade.php file is in the same place as the Themes folder.</p>
551
						<p>If you continue to get this error message, feel free to <a href="https://support.simplemachines.org/">look to us for support</a>.</p>
552
				</body>
553
			</html>';
554
		die;
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...
555
	}
556
557
	// Make sure it exists. If it doesn't, reset it.
558
	if (!isset($_SESSION['upgrader_langfile']) || preg_match('~[^\w.-]~', $_SESSION['upgrader_langfile']) === 1 || !file_exists($lang_dir . '/' . $_SESSION['upgrader_langfile']))
559
	{
560
		// Use the first one...
561
		list ($_SESSION['upgrader_langfile']) = array_keys($detected_languages);
562
563
		// If we have English and some other language, use the other language.
564
		if ($_SESSION['upgrader_langfile'] == 'Install.english.php' && count($detected_languages) > 1)
565
			list (, $_SESSION['upgrader_langfile']) = array_keys($detected_languages);
566
	}
567
568
	// For backup we load English at first, then the second language will overwrite it.
569
	if ($_SESSION['upgrader_langfile'] != 'Install.english.php')
570
		require_once($lang_dir . '/Install.english.php');
571
572
	// And now include the actual language file itself.
573
	require_once($lang_dir . '/' . $_SESSION['upgrader_langfile']);
574
575
	// Remember what we've done
576
	$loaded_langfile = $lang_dir . '/' . $_SESSION['upgrader_langfile'];
577
}
578
579
// Used to direct the user to another location.
580
function redirectLocation($location, $addForm = true)
581
{
582
	global $upgradeurl, $upcontext, $command_line;
583
584
	// Command line users can't be redirected.
585
	if ($command_line)
586
		upgradeExit(true);
587
588
	// Are we providing the core info?
589
	if ($addForm)
590
	{
591
		$upcontext['upgrade_status']['curstep'] = $upcontext['current_step'];
592
		$location = $upgradeurl . '?step=' . $upcontext['current_step'] . '&substep=' . $_GET['substep'] . '&data=' . base64_encode(json_encode($upcontext['upgrade_status'])) . $location;
593
	}
594
595
	while (@ob_end_clean())
596
		header('location: ' . strtr($location, array('&amp;' => '&')));
597
598
	// Exit - saving status as we go.
599
	upgradeExit(true);
600
}
601
602
// Load all essential data and connect to the DB as this is pre SSI.php
603
function loadEssentialData()
604
{
605
	global $db_server, $db_user, $db_passwd, $db_name, $db_connection;
606
	global $db_prefix, $db_character_set, $db_type, $db_port;
607
	global $db_mb4, $modSettings, $sourcedir, $smcFunc, $txt;
608
609
	error_reporting(E_ALL);
610
	define('SMF', 1);
611
612
	// Start the session.
613
	if (@ini_get('session.save_handler') == 'user')
614
		@ini_set('session.save_handler', 'files');
615
	@session_start();
616
617
	if (empty($smcFunc))
618
		$smcFunc = array();
619
620
	$smcFunc['random_int'] = function($min = 0, $max = PHP_INT_MAX)
621
	{
622
		global $sourcedir;
623
624
		// Oh, wouldn't it be great if I *was* crazy? Then the world would be okay.
625
		if (!is_callable('random_int'))
626
			require_once($sourcedir . '/random_compat/random.php');
627
628
		return random_int($min, $max);
629
	};
630
631
	// We need this for authentication and some upgrade code
632
	require_once($sourcedir . '/Subs-Auth.php');
633
	require_once($sourcedir . '/Class-Package.php');
634
635
	$smcFunc['strtolower'] = 'smf_strtolower';
636
637
	// Initialize everything...
638
	initialize_inputs();
639
640
	// Get the database going!
641
	if (empty($db_type) || $db_type == 'mysqli')
642
	{
643
		$db_type = 'mysql';
644
		// If overriding $db_type, need to set its settings.php entry too
645
		$changes = array();
646
		$changes['db_type'] = '\'mysql\'';
647
		require_once($sourcedir . '/Subs-Admin.php');
648
		updateSettingsFile($changes);
649
	}
650
651
	if (file_exists($sourcedir . '/Subs-Db-' . $db_type . '.php'))
652
	{
653
		require_once($sourcedir . '/Subs-Db-' . $db_type . '.php');
654
655
		// Make the connection...
656
		if (empty($db_connection))
657
		{
658
			$options = array('non_fatal' => true);
659
			// Add in the port if needed
660
			if (!empty($db_port))
661
				$options['port'] = $db_port;
662
663
			if (!empty($db_mb4))
664
				$options['db_mb4'] = $db_mb4;
665
666
			$db_connection = smf_db_initiate($db_server, $db_name, $db_user, $db_passwd, $db_prefix, $options);
667
		}
668
		else
669
			// If we've returned here, ping/reconnect to be safe
670
			$smcFunc['db_ping']($db_connection);
671
672
		// Oh dear god!!
673
		if ($db_connection === null)
674
			die($txt['error_db_connect_settings']);
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...
675
676
		if ($db_type == 'mysql' && isset($db_character_set) && preg_match('~^\w+$~', $db_character_set) === 1)
677
			$smcFunc['db_query']('', '
678
				SET NAMES {string:db_character_set}',
679
				array(
680
					'db_error_skip' => true,
681
					'db_character_set' => $db_character_set,
682
				)
683
			);
684
685
		// Load the modSettings data...
686
		$request = $smcFunc['db_query']('', '
687
			SELECT variable, value
688
			FROM {db_prefix}settings',
689
			array(
690
				'db_error_skip' => true,
691
			)
692
		);
693
		$modSettings = array();
694
		while ($row = $smcFunc['db_fetch_assoc']($request))
695
			$modSettings[$row['variable']] = $row['value'];
696
		$smcFunc['db_free_result']($request);
697
	}
698
	else
699
		return throw_error(sprintf($txt['error_sourcefile_missing'], 'Subs-Db-' . $db_type . '.php'));
700
701
	require_once($sourcedir . '/Subs.php');
702
703
	// If they don't have the file, they're going to get a warning anyway so we won't need to clean request vars.
704
	if (file_exists($sourcedir . '/QueryString.php') && php_version_check())
705
	{
706
		require_once($sourcedir . '/QueryString.php');
707
		cleanRequest();
708
	}
709
710
	if (!isset($_GET['substep']))
711
		$_GET['substep'] = 0;
712
}
713
714
function initialize_inputs()
715
{
716
	global $start_time, $db_type;
717
718
	$start_time = time();
719
720
	umask(0);
721
722
	ob_start();
723
724
	// Better to upgrade cleanly and fall apart than to screw everything up if things take too long.
725
	ignore_user_abort(true);
726
727
	// This is really quite simple; if ?delete is on the URL, delete the upgrader...
728
	if (isset($_GET['delete']))
729
	{
730
		@unlink(__FILE__);
731
732
		// And the extra little files ;).
733
		@unlink(dirname(__FILE__) . '/upgrade_1-0.sql');
734
		@unlink(dirname(__FILE__) . '/upgrade_1-1.sql');
735
		@unlink(dirname(__FILE__) . '/upgrade_2-0_' . $db_type . '.sql');
736
		@unlink(dirname(__FILE__) . '/upgrade_2-1_' . $db_type . '.sql');
737
		@unlink(dirname(__FILE__) . '/upgrade-helper.php');
738
739
		$dh = opendir(dirname(__FILE__));
740
		while ($file = readdir($dh))
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

740
		while ($file = readdir(/** @scrutinizer ignore-type */ $dh))
Loading history...
741
		{
742
			if (preg_match('~upgrade_\d-\d_([A-Za-z])+\.sql~i', $file, $matches) && isset($matches[1]))
743
				@unlink(dirname(__FILE__) . '/' . $file);
744
		}
745
		closedir($dh);
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

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

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

910
		fwrite(/** @scrutinizer ignore-type */ $fp, $modSettings['agreement']);
Loading history...
911
		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

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

1094
				$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($modSettings['theme_dir'] . '/languages/index.' . $user_language . '.php')), 0, 4096);
Loading history...
1095
				preg_match('~(?://|/\*)\s*Version:\s+(.+?);\s*index(?:[\s]{2}|\*/)~i', $temp, $match);
1096
1097
				if (empty($match[1]) || $match[1] != SMF_LANG_VERSION)
1098
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_old'], $user_language, $upcontext['language']);
1099
				elseif (!file_exists($modSettings['theme_dir'] . '/languages/Install.' . $user_language . '.php'))
1100
					$upcontext['upgrade_options_warning'] = sprintf($txt['warning_lang_missing'], $user_language, $upcontext['language']);
1101
				else
1102
				{
1103
					// Set this as the new language.
1104
					$upcontext['language'] = $user_language;
1105
					$upcontext['upgrade_status']['lang'] = $upcontext['language'];
1106
1107
					// Include the file.
1108
					load_lang_file();
1109
				}
1110
			}
1111
1112
			// If we're resuming set the step and substep to be correct.
1113
			if (isset($_POST['cont']))
1114
			{
1115
				$upcontext['current_step'] = $upcontext['user']['step'];
1116
				$_GET['substep'] = $upcontext['user']['substep'];
1117
			}
1118
1119
			return true;
1120
		}
1121
	}
1122
1123
	return false;
1124
}
1125
1126
// Step 1: Do the maintenance and backup.
1127
function UpgradeOptions()
1128
{
1129
	global $db_prefix, $command_line, $modSettings, $is_debug, $smcFunc, $packagesdir, $tasksdir, $language, $txt, $db_port;
1130
	global $boarddir, $boardurl, $sourcedir, $maintenance, $cachedir, $upcontext, $db_type, $db_server, $image_proxy_enabled;
1131
1132
	$upcontext['sub_template'] = 'upgrade_options';
1133
	$upcontext['page_title'] = $txt['upgrade_options'];
1134
1135
	db_extend('packages');
1136
	$upcontext['karma_installed'] = array('good' => false, 'bad' => false);
1137
	$member_columns = $smcFunc['db_list_columns']('{db_prefix}members');
1138
1139
	$upcontext['karma_installed']['good'] = in_array('karma_good', $member_columns);
1140
	$upcontext['karma_installed']['bad'] = in_array('karma_bad', $member_columns);
1141
1142
	unset($member_columns);
1143
1144
	// If these options are missing, we may need to migrate to a new Settings.php
1145
	$upcontext['migrateSettingsNeeded'] = detectSettingsFileMigrationNeeded();
1146
1147
	// If we've not submitted then we're done.
1148
	if (empty($_POST['upcont']))
1149
		return false;
1150
1151
	// Firstly, if they're enabling SM stat collection just do it.
1152
	if (!empty($_POST['stats']) && substr($boardurl, 0, 16) != 'http://localhost' && empty($modSettings['allow_sm_stats']) && empty($modSettings['enable_sm_stats']))
1153
	{
1154
		$upcontext['allow_sm_stats'] = true;
1155
1156
		// Don't register if we still have a key.
1157
		if (empty($modSettings['sm_stats_key']))
1158
		{
1159
			// Attempt to register the site etc.
1160
			$fp = @fsockopen('www.simplemachines.org', 80, $errno, $errstr);
1161
			if ($fp)
0 ignored issues
show
introduced by
$fp is of type false|resource, thus it always evaluated to false.
Loading history...
1162
			{
1163
				$out = 'GET /smf/stats/register_stats.php?site=' . base64_encode($boardurl) . ' HTTP/1.1' . "\r\n";
1164
				$out .= 'Host: www.simplemachines.org' . "\r\n";
1165
				$out .= 'Connection: Close' . "\r\n\r\n";
1166
				fwrite($fp, $out);
1167
1168
				$return_data = '';
1169
				while (!feof($fp))
1170
					$return_data .= fgets($fp, 128);
1171
1172
				fclose($fp);
1173
1174
				// Get the unique site ID.
1175
				preg_match('~SITE-ID:\s(\w{10})~', $return_data, $ID);
1176
1177
				if (!empty($ID[1]))
1178
					$smcFunc['db_insert']('replace',
1179
						$db_prefix . 'settings',
1180
						array('variable' => 'string', 'value' => 'string'),
1181
						array(
1182
							array('sm_stats_key', $ID[1]),
1183
							array('enable_sm_stats', 1),
1184
						),
1185
						array('variable')
1186
					);
1187
			}
1188
		}
1189
		else
1190
		{
1191
			$smcFunc['db_insert']('replace',
1192
				$db_prefix . 'settings',
1193
				array('variable' => 'string', 'value' => 'string'),
1194
				array('enable_sm_stats', 1),
1195
				array('variable')
1196
			);
1197
		}
1198
	}
1199
	// Don't remove stat collection unless we unchecked the box for real, not from the loop.
1200
	elseif (empty($_POST['stats']) && empty($upcontext['allow_sm_stats']))
1201
		$smcFunc['db_query']('', '
1202
			DELETE FROM {db_prefix}settings
1203
			WHERE variable = {string:enable_sm_stats}',
1204
			array(
1205
				'enable_sm_stats' => 'enable_sm_stats',
1206
				'db_error_skip' => true,
1207
			)
1208
		);
1209
1210
	// Deleting old karma stuff?
1211
	if (!empty($_POST['delete_karma']))
1212
	{
1213
		// Delete old settings vars.
1214
		$smcFunc['db_query']('', '
1215
			DELETE FROM {db_prefix}settings
1216
			WHERE variable IN ({array_string:karma_vars})',
1217
			array(
1218
				'karma_vars' => array('karmaMode', 'karmaTimeRestrictAdmins', 'karmaWaitTime', 'karmaMinPosts', 'karmaLabel', 'karmaSmiteLabel', 'karmaApplaudLabel'),
1219
			)
1220
		);
1221
1222
		// Cleaning up old karma member settings.
1223
		if ($upcontext['karma_installed']['good'])
1224
			$smcFunc['db_query']('', '
1225
				ALTER TABLE {db_prefix}members
1226
				DROP karma_good',
1227
				array()
1228
			);
1229
1230
		// Does karma bad was enable?
1231
		if ($upcontext['karma_installed']['bad'])
1232
			$smcFunc['db_query']('', '
1233
				ALTER TABLE {db_prefix}members
1234
				DROP karma_bad',
1235
				array()
1236
			);
1237
1238
		// Cleaning up old karma permissions.
1239
		$smcFunc['db_query']('', '
1240
			DELETE FROM {db_prefix}permissions
1241
			WHERE permission = {string:karma_vars}',
1242
			array(
1243
				'karma_vars' => 'karma_edit',
1244
			)
1245
		);
1246
		// Cleaning up old log_karma table
1247
		$smcFunc['db_query']('', '
1248
			DROP TABLE IF EXISTS {db_prefix}log_karma',
1249
			array()
1250
		);
1251
	}
1252
1253
	// Emptying the error log?
1254
	if (!empty($_POST['empty_error']))
1255
		$smcFunc['db_query']('truncate_table', '
1256
			TRUNCATE {db_prefix}log_errors',
1257
			array(
1258
			)
1259
		);
1260
1261
	$changes = array();
1262
1263
	// Add proxy settings.
1264
	if (!isset($GLOBALS['image_proxy_maxsize']))
1265
		$changes += array(
1266
			'image_proxy_secret' => '\'' . substr(sha1(mt_rand()), 0, 20) . '\'',
1267
			'image_proxy_maxsize' => 5190,
1268
			'image_proxy_enabled' => 0,
1269
		);
1270
1271
	// If $boardurl reflects https, set force_ssl
1272
	if (!function_exists('cache_put_data'))
1273
		require_once($sourcedir . '/Load.php');
1274
	if (stripos($boardurl, 'https://') !== false)
1275
		updateSettings(array('force_ssl' => '1'));
1276
1277
	// If we're overriding the language follow it through.
1278
	if (isset($upcontext['lang']) && file_exists($modSettings['theme_dir'] . '/languages/index.' . $upcontext['lang'] . '.php'))
1279
		$changes['language'] = '\'' . $upcontext['lang'] . '\'';
1280
1281
	if (!empty($_POST['maint']))
1282
	{
1283
		$changes['maintenance'] = '2';
1284
		// Remember what it was...
1285
		$upcontext['user']['main'] = $maintenance;
1286
1287
		if (!empty($_POST['maintitle']))
1288
		{
1289
			$changes['mtitle'] = '\'' . addslashes($_POST['maintitle']) . '\'';
1290
			$changes['mmessage'] = '\'' . addslashes($_POST['mainmessage']) . '\'';
1291
		}
1292
		else
1293
		{
1294
			$changes['mtitle'] = '\'' . addslashes($txt['mtitle']) . '\'';
1295
			$changes['mmessage'] = '\'' . addslashes($txt['mmessage']) . '\'';
1296
		}
1297
	}
1298
1299
	if ($command_line)
1300
		echo ' * Updating Settings.php...';
1301
1302
	// Fix some old paths.
1303
	if (substr($boarddir, 0, 1) == '.')
1304
		$changes['boarddir'] = '\'' . fixRelativePath($boarddir) . '\'';
1305
1306
	if (substr($sourcedir, 0, 1) == '.')
1307
		$changes['sourcedir'] = '\'' . fixRelativePath($sourcedir) . '\'';
1308
1309
	if (empty($cachedir) || substr($cachedir, 0, 1) == '.')
1310
		$changes['cachedir'] = '\'' . fixRelativePath($boarddir) . '/cache\'';
1311
1312
	// If they have a "host:port" setup for the host, split that into separate values
1313
	// You should never have a : in the hostname if you're not on MySQL, but better safe than sorry
1314
	if (strpos($db_server, ':') !== false && $db_type == 'mysql')
1315
	{
1316
		list ($db_server, $db_port) = explode(':', $db_server);
1317
1318
		$changes['db_server'] = '\'' . $db_server . '\'';
1319
1320
		// Only set this if we're not using the default port
1321
		if ($db_port != ini_get('mysqli.default_port'))
1322
			$changes['db_port'] = (int) $db_port;
1323
	}
1324
	elseif (!empty($db_port))
1325
	{
1326
		// If db_port is set and is the same as the default, set it to ''
1327
		if ($db_type == 'mysql')
1328
		{
1329
			if ($db_port == ini_get('mysqli.default_port'))
1330
				$changes['db_port'] = '\'\'';
1331
			elseif ($db_type == 'postgresql' && $db_port == 5432)
1332
				$changes['db_port'] = '\'\'';
1333
		}
1334
	}
1335
1336
	// Maybe we haven't had this option yet?
1337
	if (empty($packagesdir))
1338
		$changes['packagesdir'] = '\'' . fixRelativePath($boarddir) . '/Packages\'';
1339
1340
	// Add support for $tasksdir var.
1341
	if (empty($tasksdir))
1342
		$changes['tasksdir'] = '\'' . fixRelativePath($sourcedir) . '/tasks\'';
1343
1344
	// Make sure we fix the language as well.
1345
	if (stristr($language, '-utf8'))
1346
		$changes['language'] = '\'' . str_ireplace('-utf8', '', $language) . '\'';
1347
1348
	// @todo Maybe change the cookie name if going to 1.1, too?
1349
1350
	// If we are migrating the settings, get them ready.
1351
	if (!empty($_POST['migrateSettings']))
1352
	{
1353
		// Ensure this doesn't get lost in translation.
1354
		$changes['upgradeData'] = '"' . base64_encode(json_encode($upcontext['user'])) . '"';
1355
1356
		migrateSettingsFile($changes);
1357
	}
1358
	else
1359
	{
1360
		// Update Settings.php with the new settings.
1361
		require_once($sourcedir . '/Subs-Admin.php');
1362
		updateSettingsFile($changes);
1363
1364
		// Tell Settings.php to store db_last_error.php in the cache
1365
		move_db_last_error_to_cachedir();
1366
	}
1367
1368
	if ($command_line)
1369
		echo ' Successful.' . "\n";
1370
1371
	// Are we doing debug?
1372
	if (isset($_POST['debug']))
1373
	{
1374
		$upcontext['upgrade_status']['debug'] = true;
1375
		$is_debug = true;
1376
	}
1377
1378
	// If we're not backing up then jump one.
1379
	if (empty($_POST['backup']))
1380
		$upcontext['current_step']++;
1381
1382
	// If we've got here then let's proceed to the next step!
1383
	return true;
1384
}
1385
1386
// Backup the database - why not...
1387
function BackupDatabase()
1388
{
1389
	global $upcontext, $db_prefix, $command_line, $support_js, $file_steps, $smcFunc, $txt;
1390
1391
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'backup_xml' : 'backup_database';
1392
	$upcontext['page_title'] = $txt['backup_database'];
1393
1394
	// Done it already - js wise?
1395
	if (!empty($_POST['backup_done']))
1396
		return true;
1397
1398
	// Some useful stuff here.
1399
	db_extend();
1400
1401
	// Might need this as well
1402
	db_extend('packages');
1403
1404
	// Get all the table names.
1405
	$filter = str_replace('_', '\_', preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? $match[2] : $db_prefix) . '%';
1406
	$db = preg_match('~^`(.+?)`\.(.+?)$~', $db_prefix, $match) != 0 ? strtr($match[1], array('`' => '')) : false;
1407
	$tables = $smcFunc['db_list_tables']($db, $filter);
1408
1409
	$table_names = array();
1410
	foreach ($tables as $table)
1411
		if (substr($table, 0, 7) !== 'backup_')
1412
			$table_names[] = $table;
1413
1414
	$upcontext['table_count'] = count($table_names);
1415
	$upcontext['cur_table_num'] = $_GET['substep'];
1416
	$upcontext['cur_table_name'] = str_replace($db_prefix, '', isset($table_names[$_GET['substep']]) ? $table_names[$_GET['substep']] : $table_names[0]);
1417
	$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1418
	// For non-java auto submit...
1419
	$file_steps = $upcontext['table_count'];
1420
1421
	// What ones have we already done?
1422
	foreach ($table_names as $id => $table)
1423
		if ($id < $_GET['substep'])
1424
			$upcontext['previous_tables'][] = $table;
1425
1426
	if ($command_line)
1427
		echo 'Backing Up Tables.';
1428
1429
	// If we don't support javascript we backup here.
1430
	if (!$support_js || isset($_GET['xml']))
1431
	{
1432
		// Backup each table!
1433
		for ($substep = $_GET['substep'], $n = count($table_names); $substep < $n; $substep++)
1434
		{
1435
			$upcontext['cur_table_name'] = str_replace($db_prefix, '', (isset($table_names[$substep + 1]) ? $table_names[$substep + 1] : $table_names[$substep]));
1436
			$upcontext['cur_table_num'] = $substep + 1;
1437
1438
			$upcontext['step_progress'] = (int) (($upcontext['cur_table_num'] / $upcontext['table_count']) * 100);
1439
1440
			// Do we need to pause?
1441
			nextSubstep($substep);
1442
1443
			backupTable($table_names[$substep]);
1444
1445
			// If this is XML to keep it nice for the user do one table at a time anyway!
1446
			if (isset($_GET['xml']))
1447
				return upgradeExit();
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...
1448
		}
1449
1450
		if ($command_line)
1451
		{
1452
			echo "\n" . ' Successful.\'' . "\n";
1453
			flush();
1454
		}
1455
		$upcontext['step_progress'] = 100;
1456
1457
		$_GET['substep'] = 0;
1458
		// Make sure we move on!
1459
		return true;
1460
	}
1461
1462
	// Either way next place to post will be database changes!
1463
	$_GET['substep'] = 0;
1464
	return false;
1465
}
1466
1467
// Backup one table...
1468
function backupTable($table)
1469
{
1470
	global $command_line, $db_prefix, $smcFunc;
1471
1472
	if ($command_line)
1473
	{
1474
		echo "\n" . ' +++ Backing up \"' . str_replace($db_prefix, '', $table) . '"...';
1475
		flush();
1476
	}
1477
1478
	$smcFunc['db_backup_table']($table, 'backup_' . $table);
1479
1480
	if ($command_line)
1481
		echo ' done.';
1482
}
1483
1484
// Step 2: Everything.
1485
function DatabaseChanges()
1486
{
1487
	global $db_prefix, $modSettings, $smcFunc, $txt;
1488
	global $upcontext, $support_js, $db_type;
1489
1490
	// Have we just completed this?
1491
	if (!empty($_POST['database_done']))
1492
		return true;
1493
1494
	$upcontext['sub_template'] = isset($_GET['xml']) ? 'database_xml' : 'database_changes';
1495
	$upcontext['page_title'] = $txt['database_changes'];
1496
1497
	// All possible files.
1498
	// Name, < version, insert_on_complete
1499
	$files = array(
1500
		array('upgrade_1-0.sql', '1.1', '1.1 RC0'),
1501
		array('upgrade_1-1.sql', '2.0', '2.0 a'),
1502
		array('upgrade_2-0_' . $db_type . '.sql', '2.1', '2.1 dev0'),
1503
		array('upgrade_2-1_' . $db_type . '.sql', '3.0', SMF_VERSION),
1504
	);
1505
1506
	// How many files are there in total?
1507
	if (isset($_GET['filecount']))
1508
		$upcontext['file_count'] = (int) $_GET['filecount'];
1509
	else
1510
	{
1511
		$upcontext['file_count'] = 0;
1512
		foreach ($files as $file)
1513
		{
1514
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1515
				$upcontext['file_count']++;
1516
		}
1517
	}
1518
1519
	// Do each file!
1520
	$did_not_do = count($files) - $upcontext['file_count'];
1521
	$upcontext['step_progress'] = 0;
1522
	$upcontext['cur_file_num'] = 0;
1523
	foreach ($files as $file)
1524
	{
1525
		if ($did_not_do)
1526
			$did_not_do--;
1527
		else
1528
		{
1529
			$upcontext['cur_file_num']++;
1530
			$upcontext['cur_file_name'] = $file[0];
1531
			// Do we actually need to do this still?
1532
			if (!isset($modSettings['smfVersion']) || $modSettings['smfVersion'] < $file[1])
1533
			{
1534
				$nextFile = parse_sql(dirname(__FILE__) . '/' . $file[0]);
1535
				if ($nextFile)
1536
				{
1537
					// Only update the version of this if complete.
1538
					$smcFunc['db_insert']('replace',
1539
						$db_prefix . 'settings',
1540
						array('variable' => 'string', 'value' => 'string'),
1541
						array('smfVersion', $file[2]),
1542
						array('variable')
1543
					);
1544
1545
					$modSettings['smfVersion'] = $file[2];
1546
				}
1547
1548
				// If this is XML we only do this stuff once.
1549
				if (isset($_GET['xml']))
1550
				{
1551
					// Flag to move on to the next.
1552
					$upcontext['completed_step'] = true;
1553
					// Did we complete the whole file?
1554
					if ($nextFile)
1555
						$upcontext['current_debug_item_num'] = -1;
1556
					return upgradeExit();
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...
1557
				}
1558
				elseif ($support_js)
1559
					break;
1560
			}
1561
			// Set the progress bar to be right as if we had - even if we hadn't...
1562
			$upcontext['step_progress'] = ($upcontext['cur_file_num'] / $upcontext['file_count']) * 100;
1563
		}
1564
	}
1565
1566
	$_GET['substep'] = 0;
1567
	// So the template knows we're done.
1568
	if (!$support_js)
1569
	{
1570
		$upcontext['changes_complete'] = true;
1571
1572
		return true;
1573
	}
1574
	return false;
1575
}
1576
1577
// Delete the damn thing!
1578
function DeleteUpgrade()
1579
{
1580
	global $command_line, $language, $upcontext, $sourcedir;
1581
	global $user_info, $maintenance, $smcFunc, $db_type, $txt, $settings;
1582
1583
	// Now it's nice to have some of the basic SMF source files.
1584
	if (!isset($_GET['ssi']) && !$command_line)
1585
		redirectLocation('&ssi=1');
1586
1587
	$upcontext['sub_template'] = 'upgrade_complete';
1588
	$upcontext['page_title'] = $txt['upgrade_complete'];
1589
1590
	$endl = $command_line ? "\n" : '<br>' . "\n";
1591
1592
	$changes = array(
1593
		'language' => '\'' . (substr($language, -4) == '.lng' ? substr($language, 0, -4) : $language) . '\'',
1594
		'db_error_send' => '1',
1595
		'upgradeData' => '\'\'',
1596
	);
1597
1598
	// Are we in maintenance mode?
1599
	if (isset($upcontext['user']['main']))
1600
	{
1601
		if ($command_line)
1602
			echo ' * ';
1603
		$upcontext['removed_maintenance'] = true;
1604
		$changes['maintenance'] = $upcontext['user']['main'];
1605
	}
1606
	// Otherwise if somehow we are in 2 let's go to 1.
1607
	elseif (!empty($maintenance) && $maintenance == 2)
1608
		$changes['maintenance'] = 1;
1609
1610
	// Wipe this out...
1611
	$upcontext['user'] = array();
1612
1613
	require_once($sourcedir . '/Subs-Admin.php');
1614
	updateSettingsFile($changes);
1615
1616
	// Clean any old cache files away.
1617
	upgrade_clean_cache();
1618
1619
	// Can we delete the file?
1620
	$upcontext['can_delete_script'] = is_writable(dirname(__FILE__)) || is_writable(__FILE__);
1621
1622
	// Now is the perfect time to fetch the SM files.
1623
	if ($command_line)
1624
		cli_scheduled_fetchSMfiles();
1625
	else
1626
	{
1627
		require_once($sourcedir . '/ScheduledTasks.php');
1628
		scheduled_fetchSMfiles(); // Now go get those files!
1629
		// This is needed in case someone invokes the upgrader using https when upgrading an http forum
1630
		if (httpsOn())
1631
			$settings['default_theme_url'] = strtr($settings['default_theme_url'], array('http://' => 'https://'));
1632
	}
1633
1634
	// Log what we've done.
1635
	if (empty($user_info['id']))
1636
		$user_info['id'] = !empty($upcontext['user']['id']) ? $upcontext['user']['id'] : 0;
1637
1638
	// Log the action manually, so CLI still works.
1639
	$smcFunc['db_insert']('',
1640
		'{db_prefix}log_actions',
1641
		array(
1642
			'log_time' => 'int', 'id_log' => 'int', 'id_member' => 'int', 'ip' => 'inet', 'action' => 'string',
1643
			'id_board' => 'int', 'id_topic' => 'int', 'id_msg' => 'int', 'extra' => 'string-65534',
1644
		),
1645
		array(
1646
			time(), 3, $user_info['id'], $command_line ? '127.0.0.1' : $user_info['ip'], 'upgrade',
1647
			0, 0, 0, json_encode(array('version' => SMF_FULL_VERSION, 'member' => $user_info['id'])),
1648
		),
1649
		array('id_action')
1650
	);
1651
	$user_info['id'] = 0;
1652
1653
	if ($command_line)
1654
	{
1655
		echo $endl;
1656
		echo 'Upgrade Complete!', $endl;
1657
		echo 'Please delete this file as soon as possible for security reasons.', $endl;
1658
		exit;
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...
1659
	}
1660
1661
	// Make sure it says we're done.
1662
	$upcontext['overall_percent'] = 100;
1663
	if (isset($upcontext['step_progress']))
1664
		unset($upcontext['step_progress']);
1665
1666
	$_GET['substep'] = 0;
1667
	return false;
1668
}
1669
1670
// Just like the built in one, but setup for CLI to not use themes.
1671
function cli_scheduled_fetchSMfiles()
1672
{
1673
	global $sourcedir, $language, $modSettings, $smcFunc;
1674
1675
	if (empty($modSettings['time_format']))
1676
		$modSettings['time_format'] = '%B %d, %Y, %I:%M:%S %p';
1677
1678
	// What files do we want to get
1679
	$request = $smcFunc['db_query']('', '
1680
		SELECT id_file, filename, path, parameters
1681
		FROM {db_prefix}admin_info_files',
1682
		array(
1683
		)
1684
	);
1685
1686
	$js_files = array();
1687
	while ($row = $smcFunc['db_fetch_assoc']($request))
1688
	{
1689
		$js_files[$row['id_file']] = array(
1690
			'filename' => $row['filename'],
1691
			'path' => $row['path'],
1692
			'parameters' => sprintf($row['parameters'], $language, urlencode($modSettings['time_format']), urlencode(SMF_FULL_VERSION)),
1693
		);
1694
	}
1695
	$smcFunc['db_free_result']($request);
1696
1697
	// We're gonna need fetch_web_data() to pull this off.
1698
	require_once($sourcedir . '/Subs.php');
1699
1700
	foreach ($js_files as $ID_FILE => $file)
1701
	{
1702
		// Create the url
1703
		$server = empty($file['path']) || substr($file['path'], 0, 7) != 'http://' ? 'https://www.simplemachines.org' : '';
1704
		$url = $server . (!empty($file['path']) ? $file['path'] : $file['path']) . $file['filename'] . (!empty($file['parameters']) ? '?' . $file['parameters'] : '');
1705
1706
		// Get the file
1707
		$file_data = fetch_web_data($url);
1708
1709
		// If we got an error - give up - the site might be down.
1710
		if ($file_data === false)
1711
			return throw_error(sprintf('Could not retrieve the file %1$s.', $url));
1712
1713
		// Save the file to the database.
1714
		$smcFunc['db_query']('substring', '
1715
			UPDATE {db_prefix}admin_info_files
1716
			SET data = SUBSTRING({string:file_data}, 1, 65534)
1717
			WHERE id_file = {int:id_file}',
1718
			array(
1719
				'id_file' => $ID_FILE,
1720
				'file_data' => $file_data,
1721
			)
1722
		);
1723
	}
1724
	return true;
1725
}
1726
1727
function convertSettingsToTheme()
1728
{
1729
	global $db_prefix, $modSettings, $smcFunc;
1730
1731
	$values = array(
1732
		'show_latest_member' => @$GLOBALS['showlatestmember'],
1733
		'show_bbc' => isset($GLOBALS['showyabbcbutt']) ? $GLOBALS['showyabbcbutt'] : @$GLOBALS['showbbcbutt'],
1734
		'show_modify' => @$GLOBALS['showmodify'],
1735
		'show_user_images' => @$GLOBALS['showuserpic'],
1736
		'show_blurb' => @$GLOBALS['showusertext'],
1737
		'show_gender' => @$GLOBALS['showgenderimage'],
1738
		'show_newsfader' => @$GLOBALS['shownewsfader'],
1739
		'display_recent_bar' => @$GLOBALS['Show_RecentBar'],
1740
		'show_member_bar' => @$GLOBALS['Show_MemberBar'],
1741
		'linktree_link' => @$GLOBALS['curposlinks'],
1742
		'show_profile_buttons' => @$GLOBALS['profilebutton'],
1743
		'show_mark_read' => @$GLOBALS['showmarkread'],
1744
		'newsfader_time' => @$GLOBALS['fadertime'],
1745
		'use_image_buttons' => empty($GLOBALS['MenuType']) ? 1 : 0,
1746
		'enable_news' => @$GLOBALS['enable_news'],
1747
		'return_to_post' => @$modSettings['returnToPost'],
1748
	);
1749
1750
	$themeData = array();
1751
	foreach ($values as $variable => $value)
1752
	{
1753
		if (!isset($value) || $value === null)
1754
			$value = 0;
1755
1756
		$themeData[] = array(0, 1, $variable, $value);
1757
	}
1758
	if (!empty($themeData))
1759
	{
1760
		$smcFunc['db_insert']('ignore',
1761
			$db_prefix . 'themes',
1762
			array('id_member' => 'int', 'id_theme' => 'int', 'variable' => 'string', 'value' => 'string'),
1763
			$themeData,
1764
			array('id_member', 'id_theme', 'variable')
1765
		);
1766
	}
1767
}
1768
1769
// This function only works with MySQL but that's fine as it is only used for v1.0.
1770
function convertSettingstoOptions()
1771
{
1772
	global $modSettings, $smcFunc;
1773
1774
	// Format: new_setting -> old_setting_name.
1775
	$values = array(
1776
		'calendar_start_day' => 'cal_startmonday',
1777
		'view_newest_first' => 'viewNewestFirst',
1778
		'view_newest_pm_first' => 'viewNewestFirst',
1779
	);
1780
1781
	foreach ($values as $variable => $value)
1782
	{
1783
		if (empty($modSettings[$value[0]]))
1784
			continue;
1785
1786
		$smcFunc['db_query']('', '
1787
			INSERT IGNORE INTO {db_prefix}themes
1788
				(id_member, id_theme, variable, value)
1789
			SELECT id_member, 1, {string:variable}, {string:value}
1790
			FROM {db_prefix}members',
1791
			array(
1792
				'variable' => $variable,
1793
				'value' => $modSettings[$value[0]],
1794
				'db_error_skip' => true,
1795
			)
1796
		);
1797
1798
		$smcFunc['db_query']('', '
1799
			INSERT IGNORE INTO {db_prefix}themes
1800
				(id_member, id_theme, variable, value)
1801
			VALUES (-1, 1, {string:variable}, {string:value})',
1802
			array(
1803
				'variable' => $variable,
1804
				'value' => $modSettings[$value[0]],
1805
				'db_error_skip' => true,
1806
			)
1807
		);
1808
	}
1809
}
1810
1811
function php_version_check()
1812
{
1813
	return version_compare(PHP_VERSION, $GLOBALS['required_php_version'], '>=');
1814
}
1815
1816
function db_version_check()
1817
{
1818
	global $db_type, $databases;
1819
1820
	$curver = eval($databases[$db_type]['version_check']);
0 ignored issues
show
introduced by
The use of eval() is discouraged.
Loading history...
1821
	$curver = preg_replace('~\-.+?$~', '', $curver);
1822
1823
	return version_compare($databases[$db_type]['version'], $curver, '<=');
1824
}
1825
1826
function fixRelativePath($path)
1827
{
1828
	global $install_path;
1829
1830
	// Fix the . at the start, clear any duplicate slashes, and fix any trailing slash...
1831
	return addslashes(preg_replace(array('~^\.([/\\\]|$)~', '~[/]+~', '~[\\\]+~', '~[/\\\]$~'), array($install_path . '$1', '/', '\\', ''), $path));
1832
}
1833
1834
function parse_sql($filename)
1835
{
1836
	global $db_prefix, $db_collation, $boarddir, $boardurl, $command_line, $file_steps, $step_progress, $custom_warning;
1837
	global $upcontext, $support_js, $is_debug, $db_type, $db_character_set;
1838
1839
/*
1840
	Failure allowed on:
1841
		- INSERT INTO but not INSERT IGNORE INTO.
1842
		- UPDATE IGNORE but not UPDATE.
1843
		- ALTER TABLE and ALTER IGNORE TABLE.
1844
		- DROP TABLE.
1845
	Yes, I realize that this is a bit confusing... maybe it should be done differently?
1846
1847
	If a comment...
1848
		- begins with --- it is to be output, with a break only in debug mode. (and say successful\n\n if there was one before.)
1849
		- begins with ---# it is a debugging statement, no break - only shown at all in debug.
1850
		- is only ---#, it is "done." and then a break - only shown in debug.
1851
		- begins with ---{ it is a code block terminating at ---}.
1852
1853
	Every block of between "--- ..."s is a step.  Every "---#" section represents a substep.
1854
1855
	Replaces the following variables:
1856
		- {$boarddir}
1857
		- {$boardurl}
1858
		- {$db_prefix}
1859
		- {$db_collation}
1860
*/
1861
1862
	// May want to use extended functionality.
1863
	db_extend();
1864
	db_extend('packages');
1865
1866
	// Our custom error handler - does nothing but does stop public errors from XML!
1867
	set_error_handler(
1868
		function($errno, $errstr, $errfile, $errline) use ($support_js)
1869
		{
1870
			if ($support_js)
1871
				return true;
1872
			else
1873
				echo 'Error: ' . $errstr . ' File: ' . $errfile . ' Line: ' . $errline;
1874
		}
1875
	);
1876
1877
	// If we're on MySQL, set {db_collation}; this approach is used throughout upgrade_2-0_mysql.php to set new tables to utf8
1878
	// Note it is expected to be in the format: ENGINE=MyISAM{$db_collation};
1879
	if ($db_type == 'mysql')
1880
		$db_collation = ' DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci';
1881
	else
1882
		$db_collation = '';
1883
1884
	$endl = $command_line ? "\n" : '<br>' . "\n";
1885
1886
	$lines = file($filename);
1887
1888
	$current_type = 'sql';
1889
	$current_data = '';
1890
	$substep = 0;
1891
	$last_step = '';
1892
1893
	// Make sure all newly created tables will have the proper characters set; this approach is used throughout upgrade_2-1_mysql.php
1894
	if (isset($db_character_set) && $db_character_set === 'utf8')
1895
		$lines = str_replace(') ENGINE=MyISAM;', ') ENGINE=MyISAM DEFAULT CHARSET=utf8 COLLATE=utf8_general_ci;', $lines);
1896
1897
	// Count the total number of steps within this file - for progress.
1898
	$file_steps = substr_count(implode('', $lines), '---#');
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

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

2152
			mysqli_select_db(/** @scrutinizer ignore-type */ $db_connection, $db_name);
Loading history...
2153
			if ($db_connection)
2154
			{
2155
				$result = mysqli_query($db_connection, $string);
2156
				if ($result !== false)
2157
					return $result;
2158
			}
2159
		}
2160
		// Duplicate column name... should be okay ;).
2161
		elseif (in_array($mysqli_errno, array(1060, 1061, 1068, 1091)))
2162
			return false;
2163
		// Duplicate insert... make sure it's the proper type of query ;).
2164
		elseif (in_array($mysqli_errno, array(1054, 1062, 1146)) && $error_query)
2165
			return false;
2166
		// Creating an index on a non-existent column.
2167
		elseif ($mysqli_errno == 1072)
2168
			return false;
2169
		elseif ($mysqli_errno == 1050 && substr(trim($string), 0, 12) == 'RENAME TABLE')
2170
			return false;
2171
	}
2172
	// If a table already exists don't go potty.
2173
	else
2174
	{
2175
		if (in_array(substr(trim($string), 0, 8), array('CREATE T', 'CREATE S', 'DROP TABL', 'ALTER TA', 'CREATE I', 'CREATE U')))
2176
		{
2177
			if (strpos($db_error_message, 'exist') !== false)
2178
				return true;
2179
		}
2180
		elseif (strpos(trim($string), 'INSERT ') !== false)
2181
		{
2182
			if (strpos($db_error_message, 'duplicate') !== false || $ignore_insert_error)
2183
				return true;
2184
		}
2185
	}
2186
2187
	// Get the query string so we pass everything.
2188
	$query_string = '';
2189
	foreach ($_GET as $k => $v)
2190
		$query_string .= ';' . $k . '=' . $v;
2191
	if (strlen($query_string) != 0)
2192
		$query_string = '?' . substr($query_string, 1);
2193
2194
	if ($command_line)
2195
	{
2196
		echo 'Unsuccessful!  Database error message:', "\n", $db_error_message, "\n";
2197
		die;
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...
2198
	}
2199
2200
	// Bit of a bodge - do we want the error?
2201
	if (!empty($upcontext['return_error']))
2202
	{
2203
		$upcontext['error_message'] = $db_error_message;
2204
		$upcontext['error_string'] = $string;
2205
		return false;
2206
	}
2207
2208
	// Otherwise we have to display this somewhere appropriate if possible.
2209
	$upcontext['forced_error_message'] = '
2210
			<strong>' . $txt['upgrade_unsuccessful'] . '</strong><br>
2211
2212
			<div style="margin: 2ex;">
2213
				' . $txt['upgrade_thisquery'] . '
2214
				<blockquote><pre>' . nl2br(htmlspecialchars(trim($string))) . ';</pre></blockquote>
2215
2216
				' . $txt['upgrade_causerror'] . '
2217
				<blockquote>' . nl2br(htmlspecialchars($db_error_message)) . '</blockquote>
2218
			</div>
2219
2220
			<form action="' . $upgradeurl . $query_string . '" method="post">
2221
				<input type="submit" value="' . $txt['upgrade_respondtime_clickhere'] . '" class="button">
2222
			</form>
2223
		</div>';
2224
2225
	upgradeExit();
2226
}
2227
2228
// This performs a table alter, but does it unbuffered so the script can time out professionally.
2229
function protected_alter($change, $substep, $is_test = false)
2230
{
2231
	global $db_prefix, $smcFunc;
2232
2233
	db_extend('packages');
2234
2235
	// Firstly, check whether the current index/column exists.
2236
	$found = false;
2237
	if ($change['type'] === 'column')
2238
	{
2239
		$columns = $smcFunc['db_list_columns']('{db_prefix}' . $change['table'], true);
2240
		foreach ($columns as $column)
2241
		{
2242
			// Found it?
2243
			if ($column['name'] === $change['name'])
2244
			{
2245
				$found |= true;
2246
				// Do some checks on the data if we have it set.
2247
				if (isset($change['col_type']))
2248
					$found &= $change['col_type'] === $column['type'];
2249
				if (isset($change['null_allowed']))
2250
					$found &= $column['null'] == $change['null_allowed'];
2251
				if (isset($change['default']))
2252
					$found &= $change['default'] === $column['default'];
2253
			}
2254
		}
2255
	}
2256
	elseif ($change['type'] === 'index')
2257
	{
2258
		$request = upgrade_query('
2259
			SHOW INDEX
2260
			FROM ' . $db_prefix . $change['table']);
2261
		if ($request !== false)
2262
		{
2263
			$cur_index = array();
2264
2265
			while ($row = $smcFunc['db_fetch_assoc']($request))
2266
				if ($row['Key_name'] === $change['name'])
2267
					$cur_index[(int) $row['Seq_in_index']] = $row['Column_name'];
2268
2269
			ksort($cur_index, SORT_NUMERIC);
2270
			$found = array_values($cur_index) === $change['target_columns'];
2271
2272
			$smcFunc['db_free_result']($request);
2273
		}
2274
	}
2275
2276
	// If we're trying to add and it's added, we're done.
2277
	if ($found && in_array($change['method'], array('add', 'change')))
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...
2278
		return true;
2279
	// Otherwise if we're removing and it wasn't found we're also done.
2280
	elseif (!$found && in_array($change['method'], array('remove', 'change_remove')))
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...
2281
		return true;
2282
	// Otherwise is it just a test?
2283
	elseif ($is_test)
2284
		return false;
2285
2286
	// Not found it yet? Bummer! How about we see if we're currently doing it?
2287
	$running = false;
2288
	$found = false;
2289
	while (1 == 1)
2290
	{
2291
		$request = upgrade_query('
2292
			SHOW FULL PROCESSLIST');
2293
		while ($row = $smcFunc['db_fetch_assoc']($request))
2294
		{
2295
			if (strpos($row['Info'], 'ALTER TABLE ' . $db_prefix . $change['table']) !== false && strpos($row['Info'], $change['text']) !== false)
2296
				$found = true;
2297
		}
2298
2299
		// Can't find it? Then we need to run it fools!
2300
		if (!$found && !$running)
2301
		{
2302
			$smcFunc['db_free_result']($request);
2303
2304
			$success = upgrade_query('
2305
				ALTER TABLE ' . $db_prefix . $change['table'] . '
2306
				' . $change['text'], true) !== false;
2307
2308
			if (!$success)
2309
				return false;
2310
2311
			// Return
2312
			$running = true;
2313
		}
2314
		// What if we've not found it, but we'd ran it already? Must of completed.
2315
		elseif (!$found)
2316
		{
2317
			$smcFunc['db_free_result']($request);
2318
			return true;
2319
		}
2320
2321
		// Pause execution for a sec or three.
2322
		sleep(3);
2323
2324
		// Can never be too well protected.
2325
		nextSubstep($substep);
2326
	}
2327
2328
	// Protect it.
2329
	nextSubstep($substep);
2330
}
2331
2332
/**
2333
 * Alter a text column definition preserving its character set.
2334
 *
2335
 * @param array $change
2336
 * @param int $substep
2337
 */
2338
function textfield_alter($change, $substep)
2339
{
2340
	global $db_prefix, $smcFunc;
2341
2342
	$request = $smcFunc['db_query']('', '
2343
		SHOW FULL COLUMNS
2344
		FROM {db_prefix}' . $change['table'] . '
2345
		LIKE {string:column}',
2346
		array(
2347
			'column' => $change['column'],
2348
			'db_error_skip' => true,
2349
		)
2350
	);
2351
	if ($smcFunc['db_num_rows']($request) === 0)
2352
		die('Unable to find column ' . $change['column'] . ' inside table ' . $db_prefix . $change['table']);
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...
2353
	$table_row = $smcFunc['db_fetch_assoc']($request);
2354
	$smcFunc['db_free_result']($request);
2355
2356
	// If something of the current column definition is different, fix it.
2357
	$column_fix = $table_row['Type'] !== $change['type'] || (strtolower($table_row['Null']) === 'yes') !== $change['null_allowed'] || ($table_row['Default'] === null) !== !isset($change['default']) || (isset($change['default']) && $change['default'] !== $table_row['Default']);
2358
2359
	// Columns that previously allowed null, need to be converted first.
2360
	$null_fix = strtolower($table_row['Null']) === 'yes' && !$change['null_allowed'];
2361
2362
	// Get the character set that goes with the collation of the column.
2363
	if ($column_fix && !empty($table_row['Collation']))
2364
	{
2365
		$request = $smcFunc['db_query']('', '
2366
			SHOW COLLATION
2367
			LIKE {string:collation}',
2368
			array(
2369
				'collation' => $table_row['Collation'],
2370
				'db_error_skip' => true,
2371
			)
2372
		);
2373
		// No results? Just forget it all together.
2374
		if ($smcFunc['db_num_rows']($request) === 0)
2375
			unset($table_row['Collation']);
2376
		else
2377
			$collation_info = $smcFunc['db_fetch_assoc']($request);
2378
		$smcFunc['db_free_result']($request);
2379
	}
2380
2381
	if ($column_fix)
2382
	{
2383
		// Make sure there are no NULL's left.
2384
		if ($null_fix)
2385
			$smcFunc['db_query']('', '
2386
				UPDATE {db_prefix}' . $change['table'] . '
2387
				SET ' . $change['column'] . ' = {string:default}
2388
				WHERE ' . $change['column'] . ' IS NULL',
2389
				array(
2390
					'default' => isset($change['default']) ? $change['default'] : '',
2391
					'db_error_skip' => true,
2392
				)
2393
			);
2394
2395
		// Do the actual alteration.
2396
		$smcFunc['db_query']('', '
2397
			ALTER TABLE {db_prefix}' . $change['table'] . '
2398
			CHANGE COLUMN ' . $change['column'] . ' ' . $change['column'] . ' ' . $change['type'] . (isset($collation_info['Charset']) ? ' CHARACTER SET ' . $collation_info['Charset'] . ' COLLATE ' . $collation_info['Collation'] : '') . ($change['null_allowed'] ? '' : ' NOT NULL') . (isset($change['default']) ? ' default {string:default}' : ''),
2399
			array(
2400
				'default' => isset($change['default']) ? $change['default'] : '',
2401
				'db_error_skip' => true,
2402
			)
2403
		);
2404
	}
2405
	nextSubstep($substep);
2406
}
2407
2408
// Check if we need to alter this query.
2409
function checkChange(&$change)
2410
{
2411
	global $smcFunc, $db_type, $databases;
2412
	static $database_version, $where_field_support;
2413
2414
	// Attempt to find a database_version.
2415
	if (empty($database_version))
2416
	{
2417
		$database_version = $databases[$db_type]['version_check'];
2418
		$where_field_support = $db_type == 'mysql' && version_compare('5.0', $database_version, '<=');
2419
	}
2420
2421
	// Not a column we need to check on?
2422
	if (!in_array($change['name'], array('memberGroups', 'passwordSalt')))
2423
		return;
2424
2425
	// Break it up you (six|seven).
2426
	$temp = explode(' ', str_replace('NOT NULL', 'NOT_NULL', $change['text']));
2427
2428
	// Can we support a shortcut method?
2429
	if ($where_field_support)
2430
	{
2431
		// Get the details about this change.
2432
		$request = $smcFunc['db_query']('', '
2433
			SHOW FIELDS
2434
			FROM {db_prefix}{raw:table}
2435
			WHERE Field = {string:old_name} OR Field = {string:new_name}',
2436
			array(
2437
				'table' => $change['table'],
2438
				'old_name' => $temp[1],
2439
				'new_name' => $temp[2],
2440
			)
2441
		);
2442
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2443
		if ($smcFunc['db_num_rows'] != 1)
2444
			return;
2445
2446
		list (, $current_type) = $smcFunc['db_fetch_assoc']($request);
2447
		$smcFunc['db_free_result']($request);
2448
	}
2449
	else
2450
	{
2451
		// Do this the old fashion, sure method way.
2452
		$request = $smcFunc['db_query']('', '
2453
			SHOW FIELDS
2454
			FROM {db_prefix}{raw:table}',
2455
			array(
2456
				'table' => $change['table'],
2457
			)
2458
		);
2459
		// Mayday!
2460
		// !!! This doesn't technically work because we don't pass request into it, but it hasn't broke anything yet.
2461
		if ($smcFunc['db_num_rows'] == 0)
2462
			return;
2463
2464
		// Oh where, oh where has my little field gone. Oh where can it be...
2465
		while ($row = $smcFunc['db_query']($request))
2466
			if ($row['Field'] == $temp[1] || $row['Field'] == $temp[2])
2467
			{
2468
				$current_type = $row['Type'];
2469
				break;
2470
			}
2471
	}
2472
2473
	// If this doesn't match, the column may of been altered for a reason.
2474
	if (trim($current_type) != trim($temp[3]))
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...
2475
		$temp[3] = $current_type;
2476
2477
	// Piece this back together.
2478
	$change['text'] = str_replace('NOT_NULL', 'NOT NULL', implode(' ', $temp));
2479
}
2480
2481
// The next substep.
2482
function nextSubstep($substep)
2483
{
2484
	global $start_time, $timeLimitThreshold, $command_line, $custom_warning;
2485
	global $step_progress, $is_debug, $upcontext;
2486
2487
	if ($_GET['substep'] < $substep)
2488
		$_GET['substep'] = $substep;
2489
2490
	if ($command_line)
2491
	{
2492
		if (time() - $start_time > 1 && empty($is_debug))
2493
		{
2494
			echo '.';
2495
			$start_time = time();
2496
		}
2497
		return;
2498
	}
2499
2500
	@set_time_limit(300);
2501
	if (function_exists('apache_reset_timeout'))
2502
		@apache_reset_timeout();
2503
2504
	if (time() - $start_time <= $timeLimitThreshold)
2505
		return;
2506
2507
	// Do we have some custom step progress stuff?
2508
	if (!empty($step_progress))
2509
	{
2510
		$upcontext['substep_progress'] = 0;
2511
		$upcontext['substep_progress_name'] = $step_progress['name'];
2512
		if ($step_progress['current'] > $step_progress['total'])
2513
			$upcontext['substep_progress'] = 99.9;
2514
		else
2515
			$upcontext['substep_progress'] = ($step_progress['current'] / $step_progress['total']) * 100;
2516
2517
		// Make it nicely rounded.
2518
		$upcontext['substep_progress'] = round($upcontext['substep_progress'], 1);
2519
	}
2520
2521
	// If this is XML we just exit right away!
2522
	if (isset($_GET['xml']))
2523
		return upgradeExit();
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...
2524
2525
	// We're going to pause after this!
2526
	$upcontext['pause'] = true;
2527
2528
	$upcontext['query_string'] = '';
2529
	foreach ($_GET as $k => $v)
2530
	{
2531
		if ($k != 'data' && $k != 'substep' && $k != 'step')
2532
			$upcontext['query_string'] .= ';' . $k . '=' . $v;
2533
	}
2534
2535
	// Custom warning?
2536
	if (!empty($custom_warning))
2537
		$upcontext['custom_warning'] = $custom_warning;
2538
2539
	upgradeExit();
2540
}
2541
2542
function cmdStep0()
2543
{
2544
	global $boarddir, $sourcedir, $modSettings, $start_time, $cachedir, $databases, $db_type, $smcFunc, $upcontext;
2545
	global $is_debug;
2546
	$start_time = time();
2547
2548
	ob_end_clean();
2549
	ob_implicit_flush(1);
2550
	@set_time_limit(600);
2551
2552
	if (!isset($_SERVER['argv']))
2553
		$_SERVER['argv'] = array();
2554
	$_GET['maint'] = 1;
2555
2556
	foreach ($_SERVER['argv'] as $i => $arg)
2557
	{
2558
		if (preg_match('~^--language=(.+)$~', $arg, $match) != 0)
2559
			$upcontext['lang'] = $match[1];
2560
		elseif (preg_match('~^--path=(.+)$~', $arg) != 0)
2561
			continue;
2562
		elseif ($arg == '--no-maintenance')
2563
			$_GET['maint'] = 0;
2564
		elseif ($arg == '--debug')
2565
			$is_debug = true;
2566
		elseif ($arg == '--backup')
2567
			$_POST['backup'] = 1;
2568
		elseif ($arg == '--template' && (file_exists($boarddir . '/template.php') || file_exists($boarddir . '/template.html') && !file_exists($modSettings['theme_dir'] . '/converted')))
2569
			$_GET['conv'] = 1;
2570
		elseif ($i != 0)
2571
		{
2572
			echo 'SMF Command-line Upgrader
2573
Usage: /path/to/php -f ' . basename(__FILE__) . ' -- [OPTION]...
2574
2575
	--language=LANG         Reset the forum\'s language to LANG.
2576
	--no-maintenance        Don\'t put the forum into maintenance mode.
2577
	--debug                 Output debugging information.
2578
	--backup                Create backups of tables with "backup_" prefix.';
2579
			echo "\n";
2580
			exit;
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...
2581
		}
2582
	}
2583
2584
	if (!php_version_check())
2585
		print_error('Error: PHP ' . PHP_VERSION . ' does not match version requirements.', true);
2586
	if (!db_version_check())
2587
		print_error('Error: ' . $databases[$db_type]['name'] . ' ' . $databases[$db_type]['version'] . ' does not match minimum requirements.', true);
2588
2589
	// Do some checks to make sure they have proper privileges
2590
	db_extend('packages');
2591
2592
	// CREATE
2593
	$create = $smcFunc['db_create_table']('{db_prefix}priv_check', array(array('name' => 'id_test', 'type' => 'int', 'size' => 10, 'unsigned' => true, 'auto' => true)), array(array('columns' => array('id_test'), 'primary' => true)), array(), 'overwrite');
2594
2595
	// ALTER
2596
	$alter = $smcFunc['db_add_column']('{db_prefix}priv_check', array('name' => 'txt', 'type' => 'varchar', 'size' => 4, 'null' => false, 'default' => ''));
2597
2598
	// DROP
2599
	$drop = $smcFunc['db_drop_table']('{db_prefix}priv_check');
2600
2601
	// Sorry... we need CREATE, ALTER and DROP
2602
	if (!$create || !$alter || !$drop)
2603
		print_error("The " . $databases[$db_type]['name'] . " user you have set in Settings.php does not have proper privileges.\n\nPlease ask your host to give this user the ALTER, CREATE, and DROP privileges.", true);
2604
2605
	$check = @file_exists($modSettings['theme_dir'] . '/index.template.php')
2606
		&& @file_exists($sourcedir . '/QueryString.php')
2607
		&& @file_exists($sourcedir . '/ManageBoards.php');
2608
	if (!$check && !isset($modSettings['smfVersion']))
2609
		print_error('Error: Some files are missing or out-of-date.', true);
2610
2611
	// Do a quick version spot check.
2612
	$temp = substr(@implode('', @file($boarddir . '/index.php')), 0, 4096);
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

2612
	$temp = substr(@implode('', /** @scrutinizer ignore-type */ @file($boarddir . '/index.php')), 0, 4096);
Loading history...
2613
	preg_match('~\*\s@version\s+(.+)[\s]{2}~i', $temp, $match);
2614
	if (empty($match[1]) || (trim($match[1]) != SMF_VERSION))
2615
		print_error('Error: Some files have not yet been updated properly.');
2616
2617
	// Make sure Settings.php is writable.
2618
	quickFileWritable($boarddir . '/Settings.php');
2619
	if (!is_writable($boarddir . '/Settings.php'))
2620
		print_error('Error: Unable to obtain write access to "Settings.php".', true);
2621
2622
	// Make sure Settings_bak.php is writable.
2623
	quickFileWritable($boarddir . '/Settings_bak.php');
2624
	if (!is_writable($boarddir . '/Settings_bak.php'))
2625
		print_error('Error: Unable to obtain write access to "Settings_bak.php".');
2626
2627
	if (isset($modSettings['agreement']) && (!is_writable($boarddir) || file_exists($boarddir . '/agreement.txt')) && !is_writable($boarddir . '/agreement.txt'))
2628
		print_error('Error: Unable to obtain write access to "agreement.txt".');
2629
	elseif (isset($modSettings['agreement']))
2630
	{
2631
		$fp = fopen($boarddir . '/agreement.txt', 'w');
2632
		fwrite($fp, $modSettings['agreement']);
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

2632
		fwrite(/** @scrutinizer ignore-type */ $fp, $modSettings['agreement']);
Loading history...
2633
		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

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